When the last reference to a descriptor table is dropped close_files() closes every file but punts the actual work to task work. For an exiting task that task work only runs in exit_task_work(). Before commit 4a9d4b024a31 ("switch fput to task_work_add") fput() was synchronous everywhere and exit released its files in exit_files(). The deferral made fput() safe from any context. And exit_files() offloaded to task work as a side-effect. And that has downsides. Oleg and Neil noticed that some time ago. A task that exits with a big descriptor table ends up queueing a very large number of files on task work. That walks the task work list under ->pi_lock and costs a lot of atomics too. Let close_files() close right away. The walk puts the last references through filp_close_list(). If it's the last reference it places them on a private list and drains it through fput_list(). Note, this keeps keeps the exact order the deferred path had. Every filp_flush() first and then the final __fput()s in the reverse order task_work_run() giving the same ordering even. Every put of a dying table is synchronous now: - exit_files() - copy_process() - close_range(CLOSE_RANGE_UNSHARE) - unshare(2) - exec Kernel threads don't own a file descriptor table and exec already splats where they to exec. kthreadd and every kthread share init_files and init_task pins that forever. Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/file.c b/fs/file.c index 185de8deb39d..47ce844be95b 100644 --- a/fs/file.c +++ b/fs/file.c @@ -489,7 +489,7 @@ int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) return 0; } -static struct fdtable *close_files(struct files_struct * files) +static struct fdtable *close_files(struct files_struct *files) { /* * It is safe to dereference the fd table without RCU or @@ -498,6 +498,7 @@ static struct fdtable *close_files(struct files_struct * files) */ struct fdtable *fdt = rcu_dereference_raw(files->fdt); unsigned int i, j = 0; + LLIST_HEAD(to_close); for (;;) { unsigned long set; @@ -509,7 +510,8 @@ static struct fdtable *close_files(struct files_struct * files) if (set & 1) { struct file *file = fdt->fd[i]; if (file) { - filp_close(file, files); + filp_flush(file, files); + fput_close_list(file, &to_close); cond_resched(); } } @@ -518,6 +520,7 @@ static struct fdtable *close_files(struct files_struct * files) } } + fput_list(&to_close); return fdt; } -- 2.53.0