mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
super: take lock after last reference count
__put_super() required the caller to hold sb_lock, so put_super() wrapped it. The per-device superblock table introduced later drops its passive references from contexts that do not hold sb_lock, so make put_super() self-locking: drop the count first and take sb_lock only for the final list_del. With the count now dropped outside sb_lock a superblock can briefly sit on @super_blocks with s_passive == 0 before it is unlinked, so the list walkers (__iterate_supers(), iterate_supers_type(), user_get_super()) switch to refcount_inc_not_zero() and skip it. Link: https://patch.msgid.link/20260616-work-super-bdev_holder_global-v2-3-7df6b864028e@kernel.org Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
parent
3ec9800c2d
commit
9c486f2899
63
fs/super.c
63
fs/super.c
|
|
@ -403,12 +403,17 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
|
|||
/* Superblock refcounting */
|
||||
|
||||
/*
|
||||
* Drop a superblock's refcount. The caller must hold sb_lock.
|
||||
* Drop a superblock's passive reference. Must be called WITHOUT sb_lock held;
|
||||
* put_super() acquires sb_lock itself when the final reference is dropped.
|
||||
*/
|
||||
static void __put_super(struct super_block *s)
|
||||
void put_super(struct super_block *s)
|
||||
{
|
||||
if (refcount_dec_and_test(&s->s_passive)) {
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
list_del_init(&s->s_list);
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
WARN_ON(s->s_dentry_lru.node);
|
||||
WARN_ON(s->s_inode_lru.node);
|
||||
WARN_ON(s->s_mounts);
|
||||
|
|
@ -416,20 +421,6 @@ static void __put_super(struct super_block *s)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* put_super - drop a temporary reference to superblock
|
||||
* @sb: superblock in question
|
||||
*
|
||||
* Drops a temporary reference, frees superblock if there's no
|
||||
* references left.
|
||||
*/
|
||||
void put_super(struct super_block *sb)
|
||||
{
|
||||
spin_lock(&sb_lock);
|
||||
__put_super(sb);
|
||||
spin_unlock(&sb_lock);
|
||||
}
|
||||
|
||||
static void kill_super_notify(struct super_block *sb)
|
||||
{
|
||||
lockdep_assert_not_held(&sb->s_umount);
|
||||
|
|
@ -478,11 +469,7 @@ void deactivate_locked_super(struct super_block *s)
|
|||
|
||||
kill_super_notify(s);
|
||||
|
||||
/*
|
||||
* Since list_lru_destroy() may sleep, we cannot call it from
|
||||
* put_super(), where we hold the sb_lock. Therefore we destroy
|
||||
* the lru lists right now.
|
||||
*/
|
||||
/* list_lru_destroy() may sleep; put_super() callers may not. */
|
||||
list_lru_destroy(&s->s_dentry_lru);
|
||||
list_lru_destroy(&s->s_inode_lru);
|
||||
|
||||
|
|
@ -851,14 +838,17 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg,
|
|||
struct super_block *sb, *p = NULL;
|
||||
bool excl = flags & SUPER_ITER_EXCL;
|
||||
|
||||
guard(spinlock)(&sb_lock);
|
||||
spin_lock(&sb_lock);
|
||||
|
||||
for (sb = first_super(flags);
|
||||
!list_entry_is_head(sb, &super_blocks, s_list);
|
||||
sb = next_super(sb, flags)) {
|
||||
if (super_flags(sb, SB_DYING))
|
||||
continue;
|
||||
refcount_inc(&sb->s_passive);
|
||||
|
||||
if (!refcount_inc_not_zero(&sb->s_passive))
|
||||
continue;
|
||||
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
if (flags & SUPER_ITER_UNLOCKED) {
|
||||
|
|
@ -868,13 +858,14 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg,
|
|||
super_unlock(sb, excl);
|
||||
}
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
if (p)
|
||||
__put_super(p);
|
||||
put_super(p);
|
||||
p = sb;
|
||||
spin_lock(&sb_lock);
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
if (p)
|
||||
__put_super(p);
|
||||
put_super(p);
|
||||
}
|
||||
|
||||
void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
|
||||
|
|
@ -903,7 +894,9 @@ void iterate_supers_type(struct file_system_type *type,
|
|||
if (super_flags(sb, SB_DYING))
|
||||
continue;
|
||||
|
||||
refcount_inc(&sb->s_passive);
|
||||
if (!refcount_inc_not_zero(&sb->s_passive))
|
||||
continue;
|
||||
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
locked = super_lock_shared(sb);
|
||||
|
|
@ -912,14 +905,14 @@ void iterate_supers_type(struct file_system_type *type,
|
|||
super_unlock_shared(sb);
|
||||
}
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
if (p)
|
||||
__put_super(p);
|
||||
put_super(p);
|
||||
p = sb;
|
||||
spin_lock(&sb_lock);
|
||||
}
|
||||
if (p)
|
||||
__put_super(p);
|
||||
spin_unlock(&sb_lock);
|
||||
if (p)
|
||||
put_super(p);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(iterate_supers_type);
|
||||
|
|
@ -935,15 +928,17 @@ struct super_block *user_get_super(dev_t dev, bool excl)
|
|||
if (sb->s_dev != dev)
|
||||
continue;
|
||||
|
||||
refcount_inc(&sb->s_passive);
|
||||
if (!refcount_inc_not_zero(&sb->s_passive))
|
||||
continue;
|
||||
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
locked = super_lock(sb, excl);
|
||||
if (locked)
|
||||
return sb;
|
||||
|
||||
put_super(sb);
|
||||
spin_lock(&sb_lock);
|
||||
__put_super(sb);
|
||||
break;
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
|
|
@ -1368,9 +1363,7 @@ static struct super_block *bdev_super_lock(struct block_device *bdev, bool excl)
|
|||
lockdep_assert_not_held(&bdev->bd_disk->open_mutex);
|
||||
|
||||
/* Make sure sb doesn't go away from under us */
|
||||
spin_lock(&sb_lock);
|
||||
refcount_inc(&sb->s_passive);
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user