From 4a9d4b024a3102fc083c925c242d98ac27b1c5f6 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 24 Jun 2012 09:56:45 +0400 Subject: [PATCH] switch fput to task_work_add ... and schedule_work() for interrupt/kernel_thread callers (and yes, now it *is* OK to call from interrupt). We are guaranteed that __fput() will be done before we return to userland (or exit). Note that for fput() from a kernel thread we get an async behaviour; it's almost always OK, but sometimes you might need to have __fput() completed before you do anything else. There are two mechanisms for that - a general barrier (flush_delayed_fput()) and explicit __fput_sync(). Both should be used with care (as was the case for fput() from kernel threads all along). See comments in fs/file_table.c for details. Signed-off-by: Al Viro --- fs/file_table.c | 72 ++++++++++++++++++++++++++++++++++++++++++-- include/linux/file.h | 3 ++ init/main.c | 3 +- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/fs/file_table.c b/fs/file_table.c index 9ace2781931..b3fc4d67a26 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -251,7 +253,6 @@ static void __fput(struct file *file) } fops_put(file->f_op); put_pid(file->f_owner.pid); - file_sb_list_del(file); if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) i_readcount_dec(inode); if (file->f_mode & FMODE_WRITE) @@ -263,10 +264,77 @@ static void __fput(struct file *file) mntput(mnt); } +static DEFINE_SPINLOCK(delayed_fput_lock); +static LIST_HEAD(delayed_fput_list); +static void delayed_fput(struct work_struct *unused) +{ + LIST_HEAD(head); + spin_lock_irq(&delayed_fput_lock); + list_splice_init(&delayed_fput_list, &head); + spin_unlock_irq(&delayed_fput_lock); + while (!list_empty(&head)) { + struct file *f = list_first_entry(&head, struct file, f_u.fu_list); + list_del_init(&f->f_u.fu_list); + __fput(f); + } +} + +static void ____fput(struct callback_head *work) +{ + __fput(container_of(work, struct file, f_u.fu_rcuhead)); +} + +/* + * If kernel thread really needs to have the final fput() it has done + * to complete, call this. The only user right now is the boot - we + * *do* need to make sure our writes to binaries on initramfs has + * not left us with opened struct file waiting for __fput() - execve() + * won't work without that. Please, don't add more callers without + * very good reasons; in particular, never call that with locks + * held and never call that from a thread that might need to do + * some work on any kind of umount. + */ +void flush_delayed_fput(void) +{ + delayed_fput(NULL); +} + +static DECLARE_WORK(delayed_fput_work, delayed_fput); + void fput(struct file *file) { - if (atomic_long_dec_and_test(&file->f_count)) + if (atomic_long_dec_and_test(&file->f_count)) { + struct task_struct *task = current; + file_sb_list_del(file); + if (unlikely(in_interrupt() || task->flags & PF_KTHREAD)) { + unsigned long flags; + spin_lock_irqsave(&delayed_fput_lock, flags); + list_add(&file->f_u.fu_list, &delayed_fput_list); + schedule_work(&delayed_fput_work); + spin_unlock_irqrestore(&delayed_fput_lock, flags); + return; + } + init_task_work(&file->f_u.fu_rcuhead, ____fput); + task_work_add(task, &file->f_u.fu_rcuhead, true); + } +} + +/* + * synchronous analog of fput(); for kernel threads that might be needed + * in some umount() (and thus can't use flush_delayed_fput() without + * risking deadlocks), need to wait for completion of __fput() and know + * for this specific struct file it won't involve anything that would + * need them. Use only if you really need it - at the very least, + * don't blindly convert fput() by kernel thread to that. + */ +void __fput_sync(struct file *file) +{ + if (atomic_long_dec_and_test(&file->f_count)) { + struct task_struct *task = current; + file_sb_list_del(file); + BUG_ON(!(task->flags & PF_KTHREAD)); __fput(file); + } } EXPORT_SYMBOL(fput); diff --git a/include/linux/file.h b/include/linux/file.h index 58bf158c53d..a22408bac0d 100644 --- a/include/linux/file.h +++ b/include/linux/file.h @@ -39,4 +39,7 @@ extern void put_unused_fd(unsigned int fd); extern void fd_install(unsigned int fd, struct file *file); +extern void flush_delayed_fput(void); +extern void __fput_sync(struct file *); + #endif /* __LINUX_FILE_H */ diff --git a/init/main.c b/init/main.c index b5cc0a7c470..3f151f6c6da 100644 --- a/init/main.c +++ b/init/main.c @@ -68,6 +68,7 @@ #include #include #include +#include #include #include @@ -804,8 +805,8 @@ static noinline int init_post(void) system_state = SYSTEM_RUNNING; numa_default_policy(); - current->signal->flags |= SIGNAL_UNKILLABLE; + flush_delayed_fput(); if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); -- 2.20.1