Re: [PATCH v3] kernfs: Use RCU for kernfs_node::name and ::parent lookup.

From: Tejun Heo
Date: Tue Dec 03 2024 - 17:27:56 EST


Hello, sorry about the delay.

Generally looks good to me but I have some rcu deref accessor related
comments.

On Thu, Nov 21, 2024 at 06:52:50PM +0100, Sebastian Andrzej Siewior wrote:
> diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h
...
> @@ -1312,6 +1314,11 @@ int rdtgroup_pseudo_lock_create(struct rdtgroup *rdtgrp)
> ret = -EINVAL;
> goto out_region;
> }
> + kn_name = kstrdup(rdt_kn_get_name(rdtgrp->kn), GFP_KERNEL);

Shouldn't this be freed somewhere?

> + if (!kn_name) {
> + ret = -ENOMEM;
> + goto out_cstates;
> + }
>
> plr->thread_done = 0;
>
...
> diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
...
> @@ -533,7 +543,8 @@ static void kernfs_free_rcu(struct rcu_head *rcu)
> {
> struct kernfs_node *kn = container_of(rcu, struct kernfs_node, rcu);
>
> - kfree_const(kn->name);
> + /* If the whole node goes away, the name can't be used outside */
> + kfree_const(rcu_dereference_check(kn->name, true));

rcu_access_pointer()?

> @@ -557,16 +568,18 @@ void kernfs_put(struct kernfs_node *kn)
> if (!kn || !atomic_dec_and_test(&kn->count))
> return;
> root = kernfs_root(kn);
> + guard(rcu)();
> repeat:
> /*
> * Moving/renaming is always done while holding reference.
> * kn->parent won't change beneath us.
> */
> - parent = kn->parent;
> + parent = rcu_dereference(kn->parent);

I wonder whether it'd be better to encode the reference count rule (ie. add
the condition kn->count == 0 to deref_check) in the kn->parent deref
accessor. This function doesn't need RCU read lock and holding it makes it
more confusing.

> diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
> index 8502ef68459b9..05f7b30283150 100644
> --- a/fs/kernfs/file.c
> +++ b/fs/kernfs/file.c
> @@ -911,9 +911,11 @@ static void kernfs_notify_workfn(struct work_struct *work)
> /* kick fsnotify */
>
> down_read(&root->kernfs_supers_rwsem);
> + down_read(&root->kernfs_rwsem);

Why is this addition necessary? Hmm... was the code previously broken w.r.t.
renaming? Can this be RCU?

> diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c
> index 1358c21837f1a..db71faba3bb53 100644
> --- a/fs/kernfs/mount.c
> +++ b/fs/kernfs/mount.c
> @@ -145,8 +145,10 @@ static struct dentry *kernfs_fh_to_parent(struct super_block *sb,
> static struct dentry *kernfs_get_parent_dentry(struct dentry *child)
> {
> struct kernfs_node *kn = kernfs_dentry_node(child);
> + struct kernfs_root *root = kernfs_root(kn);
>
> - return d_obtain_alias(kernfs_get_inode(child->d_sb, kn->parent));
> + guard(rwsem_read)(&root->kernfs_rwsem);
> + return d_obtain_alias(kernfs_get_inode(child->d_sb, kernfs_rcu_get_parent(kn)));

Ditto.

> @@ -186,10 +188,10 @@ static struct kernfs_node *find_next_ancestor(struct kernfs_node *child,
> return NULL;
> }
>
> - while (child->parent != parent) {
> - if (!child->parent)
> + while (kernfs_rcu_get_parent(child) != parent) {
> + child = kernfs_rcu_get_parent(child);
> + if (!child)

I think kernfs_rcu_get_parent() name is a bit confusing given that it allows
derefing without RCU if the rwsem is locked. Maybe just kernfs_get_parent()
or kernfs_parent()?

> @@ -216,6 +219,9 @@ struct dentry *kernfs_node_dentry(struct kernfs_node *kn,
> if (!kn->parent)
> return dentry;
>
> + root = kernfs_root(kn);
> + guard(rwsem_read)(&root->kernfs_rwsem);

Here too, it's a bit confusing that it's adding new locking. Was the code
broken before? If so, it'd be clearer if the fixes were in their own patch.

> diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
> index d1995e2d6c943..e9bfe3e80809d 100644
> --- a/fs/sysfs/file.c
> +++ b/fs/sysfs/file.c
> @@ -19,13 +19,19 @@
>
> #include "sysfs.h"
>
> +static struct kobject *sysfs_file_kobj(struct kernfs_node *kn)
> +{
> + guard(rcu)();
> + return rcu_dereference(kn->parent)->priv;
> +}

I wonder whether it'd be better to rename kn->parent to something like
kn->__parent (or maybe some other suffix) to clarify that the field is not
to be deref'ed directly and kernfs_parent() helper is made available to the
users. That way, users can benefit from the additional conditions in
rcu_dereference_check().

> diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c
> index e28d5f0d20ed0..202e329759b12 100644
> --- a/kernel/cgroup/cgroup-v1.c
> +++ b/kernel/cgroup/cgroup-v1.c
> @@ -844,7 +844,7 @@ static int cgroup1_rename(struct kernfs_node *kn, struct kernfs_node *new_parent
>
> if (kernfs_type(kn) != KERNFS_DIR)
> return -ENOTDIR;
> - if (kn->parent != new_parent)
> + if (rcu_dereference_check(kn->parent, true) != new_parent)
> return -EIO;

This isn't being derefed, rcu_access_pointer()?

> /*
> diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
> index 044c7ba1cc482..d11d05a53783c 100644
> --- a/kernel/cgroup/cgroup.c
> +++ b/kernel/cgroup/cgroup.c
> @@ -633,9 +633,15 @@ int cgroup_task_count(const struct cgroup *cgrp)
> return count;
> }
>
> +static struct cgroup *cg_get_parent_priv(struct kernfs_node *kn)
> +{
> + /* The parent can not be changed */
> + return rcu_dereference_check(kn->parent, true)->priv;
> +}

e.g. Here, it'd be a lot better if kernfs provided helper can be used so
that deref condition check can be preserved.

Thanks.

--
tejun