From: Ye Bin In order to better analyze the issue of file system uninstallation caused by kernel module opening files, it is necessary to perform dentry recycling on a single file system. But now, apart from global dentry recycling, it is not supported to do dentry recycling on a single file system separately. This feature has usage scenarios in problem localization scenarios.At the same time, it also provides users with a slightly fine-grained pagecache/entry recycling mechanism. This patch supports the recycling of pagecache/entry for individual file systems. Signed-off-by: Ye Bin --- fs/drop_caches.c | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/fs/drop_caches.c b/fs/drop_caches.c index 49f56a598ecb..0cd8ad9df07a 100644 --- a/fs/drop_caches.c +++ b/fs/drop_caches.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "internal.h" /* A global variable is a bit ugly, but it keeps the code simple */ @@ -78,6 +80,124 @@ static int drop_caches_sysctl_handler(const struct ctl_table *table, int write, return 0; } +struct drop_fs_caches_work { + struct callback_head task_work; + dev_t dev; + char *path; + unsigned int ctl; +}; + +static void drop_fs_caches(struct callback_head *twork) +{ + int ret; + struct super_block *sb; + static bool suppress; + struct drop_fs_caches_work *work = container_of(twork, + struct drop_fs_caches_work, task_work); + unsigned int ctl = work->ctl; + dev_t dev = work->dev; + + if (work->path) { + struct path path; + + ret = kern_path(work->path, LOOKUP_FOLLOW, &path); + if (ret) { + pr_err("%s (%d): %s: failed to get path(%s) %d\n", + current->comm, task_pid_nr(current), + __func__, work->path, ret); + goto out; + } + dev = path.dentry->d_sb->s_dev; + /* Make this file's dentry and inode recyclable */ + path_put(&path); + } + + sb = user_get_super(dev, false); + if (!sb) { + pr_err("%s (%d): %s: failed to get dev(%u:%u)'s sb\n", + current->comm, task_pid_nr(current), __func__, + MAJOR(dev), MINOR(dev)); + goto out; + } + + if (ctl & BIT(0)) { + lru_add_drain_all(); + drop_pagecache_sb(sb, NULL); + count_vm_event(DROP_PAGECACHE); + } + + if (ctl & BIT(1)) { + drop_sb_dentry_inode(sb); + count_vm_event(DROP_SLAB); + } + + if (!READ_ONCE(suppress)) { + pr_info("%s (%d): %s: %d %u:%u\n", current->comm, + task_pid_nr(current), __func__, ctl, + MAJOR(sb->s_dev), MINOR(sb->s_dev)); + + if (ctl & BIT(2)) + WRITE_ONCE(suppress, true); + } + + drop_super(sb); +out: + kfree(work->path); + kfree(work); +} + +static int drop_fs_caches_sysctl_handler(const struct ctl_table *table, + int write, void *buffer, + size_t *length, loff_t *ppos) +{ + struct drop_fs_caches_work *work = NULL; + unsigned int major, minor; + unsigned int ctl; + int ret; + char *path = NULL; + + if (!write) + return 0; + + if (sscanf(buffer, "%u %u:%u", &ctl, &major, &minor) != 3) { + path = kstrdup(buffer, GFP_NOFS); + if (!path) { + ret = -ENOMEM; + goto out; + } + + if (sscanf(buffer, "%u %s", &ctl, path) != 2) { + ret = -EINVAL; + goto out; + } + } + + if (ctl < 1 || ctl > 7) { + ret = -EINVAL; + goto out; + } + + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) { + ret = -ENOMEM; + goto out; + } + + init_task_work(&work->task_work, drop_fs_caches); + if (!path) + work->dev = MKDEV(major, minor); + work->path = path; + work->ctl = ctl; + ret = task_work_add(current, &work->task_work, TWA_RESUME); +out: + if (ret) { + kfree(path); + kfree(work); + } + + return ret; +} + static const struct ctl_table drop_caches_table[] = { { .procname = "drop_caches", @@ -88,6 +208,11 @@ static const struct ctl_table drop_caches_table[] = { .extra1 = SYSCTL_ONE, .extra2 = SYSCTL_FOUR, }, + { + .procname = "drop_fs_caches", + .mode = 0200, + .proc_handler = drop_fs_caches_sysctl_handler, + }, }; static int __init init_vm_drop_caches_sysctls(void) -- 2.34.1