[PATCH RFC 1/2] fs: add superblock deferred iput infrastructure
Mikhail Rudenko <[email protected]>
| Newsgroups | org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <20260813-deferred-dirtytime-iput-v1-1-ad2fb3ad6bdb@yandex-team.ru> |
Add the superblock side of deferred final iput processing. The new helper queues an inode on a per-superblock list and schedules an unbound WQ_MEM_RECLAIM worker to retry iput() outside the caller's context. Superblock teardown shuts the queue down and flushes pending work after the shrinker is unregistered and before ->kill_sb(). This processes queued inodes while the superblock and filesystem are still alive. This is preparation for deferring dirtytime final iput from PF_MEMALLOC context. Signed-off-by: Mikhail Rudenko <[email protected]> --- fs/dcache.c | 1 + fs/internal.h | 3 ++ fs/super.c | 92 ++++++++++++++++++++++++++++++++++++++++++ include/linux/fs/super_types.h | 6 +++ 4 files changed, 102 insertions(+) diff --git a/fs/dcache.c b/fs/dcache.c index 3e9af9de7074..5465d8dbfdd2 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -3511,6 +3511,7 @@ void __init vfs_caches_init(void) { filename_init(); dcache_init(); + super_init(); inode_init(); files_init(); files_maxfiles_init(); diff --git a/fs/internal.h b/fs/internal.h index 355d93f92208..641b9a0c612d 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -9,6 +9,7 @@ struct super_block; struct file_system_type; struct iomap; struct iomap_ops; +struct inode; struct linux_binprm; struct path; struct mount; @@ -138,6 +139,8 @@ extern bool super_trylock_shared(struct super_block *sb); struct super_block *user_get_super(dev_t, bool excl); void put_super(struct super_block *sb); extern bool mount_capable(struct fs_context *); +void __init super_init(void); +int super_defer_iput(struct inode *inode); /* * Prepare superblock for changing its read-only state (i.e., either remount diff --git a/fs/super.c b/fs/super.c index a8fd61136aaf..2f318694c98b 100644 --- a/fs/super.c +++ b/fs/super.c @@ -37,6 +37,7 @@ #include <linux/user_namespace.h> #include <linux/fs_context.h> #include <linux/fserror.h> +#include <linux/workqueue.h> #include <uapi/linux/mount.h> #include "internal.h" @@ -45,6 +46,7 @@ static int thaw_super_locked(struct super_block *sb, enum freeze_holder who, static LIST_HEAD(super_blocks); static DEFINE_SPINLOCK(sb_lock); +static struct workqueue_struct *super_deferred_iput_wq __ro_after_init; static char *sb_writers_name[SB_FREEZE_LEVELS] = { "sb_writers", @@ -52,6 +54,89 @@ static char *sb_writers_name[SB_FREEZE_LEVELS] = { "sb_internal", }; +/* + * sb->s_deferred_iput_lock protects sb->s_deferred_iputs and inode->i_lru + * while the inode is queued there. + */ +static void super_deferred_iput_work(struct work_struct *work) +{ + struct super_block *sb = container_of(work, struct super_block, + s_deferred_iput_work); + LIST_HEAD(pending); + + for (;;) { + spin_lock(&sb->s_deferred_iput_lock); + list_splice_init(&sb->s_deferred_iputs, &pending); + spin_unlock(&sb->s_deferred_iput_lock); + + if (list_empty(&pending)) + break; + + while (!list_empty(&pending)) { + struct inode *inode; + + inode = list_first_entry(&pending, struct inode, i_lru); + list_del_init(&inode->i_lru); + iput(inode); + cond_resched(); + } + } +} + +static void __init super_deferred_iput_wq_init(void) +{ + super_deferred_iput_wq = alloc_workqueue("super_deferred_iput", + WQ_UNBOUND | WQ_MEM_RECLAIM, 0); + if (!super_deferred_iput_wq) + panic("Failed to allocate super deferred iput workqueue\n"); +} + +void __init super_init(void) +{ + super_deferred_iput_wq_init(); +} + +static void super_deferred_iput_init(struct super_block *sb) +{ + spin_lock_init(&sb->s_deferred_iput_lock); + INIT_LIST_HEAD(&sb->s_deferred_iputs); + INIT_WORK(&sb->s_deferred_iput_work, super_deferred_iput_work); + sb->s_deferred_iput_shutdown = false; +} + +static void super_deferred_iput_shutdown(struct super_block *sb) +{ + bool empty; + + spin_lock(&sb->s_deferred_iput_lock); + sb->s_deferred_iput_shutdown = true; + spin_unlock(&sb->s_deferred_iput_lock); + + flush_work(&sb->s_deferred_iput_work); + + spin_lock(&sb->s_deferred_iput_lock); + empty = list_empty(&sb->s_deferred_iputs); + spin_unlock(&sb->s_deferred_iput_lock); + WARN_ON_ONCE(!empty); +} + +int super_defer_iput(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + int ret = -ESHUTDOWN; + + spin_lock(&sb->s_deferred_iput_lock); + if (!sb->s_deferred_iput_shutdown) { + list_add_tail(&inode->i_lru, &sb->s_deferred_iputs); + /* Queue under the lock so shutdown cannot miss this work. */ + queue_work(super_deferred_iput_wq, &sb->s_deferred_iput_work); + ret = 0; + } + spin_unlock(&sb->s_deferred_iput_lock); + + return ret; +} + static inline void __super_lock(struct super_block *sb, bool excl) { if (excl) @@ -363,6 +448,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, mutex_init(&s->s_sync_lock); INIT_LIST_HEAD(&s->s_inodes); spin_lock_init(&s->s_inode_list_lock); + super_deferred_iput_init(s); INIT_LIST_HEAD(&s->s_inodes_wb); spin_lock_init(&s->s_inode_wblist_lock); fserror_mount(s); @@ -474,6 +560,12 @@ void deactivate_locked_super(struct super_block *s) struct file_system_type *fs = s->s_type; if (atomic_dec_and_test(&s->s_active)) { shrinker_free(s->s_shrink); + /* + * The shrinker can leave final inode references queued for + * processing outside reclaim. Drain them before + * filesystem-specific shutdown starts. + */ + super_deferred_iput_shutdown(s); fs->kill_sb(s); kill_super_notify(s); diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h index ef7941e9dc79..9bd4abbdbd51 100644 --- a/include/linux/fs/super_types.h +++ b/include/linux/fs/super_types.h @@ -255,6 +255,12 @@ struct super_block { */ struct list_lru s_dentry_lru; struct list_lru s_inode_lru; + /* Protects s_deferred_iputs and s_deferred_iput_shutdown. */ + spinlock_t s_deferred_iput_lock; + /* Inodes whose final iput was deferred from PF_MEMALLOC context. */ + struct list_head s_deferred_iputs; + struct work_struct s_deferred_iput_work; + bool s_deferred_iput_shutdown; struct rcu_head rcu; struct work_struct destroy_work; -- 2.55.0