On Sun, 2014-11-23 at 14:51 +0400, Vladimir Shebordaev wrote: > Hi, > > I would like to suggest an interface to list inodes that currently > occupy page cache in human readable form. > > A piece of code below creates a dedicated proc entry, namely, > /proc/kpagecache. Upon read request it traverses all the inodes of > each superblock and shows their page cache usage summary. It is done > in a stateful way, so it needs to access super_blocks list and has to > get and put superblocks on its own. >
The same thing with tabs in their places. If anybody cares. > I am not quite sure who will give a fuck. Actually, it was a task for > my recent job interview. I still don't know what they exactly meant. I > just think it would be anyway nice to have such an interface. > > In the hope it helps. > > -- > Regards, > Vladimir > commit 7f1a8e195c7a36dd10d22ce48bf4832d7cfcb26e Author: Vladimir Shebordaev <vshebord...@mail.ru> Date: Sun Nov 23 21:19:31 2014 +0300 added /proc/kpagecache interface to show file page cache usage diff --git a/fs/proc/Makefile b/fs/proc/Makefile index 7151ea4..83193c0 100644 --- a/fs/proc/Makefile +++ b/fs/proc/Makefile @@ -29,4 +29,4 @@ proc-$(CONFIG_NET) += proc_net.o proc-$(CONFIG_PROC_KCORE) += kcore.o proc-$(CONFIG_PROC_VMCORE) += vmcore.o proc-$(CONFIG_PRINTK) += kmsg.o -proc-$(CONFIG_PROC_PAGE_MONITOR) += page.o +proc-$(CONFIG_PROC_PAGE_MONITOR) += page.o pagecache.o diff --git a/fs/internal.h b/fs/internal.h index 757ba2a..330ea78 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -88,6 +88,9 @@ extern struct dentry *mount_fs(struct file_system_type *, int, const char *, void *); extern struct super_block *user_get_super(dev_t); +extern void __put_super(struct super_block *sb); +extern void put_super(struct super_block *sb); + /* * open.c */ diff --git a/fs/super.c b/fs/super.c index eae088f..24ed119 100644 --- a/fs/super.c +++ b/fs/super.c @@ -242,7 +242,7 @@ fail: /* * Drop a superblock's refcount. The caller must hold sb_lock. */ -static void __put_super(struct super_block *sb) +void __put_super(struct super_block *sb) { if (!--sb->s_count) { list_del_init(&sb->s_list); @@ -257,7 +257,7 @@ static void __put_super(struct super_block *sb) * Drops a temporary reference, frees superblock if there's no * references left. */ -static void put_super(struct super_block *sb) +void put_super(struct super_block *sb) { spin_lock(&sb_lock); __put_super(sb); diff --git a/fs/proc/pagecache.c b/fs/proc/pagecache.c new file mode 100644 index 0000000..d940f35 --- /dev/null +++ b/fs/proc/pagecache.c @@ -0,0 +1,412 @@ +/* + * fs/proc/pagecache.c + * + * Copyright (C) 2014 + * + * Author: Vladimir Shebordaev <vshebord...@mail.ru> + * + * /proc/kpagecache interface to show file page cache usage + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> + +#include <linux/nsproxy.h> +#include <linux/backing-dev.h> +#include <linux/page-flags.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/path.h> +#include <linux/fs_struct.h> +#include <linux/mount.h> +#include <linux/ctype.h> +#include <linux/unistd.h> + +#include <linux/seq_file.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> + +#include "../internal.h" + +#define NR_PAGES (PAGE_ALIGN(PATH_MAX) >> PAGE_SHIFT) +#define BUFSIZE (NR_PAGES << PAGE_SHIFT) + +struct iter { + struct inode *inode; + char *buf; +}; + +struct iter *iter_next(struct iter *iter) +{ + struct super_block *sb, *p; + struct inode *inode, *prev; + + inode = iter->inode; + prev = inode; + sb = inode->i_sb; + + spin_lock(&inode_sb_list_lock); +next: + inode = list_next_entry(inode, i_sb_list); +check: + if (&inode->i_sb_list == &sb->s_inodes) + inode = NULL; + if (inode) { + spin_lock(&inode->i_lock); + if ((inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) || + !(mapping_cap_writeback_dirty(inode->i_mapping)) || + (inode->i_mapping->nrpages == 0) || + hlist_empty(&inode->i_dentry)) { + spin_unlock(&inode->i_lock); + goto next; + } + __iget(inode); + spin_unlock(&inode->i_lock); + } + spin_unlock(&inode_sb_list_lock); + + iput(prev); + prev = NULL; + + if (inode) + goto out; + + up_read(&sb->s_umount); + p = sb; + spin_lock(&sb_lock); +retry: + sb = list_next_entry(sb, s_list); + if (&sb->s_list == &super_blocks) + sb = NULL; + if (sb) { + if (hlist_unhashed(&sb->s_instances)) + goto retry; + sb->s_count++; + } + if (p) { + __put_super(p); + p = NULL; + } + spin_unlock(&sb_lock); + + if (sb) { + down_read(&sb->s_umount); + if (!sb->s_root || !(sb->s_flags & MS_BORN) || !sb->s_bdi || + !bdi_cap_writeback_dirty(sb->s_bdi)) { + up_read(&sb->s_umount); + p = sb; + spin_lock(&sb_lock); + goto retry; + } + spin_lock(&inode_sb_list_lock); + if (list_empty(&sb->s_inodes)) { + spin_unlock(&inode_sb_list_lock); + up_read(&sb->s_umount); + p = sb; + spin_lock(&sb_lock); + goto retry; + } + inode = list_first_entry(&sb->s_inodes, struct inode, i_sb_list); + goto check; + } +out: + iter->inode = inode; + return inode ? iter : NULL; +} + +struct iter *iter_first(struct iter *iter) +{ + struct super_block *sb, *p; + struct inode *inode; + + inode = NULL; + p = NULL; + + spin_lock(&sb_lock); + sb = list_first_entry(&super_blocks, struct super_block, s_list); +check: + if (&sb->s_list == &super_blocks) + sb = NULL; + if (sb) { + if (hlist_unhashed(&sb->s_instances)) { +retry: + sb = list_next_entry(sb, s_list); + goto check; + } + sb->s_count++; + } + if (p) { + __put_super(p); + p = NULL; + } + spin_unlock(&sb_lock); + + if (!sb) + goto out; + + down_read(&sb->s_umount); + if (!sb->s_root || !(sb->s_flags & MS_BORN) || !sb->s_bdi || + !bdi_cap_writeback_dirty(sb->s_bdi)) { + up_read(&sb->s_umount); + p = sb; + spin_lock(&sb_lock); + goto retry; + } + + spin_lock(&inode_sb_list_lock); + if (list_empty(&sb->s_inodes)) { + spin_unlock(&inode_sb_list_lock); + up_read(&sb->s_umount); + p = sb; + spin_lock(&sb_lock); + goto retry; + } + + inode = list_first_entry(&sb->s_inodes, struct inode, i_sb_list); +next: + if (&inode->i_sb_list == &sb->s_inodes) { + spin_unlock(&inode_sb_list_lock); + up_read(&sb->s_umount); + inode = NULL; + p = sb; + spin_lock(&sb_lock); + goto retry; + } + + if (inode) { + spin_lock(&inode->i_lock); + if ((inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) || + !(mapping_cap_writeback_dirty(inode->i_mapping)) || + (inode->i_mapping->nrpages == 0) || + hlist_empty(&inode->i_dentry)) { + spin_unlock(&inode->i_lock); + inode = list_next_entry(inode, i_sb_list); + goto next; + } + __iget(inode); + spin_unlock(&inode->i_lock); + } + spin_unlock(&inode_sb_list_lock); +out: + iter->inode = inode; + return inode ? iter : NULL; +} + +static int iter_init(struct iter *iter) +{ + memset(iter, 0, sizeof(*iter)); + iter->buf = (char *)__get_free_pages(GFP_TEMPORARY, order_base_2(NR_PAGES)); + + return iter->buf ? 0 : -ENOMEM; +} + +static void iter_destroy(struct iter *iter) +{ + free_pages((unsigned long)iter->buf, order_base_2(NR_PAGES)); +} + +struct inode_stat { + unsigned long nr_pages; + unsigned long nr_shadow; + unsigned long nr_dirty; + unsigned long nr_active; + unsigned long nr_mlocked; + unsigned long nr_locked; + unsigned long nr_reclaim; +}; + +static int get_inode_stat(struct inode *inode, struct inode_stat *stat) +{ + int ret; + void **slot; + struct radix_tree_iter iter; + + ret = 0; + memset(stat, 0, sizeof(*stat)); + + rcu_read_lock(); +retry: + radix_tree_for_each_slot(slot, &inode->i_mapping->page_tree, &iter, 0) { + struct page *page; + + page = radix_tree_deref_slot(slot); + if (unlikely(!page)) + continue; + if (radix_tree_exception(page)) { + if (radix_tree_deref_retry(page)) + goto retry; + /* we are to avoid swap backed mappings */ + BUG(); + } + if (PageDirty(page)) + ++stat->nr_dirty; + if (PageLocked(page)) + ++stat->nr_locked; + if (PageActive(page)) + ++stat->nr_active; + if (PageMlocked(page)) + ++stat->nr_mlocked; + if (PageReclaim(page)) + ++stat->nr_reclaim; + ++ret; + } + rcu_read_unlock(); + + stat->nr_pages = ret; + + return ret; +} + +static int seq_show(struct seq_file *m, void *priv) +{ + int ret; + struct iter *iter; + struct inode *inode; + struct inode_stat stat; + struct path path; + + if (unlikely(priv == SEQ_START_TOKEN)) { + seq_printf(m, " pages " + "\t device/path\n" + " lo ml di ac re total\n"); + return 0; + } + + iter = priv; + inode = iter->inode; + + ret = get_inode_stat(inode, &stat); + if (ret < 0) + goto out; + + get_fs_root(current->fs, &path); + dput(path.dentry); + + /* only the name of the last instantiated link is displayed */ + path.dentry = hlist_entry(inode->i_dentry.first, struct dentry, d_alias); + + seq_printf(m, "% 6ld % 6ld % 6ld % 6ld % 6ld % 6ld\t(%u:%u)%s\n", + stat.nr_locked, stat.nr_mlocked, stat.nr_dirty, + stat.nr_active, stat.nr_reclaim, stat.nr_pages, + MAJOR(inode->i_sb->s_dev), + MINOR(inode->i_sb->s_dev), + d_path(&path, iter->buf, BUFSIZE)); + + mntput(path.mnt); +out: + return 0; +} + +static void *seq_next(struct seq_file *m, void *priv, loff_t *pos) +{ + ++(*pos); + return (priv == SEQ_START_TOKEN) ? + iter_first(m->private) : + iter_next(priv); +} + +static void *seq_start(struct seq_file *m, loff_t *pos) +{ + struct iter *iter; + loff_t off; + + if (!*pos) + return SEQ_START_TOKEN; + + iter = iter_first(m->private); + + for (off = 1; iter && off < *pos; ++off) + iter = iter_next(iter); + + return iter; +} + +static void seq_stop(struct seq_file *m, void *priv) +{ + struct iter *iter; + struct inode *inode; + struct super_block *sb; + + if (priv == SEQ_START_TOKEN) + return; + + iter = priv; + if (!iter) + return; + + inode = iter->inode; + if (inode) { + sb = inode->i_sb; + iput(inode); + up_read(&sb->s_umount); + put_super(sb); + } +} + +static const struct seq_operations seq_ops = { + .start = seq_start, + .next = seq_next, + .stop = seq_stop, + .show = seq_show +}; + +static int page_cache_open(struct inode *inode, struct file *file) +{ + int ret; + struct iter *iter; + + ret = -ENOMEM; + iter = __seq_open_private(file, &seq_ops, sizeof(*iter)); + if (!iter) + goto out; + + ret = iter_init(iter); +out: + return ret; +} + +static int page_cache_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq; + + seq = file->private_data; + iter_destroy(seq->private); + kfree(seq->private); + return seq_release(inode, file); +} + +static const struct file_operations page_cache_fops = { + .open = page_cache_open, + .read = seq_read, + .llseek = seq_lseek, + .release = page_cache_release +}; + +#ifndef PROCENTRY +#define PROCENTRY "kpagecache" +#endif + +static int __init page_cache_init(void) +{ + int ret; + + ret = -ENOENT; + + if (!proc_create(PROCENTRY, S_IFREG|0400, NULL, &page_cache_fops)) + goto out; + + ret = 0; +out: + return ret; +} +module_init(page_cache_init); + +static void __exit page_cache_exit(void) +{ + remove_proc_entry(PROCENTRY, NULL); +} +module_exit(page_cache_exit); + +MODULE_LICENSE("GPL"); -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/