
This is a backport of a collection of 12 upstream patches. The main one being the switch to use a rwsem instead. The next important one being the switch of the rwsem to be a per filesystem lock instead of global. See the individual patches for details. They did not require much work or wiggling to get them applied. They all come from Linus' tree and are easily located. As such I have not modified their individual headers with upstream commit ids. Verification: - two scripts, the concept behind them supplied by Vefa Bicakci. The first one causes a lot of concurrent contention in sysfs. The second script highlights how well systemd is also contending. Run Script1 followed by Script2 Without this change, Script2 has timeouts and fails. Script1: for i in `seq 20`; do (while :; do find /sys/fs/cgroup/ -type f -readable -print0 \ 2>/dev/null | xargs -0 -n 20 -r cat >&/dev/null ; done) & done for i in `seq 10`; do (while :; do systemd-run --scope -q sleep 0.5 >/dev/null; done) & done Script2: while true; do date -Is /usr/bin/time -f %e systemctl enable -q lighttpd.service || break /usr/bin/time -f %e systemctl disable -q lighttpd.service || break /usr/bin/time -f %e systemctl restart -q lighttpd.service || break sleep 0.5 || break done - also soak testing to ensure that these patches don't introduce issues Partial-Bug: 2016028 Signed-off-by: Jim Somerville <jim.somerville@windriver.com> Change-Id: I6ad64cd7c90f756c6eb904065febfeb516e73009
101 lines
3.3 KiB
Diff
101 lines
3.3 KiB
Diff
From 55a6f84896a433e74ddddf2203cbb5fec5c05fca Mon Sep 17 00:00:00 2001
|
|
From: Ian Kent <raven@themaw.net>
|
|
Date: Fri, 16 Jul 2021 17:28:18 +0800
|
|
Subject: [PATCH] kernfs: add a revision to identify directory node changes
|
|
|
|
Add a revision counter to kernfs directory nodes so it can be used
|
|
to detect if a directory node has changed during negative dentry
|
|
revalidation.
|
|
|
|
There's an assumption that sizeof(unsigned long) <= sizeof(pointer)
|
|
on all architectures and as far as I know that assumption holds.
|
|
|
|
So adding a revision counter to the struct kernfs_elem_dir variant of
|
|
the kernfs_node type union won't increase the size of the kernfs_node
|
|
struct. This is because struct kernfs_elem_dir is at least
|
|
sizeof(pointer) smaller than the largest union variant. It's tempting
|
|
to make the revision counter a u64 but that would increase the size of
|
|
kernfs_node on archs where sizeof(pointer) is smaller than the revision
|
|
counter.
|
|
|
|
Reviewed-by: Miklos Szeredi <mszeredi@redhat.com>
|
|
Signed-off-by: Ian Kent <raven@themaw.net>
|
|
Link: https://lore.kernel.org/r/162642769895.63632.8356662784964509867.stgit@web.messagingengine.com
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
Signed-off-by: Jim Somerville <jim.somerville@windriver.com>
|
|
---
|
|
fs/kernfs/dir.c | 2 ++
|
|
fs/kernfs/kernfs-internal.h | 19 +++++++++++++++++++
|
|
include/linux/kernfs.h | 5 +++++
|
|
3 files changed, 26 insertions(+)
|
|
|
|
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
|
|
index c16571cbb997..410fbbf797df 100644
|
|
--- a/fs/kernfs/dir.c
|
|
+++ b/fs/kernfs/dir.c
|
|
@@ -380,6 +380,7 @@ static int kernfs_link_sibling(struct kernfs_node *kn)
|
|
/* successfully added, account subdir number */
|
|
if (kernfs_type(kn) == KERNFS_DIR)
|
|
kn->parent->dir.subdirs++;
|
|
+ kernfs_inc_rev(kn->parent);
|
|
|
|
return 0;
|
|
}
|
|
@@ -402,6 +403,7 @@ static bool kernfs_unlink_sibling(struct kernfs_node *kn)
|
|
|
|
if (kernfs_type(kn) == KERNFS_DIR)
|
|
kn->parent->dir.subdirs--;
|
|
+ kernfs_inc_rev(kn->parent);
|
|
|
|
rb_erase(&kn->rb, &kn->parent->dir.children);
|
|
RB_CLEAR_NODE(&kn->rb);
|
|
diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h
|
|
index 7ee97ef59184..6a8d0ca26d03 100644
|
|
--- a/fs/kernfs/kernfs-internal.h
|
|
+++ b/fs/kernfs/kernfs-internal.h
|
|
@@ -81,6 +81,25 @@ static inline struct kernfs_node *kernfs_dentry_node(struct dentry *dentry)
|
|
return d_inode(dentry)->i_private;
|
|
}
|
|
|
|
+static inline void kernfs_set_rev(struct kernfs_node *parent,
|
|
+ struct dentry *dentry)
|
|
+{
|
|
+ dentry->d_time = parent->dir.rev;
|
|
+}
|
|
+
|
|
+static inline void kernfs_inc_rev(struct kernfs_node *parent)
|
|
+{
|
|
+ parent->dir.rev++;
|
|
+}
|
|
+
|
|
+static inline bool kernfs_dir_changed(struct kernfs_node *parent,
|
|
+ struct dentry *dentry)
|
|
+{
|
|
+ if (parent->dir.rev != dentry->d_time)
|
|
+ return true;
|
|
+ return false;
|
|
+}
|
|
+
|
|
extern const struct super_operations kernfs_sops;
|
|
extern struct kmem_cache *kernfs_node_cache, *kernfs_iattrs_cache;
|
|
|
|
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
|
|
index 89f6a4214a70..195afa63ab1c 100644
|
|
--- a/include/linux/kernfs.h
|
|
+++ b/include/linux/kernfs.h
|
|
@@ -98,6 +98,11 @@ struct kernfs_elem_dir {
|
|
* better directly in kernfs_node but is here to save space.
|
|
*/
|
|
struct kernfs_root *root;
|
|
+ /*
|
|
+ * Monotonic revision counter, used to identify if a directory
|
|
+ * node has changed during negative dentry revalidation.
|
|
+ */
|
|
+ unsigned long rev;
|
|
};
|
|
|
|
struct kernfs_elem_symlink {
|
|
--
|
|
2.25.1
|
|
|