[PATCH v2 6/6] kernfs: make attr_mutex a local kernfs node lock

From: Ian Kent
Date: Wed Jun 17 2020 - 03:38:27 EST


The global mutex attr_mutex is used to protect the update of inode
attributes in kernfs_refresh_inode() (as well as kernfs node attribute
structure creation) and this function is called by the inode operation
.permission().

Since .permission() is called quite frequently during path walks it
can lead to contention when the number of concurrent path walks is
high.

This mutex is used for kernfs node objects only so make it local to
the kernfs node to reduce the impact of this type of contention.

Signed-off-by: Ian Kent <raven@xxxxxxxxxx>
---
fs/kernfs/dir.c | 1 +
fs/kernfs/inode.c | 12 ++++++------
include/linux/kernfs.h | 2 ++
3 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 03f4f179bbc4..3233e01651e4 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -597,6 +597,7 @@ static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,
kn = kmem_cache_zalloc(kernfs_node_cache, GFP_KERNEL);
if (!kn)
goto err_out1;
+ mutex_init(&kn->attr_mutex);

idr_preload(GFP_KERNEL);
spin_lock(&kernfs_idr_lock);
diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c
index 5c3fac356ce0..5eb11094bb2e 100644
--- a/fs/kernfs/inode.c
+++ b/fs/kernfs/inode.c
@@ -36,7 +36,7 @@ static struct kernfs_iattrs *__kernfs_iattrs(struct kernfs_node *kn, int alloc)
{
struct kernfs_iattrs *iattr = NULL;

- mutex_lock(&attr_mutex);
+ mutex_lock(&kn->attr_mutex);
if (kn->iattr || !alloc) {
iattr = kn->iattr;
goto out_unlock;
@@ -59,7 +59,7 @@ static struct kernfs_iattrs *__kernfs_iattrs(struct kernfs_node *kn, int alloc)
atomic_set(&iattr->user_xattr_size, 0);
kn->iattr = iattr;
out_unlock:
- mutex_unlock(&attr_mutex);
+ mutex_unlock(&kn->attr_mutex);
return iattr;
}

@@ -192,9 +192,9 @@ int kernfs_iop_getattr(const struct path *path, struct kstat *stat,
struct kernfs_node *kn = inode->i_private;

down_read(&kernfs_rwsem);
- mutex_lock(&attr_mutex);
+ mutex_lock(&kn->attr_mutex);
kernfs_refresh_inode(kn, inode);
- mutex_unlock(&attr_mutex);
+ mutex_unlock(&kn->attr_mutex);
up_read(&kernfs_rwsem);

generic_fillattr(inode, stat);
@@ -286,9 +286,9 @@ int kernfs_iop_permission(struct inode *inode, int mask)
kn = inode->i_private;

down_read(&kernfs_rwsem);
- mutex_lock(&attr_mutex);
+ mutex_lock(&kn->attr_mutex);
kernfs_refresh_inode(kn, inode);
- mutex_unlock(&attr_mutex);
+ mutex_unlock(&kn->attr_mutex);
up_read(&kernfs_rwsem);

return generic_permission(inode, mask);
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index 74727d98e380..8669f65d5a39 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -142,6 +142,8 @@ struct kernfs_node {

struct rb_node rb;

+ struct mutex attr_mutex; /* protect attr updates */
+
const void *ns; /* namespace tag */
unsigned int hash; /* ns + name hash */
union {