引言
系统运维时,经常会使用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
- 每个打开的文件都会有一个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)