量化分析pagecache,揭开文件缓存的神秘面纱

引言

系统运维时,经常会使用top或者free查看服务器的内存使用情况,一般情况下,显示结果中的buffer/cache都比较大,查阅各种资料也无法得到比较明确的答案。阅读本文可以获得以下知识 :
1、 明确free命令中的buff/cache的确认含义。
2、 /proc/meminfo和/proc/vmstat中某些字段的含义
3、 inode与pagecache的关系
4、 通过编写内核代码模块来查看pagecache中具体信息

cache起源

存储器的层次结构,从左到右,访问延迟越来越大,容量越来越大。
register -> L1cache -> L2cache -> L3cache -> DRAM -> SDD -> HDD
将HDD中的文件内容缓存到DRAM中,程序访问的速度就会变快,响应就会很快。
层层缓存,也是为了尽量屏蔽不同介质的性能差异。

inode与pagecache

1633058647(1).png
  • 每个打开的文件都会有一个inode,串在该块设备的super_block中。
  • inode中有个变量i_mapping radix_tree_root i_pages,所有该文件在内存中的缓存页(page)会挂在这颗radix数上。图中的小圆圈就表示一个page,图中的编号表示该page在文件中的索引。假设一个文件长度为4096 * 4 = 16348,pagesize是4k(4096),那么该文件总共会有4个缓存页,假如要访问文件位置为10000位置的数据,10000 % 4096 = 2,那么该数据是在编号为2的小圆圈内。
  • 如果一个inode的模式是文件,那么该inode的块的内容就是文件的内容,如果一个inode的模式是目录,那么该inode的块的内容是该目录下的文件的inode号和文件名,即inode中不存储文件名信息。

proc文件系统下内存展示

[root@localhost test]# cat /proc/vmstat 
nr_inactive_file 17603    // 非活跃文件页缓存的页数
nr_active_file 20378    // 活跃文件页缓存的页数
nr_slab_reclaimable 6429
nr_slab_unreclaimable 9888
nr_file_pages 44346   
....

[root@localhost test]# cat /proc/meminfo 
Cached:           153840 kB
SwapCached:            0 kB
Active:           135668 kB
Inactive:          93276 kB
Active(anon):      76576 kB
Inactive(anon):    23988 kB
Active(file):      59092 kB    // 在活跃链表上
Inactive(file):    69288 kB   // 在非活跃链表上
Shmem:             25460 kB
Slab:              61476 kB    // 下面的slabinfo可以清晰展示保存的数据
SReclaimable:      24272 kB   //可以被回收的slab
SUnreclaim:        37204 kB

[root@localhost test]# cat /proc/slabinfo 
slabinfo - version: 2.1
ovl_inode            288    288    672   24  // overlay文件系统使用的inode
TCP                   60     60   2176   15
kmalloc-128         2197   2400    128   32   // 连续的128字节的内存
inode_cache        11524  12069    592   27     // 通用inode结构
dentry             19299  30387    192   21   // 目录项缓存
task_struct          312    330   5568    5   // task缓存

[root@localhost run]# du -sh /run/
25M     /run/                //这里近似等于/proc/meminfo中的Shmem

buff/cache (未使能swap分区的情况下)

  • 系统中的pagecache = Buffers + Cached = inactive files + active files + shmem(数据来自/proc/meminfo)。
  • free中的buff/cache = Buffers + Cached + SReclaimable
    为什么要加上SReclaimable呢?因为这个也是可以被回收的。
  • /proc/meminfo中的Cached = inactive files + active files + Shmem
    为什么要加上Shmem呢?因为shmem也是内核管理的一种文件系统。

不同层次的cache,确实让人有点迷惑。下面做个总结。
pagecache是文件缓存,包含磁盘文件系统和内存文件系统的文件缓存。shmem(包括进程通信的共享内存)也是基于文件。
free或top中的buff/cache关注的是可以被回收的内存。
如何理解可以被回收的内存呢?当有进程申请内存时(缺页异常处理过程中),发现系统内存不足时,可以被释放的内存(直接回收和kswapd回收),如Cached,脏页可以被刷回磁盘,等到需要的时候再从磁盘读取就可以了,慢点总好过于不能用。如Sreclaimable,slab中的inode和dentry,也是可以回收,当需要的时候,也可以再从磁盘读取。

编写内核模块展示cache中的每个文件的缓存页数

只包括了磁盘文件系统中的文件,暂未考虑由内核直接管理的文件系统,如pipefs,sockfs,tmpfs,procfs,cgroupfs,bdev等等,留作以后补充(分析到这里也够用了)
我是内核的初学者,所以这里的代码基本都是从内核代码中复制过来,重新组装的。

// pagecache.c

#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <trace/events/kmem.h>

// 只统计大于这个阈值页数的文件
static int pagethreshnum = 0;
module_param(pagethreshnum, int, 0);
// 根文件系统对应的盘符, df -h  | grep "/$"
static char *devname = "/dev/dm-0";
module_param(devname, charp, S_IRUSR);
static long unsigned int total_pages = 0;

static int __init pagecache_cal_init(void)
{
        struct super_block *sb= NULL;
        struct block_device *b_dev = NULL;
        struct inode *inode, *next = NULL;
        struct dentry *d_entry;
        // 申请存放文件路径的buf
        char * buf = kmalloc(PATH_MAX, GFP_NOFS);
        if (unlikely(!buf))
        {
                pr_info("request memory error.\n");
                return -ENOMEM;
        }

        // 通过盘符找到block_device结构,进而找到super_block
        b_dev = lookup_bdev(devname);
        if (IS_ERR(b_dev))
        {
                pr_info("%s devname is error\n", devname);
                kfree(buf);
                return b_dev;
        }

        sb = b_dev->bd_super;
        if(b_dev && sb)
        {
                pr_info("disk name : %s\n",b_dev->bd_disk->disk_name);
                pr_info("disk_key : %d\n",sb->s_dev);
                pr_info("dentry name : %s\n",sb->s_root->d_name.name);
        }

        spin_lock(&sb->s_inode_list_lock);
        // 遍历在内存中的inode
        list_for_each_entry_safe(inode, next, &sb->s_inodes, i_sb_list) {
                int nrpages = inode->i_mapping->nrpages ;
                if (nrpages > pagethreshnum) {
                        // 通过inode获取到dentry
                        d_entry = d_find_any_alias(inode);
                        if (d_entry)
                        {
                                memset(buf, 0, PATH_MAX);
                                // 获取文件路径的绝对路径
                                char * abs_path = dentry_path_raw(d_entry, buf, PATH_MAX);
                                pr_info("filepath %s has %d pages cached in memory. \n", abs_path, nrpages);
                        }
                        else
                        {
                                pr_info("This inode has no dentry, inode_num : %d  %d pages in memory. \n", inode->i_ino, nrpages);
                        }
                        // 统计所有的缓存页
                        total_pages += nrpages;
                }

        }
        kfree(buf);
        spin_unlock(&sb->s_inode_list_lock);

        return 0;
}

static void __exit pagecache_cal_exit(void)
{
        pr_info("total file pages : %lu\n", total_pages);
}

module_init(pagecache_cal_init);
module_exit(pagecache_cal_exit);

MODULE_LICENSE("GPL");

效果展示

编译内核模块,并安装,等一会儿,再卸载模块,通过dmesg或者系统日志查看

[root@localhost test]# insmod pagecache.ko devname="/dev/dm-0" pagethreshnum=20
[root@localhost test]# rmmod pagecache.ko 
[root@localhost test]# dmesg 
[24510.295604] abc
[24510.295610] disk name : dm-0
[24510.295611] disk_key : 265289728
[24510.295612] dentry name : /
[24510.295614] filepath /root/test/pagecache.ko has 60 pages cached in memory. 
[24510.295615] filepath /root/test/pagecache.o has 42 pages cached in memory. 
[24510.295847] filepath /usr/bin/grotty has 25 pages cached in memory. 
[24510.295848] filepath /usr/bin/troff has 129 pages cached in memory. 
[24510.295849] filepath /usr/bin/groff has 21 pages cached in memory. 
[24510.295851] filepath /usr/bin/less has 39 pages cached in memory.
[24510.295960] filepath /var/lib/docker/overlay2/d3d6a33b2245f8579a49a0d95b97d177451830798e30d262d6006b3e52c43643/diff/usr/lib/libpcre.so.1.2.12 has 28 pages cached in memory. 
[24510.295961] filepath /var/lib/docker/overlay2/d3d6a33b2245f8579a49a0d95b97d177451830798e30d262d6006b3e52c43643/diff/usr/lib/libmbedcrypto.so.2.16.6 has 66 pages cached in memory. 
[24510.295963] filepath /var/lib/docker/overlay2/d3d6a33b2245f8579a49a0d95b97d177451830798e30d262d6006b3e52c43643/diff/usr/bin/ss-server has 48 pages cached in memory. 
[24510.296006] filepath /var/lib/docker/overlay2/8023c7d83aa6d12f34a71ef46f8e9200af360d5332f9bb859a8aa8012c58d94d/diff/lib/ld-musl-x86_64.so.1 has 121 pages cached in memory. 
......
[24510.296252] filepath /usr/lib64/libpthread-2.17.so has 35 pages cached in memory. 
[24510.296253] filepath /usr/lib64/libgcc_s-4.8.5-20150702.so.1 has 22 pages cached in memory. 
[24510.296254] filepath /usr/lib64/libmount.so.1.1.0 has 68 pages cached in memory. 
[24510.296255] filepath /usr/lib64/libkmod.so.2.2.10 has 21 pages cached in memory. 
[24510.296256] filepath /usr/lib64/libaudit.so.1.0.0 has 32 pages cached in memory. 
[24510.296256] filepath /usr/lib64/libselinux.so.1 has 39 pages cached in memory. 
[24510.296258] filepath /usr/lib64/ld-2.17.so has 40 pages cached in memory. 
[24510.296258] filepath /usr/lib/systemd/systemd has 325 pages cached in memory. 
[24519.747193] total file pages : 36019 (page size 4k)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • Page Cache是通过将磁盘中的数据缓存到内存中,减少磁盘I/O操作,从而提高性能。此外,还要确保Page C...
    Balram阅读 3,976评论 0 3
  • 前言 之前在实习时,听了 OOM 的分享之后,就对 Linux 内核内存管理充满兴趣,但是这块知识非常庞大,没有一...
    程序员BUG阅读 952评论 0 3
  • 缓存机制介绍 Linux 系统中,为了提高文件系统的读写性能1,内核会利用一部分内存(相当大的剩余内存)分配缓存区...
    taj3991阅读 1,136评论 0 0
  • Basic CPU / Mem / Disk Info 1. CPU Cores 物理 CPU 的核数 ca...
    zpei0411阅读 12,087评论 0 6
  • 问题现场 查看系统内存的使用状态 监控报警可用内存空间不足,常规的解决方案如下: 增加内存(增加成本) 增加虚拟内...
    七路灯阅读 9,102评论 0 3