free 命令是Linux系统上查看内存使用状况最常用的工具,然而很少有人能说清楚 “buffers” 与 “cached” 之间的区别:
我们先抛出结论,如果你对研究过程感兴趣可以继续阅读后面的段落:
buffers 表示块设备(block device)所占用的缓存页,包括:直接读写块设备、以及文件系统元数据(metadata),比如SuperBlock所使用的缓存页;
cached 表示普通文件数据所占用的缓存页。
下面是分析过程:
先用 strace 跟踪 free 命令,看看它是如何计算 buffers 和 cached 的:
#stracefree ... open("/proc/meminfo",O_RDONLY)=3 lseek(3,0,SEEK_SET)=0 read(3,"MemTotal:3848656kB MemF"...,2047)=1170 ...
显然 free 命令是从 /proc/meminfo 中读取信息的,跟我们直接读到的结果一样:
#cat/proc/meminfo MemTotal:3848656kB MemFree:865640kB Buffers:324432kB Cached:2024904kB ... SwapTotal:2031612kB SwapFree:2031612kB ... Shmem:5312kB ...
那么 /proc/meminfo 中的 Buffers 和 Cached 又是如何得来的呢?这回没法偷懒,只能去看源代码了。源代码文件是:fs/proc/meminfo.c ,我们感兴趣的函数是:meminfo_proc_show(),阅读得知:
Cached 来自于以下公式:
global_page_state(NR_FILE_PAGES)–total_swapcache_pages–i.bufferram
global_page_state(NR_FILE_PAGES) 表示所有的缓存页(page cache)的总和,它包括:
Cached
Buffers 也就是上面公式中的 i.bufferram,来自于 nr_blockdev_pages() 函数的返回值。
交换区缓存(swap cache)
global_page_state(NR_FILE_PAGES) 来自 vmstat[NR_FILE_PAGES],vmstat[NR_FILE_PAGES] 可以通过 /proc/vmstat 来查看,表示所有缓存页的总数量:
#cat/proc/vmstat ... nr_file_pages587334 ...
注意以上nr_file_pages是以page为单位(一个page等于4KB),而free命令是以KB为单位的。
直接修改 nr_file_pages 的内核函数是:__inc_zone_page_state(page, NR_FILE_PAGES) 和__dec_zone_page_state(page, NR_FILE_PAGES),一个用于增加,一个用于减少。
Swap Cache是什么?
用户进程的内存页分为两种:file-backed pages(与文件对应的内存页)和anonymous pages(匿名页)。匿名页(anonymous pages)是没有关联任何文件的,比如用户进程通过malloc()申请的内存页,如果发生swapping换页,它们没有关联的文件进行回写,所以只能写入到交换区里。
交换区可以包括一个或多个交换区设备(裸盘、逻辑卷、文件都可以充当交换区设备),每一个交换区设备在内存里都有对应的swap cache,可以把swap cache理解为交换区设备的page cache:page cache对应的是一个个文件,swap cache对应的是一个个交换区设备,kernel管理swap cache与管理page cache一样,用的都是radix-tree,唯一的区别是:page cache与文件的对应关系在打开文件时就确定了,而一个匿名页只有在即将被swap-out的时候才决定它会被放到哪一个交换区设备,即匿名页与swap cache的对应关系在即将被swap-out时才确立。
并不是每一个匿名页都在swap cache中,只有以下情形之一的匿名页才在:
匿名页即将被swap-out时会先被放进swap cache,但通常只存在很短暂的时间,因为紧接着在pageout完成之后它就会从swap cache中删除,毕竟swap-out的目的就是为了腾出空闲内存;【注:参见mm/vmscan.c: shrink_page_list(),它调用的add_to_swap()会把swap cache页面标记成dirty,然后它调用try_to_unmap()将页面对应的page table mapping都删除,再调用pageout()回写dirty page,最后try_to_free_swap()会把该页从swap cache中删除。】
曾经被swap-out现在又被swap-in的匿名页会在swap cache中,直到页面中的内容发生变化、或者原来用过的交换区空间被回收为止。【注:当匿名页的内容发生变化时会删除对应的swap cache,代码参见mm/swapfile.c: reuse_swap_page()。】
cached:
Cached 表示除去 buffers 和 swap cache 之外,剩下的也就是普通文件的缓存页的数量:
global_page_state(NR_FILE_PAGES)–total_swapcache_pages–i.bufferram
所以关键还是要理解 buffers 是什么含义。
buffers:
从源代码中看到,buffers 来自于 nr_blockdev_pages() 函数的返回值:
longnr_blockdev_pages(void) { structblock_device*bdev; longret=0; spin_lock(&bdev_lock); list_for_each_entry(bdev,&all_bdevs,bd_list){ ret+=bdev->bd_inode->i_mapping->nrpages; } spin_unlock(&bdev_lock); returnret; }
这段代码的意思是遍历所有的块设备(block device),累加每个块设备的inode的i_mapping的页数,统计得到的就是 buffers。显然 buffers 是与块设备直接相关的。
那么谁会更新块设备的缓存页数量(nrpages)呢?我们继续向下看。
搜索kernel源代码发现,最终点我更新mapping->nrpages字段的函数就是:
pagemap.h:add_to_page_cache >filemap.c:add_to_page_cache_locked
C__add_to_page_cache_locked >page_cache_tree_insert 和: filemap.c:delete_from_page_cache >__delete_from_page_cache >page_cache_tree_delete
staticinlineintadd_to_page_cache(structpage*page, structaddress_space*mapping,pgoff_toffset,gfp_tgfp_mask) { interror; __set_page_locked(page); error=add_to_page_cache_locked(page,mapping,offset,gfp_mask); if(unlikely(error)) __clear_page_locked(page); returnerror; } voiddelete_from_page_cache(structpage*page) { structaddress_space*mapping=page->mapping; void(*freepage)(structpage*); BUG_ON(!PageLocked(page)); freepage=mapping->a_ops->freepage; spin_lock_irq(&mapping->tree_lock); __delete_from_page_cache(page,NULL); spin_unlock_irq(&mapping->tree_lock); mem_cgroup_uncharge_cache_page(page); if(freepage) freepage(page); page_cache_release(page); }
这两个函数是通用的,block device 和 文件inode 都可以调用,至于更新的是块设备的(buffers)还是文件的(cached),取决于参数变量mapping:如果mapping对应的是块设备,那么相应的统计信息会反映在 buffers 中;如果mapping对应的是文件inode,影响的就是 cached。我们下面看看kernel中哪些地方会把块设备的mapping传递进来。
首先是块设备本身,打开时使用 bdev->bd_inode->i_mapping。
staticintblkdev_open(structinode*inode,structfile*filp) { structblock_device*bdev; /* *Preservebackwardscompatibilityandallowlargefileaccess *evenifuserspacedoesn'taskforitexplicitly.Somemkfs *binaryneedsit.Wemightwanttodropthisworkaround *duringanunstablebranch. */ filp->f_flags|=O_LARGEFILE; if(filp->f_flags&O_NDELAY) filp->f_mode|=FMODE_NDELAY; if(filp->f_flags&O_EXCL) filp->f_mode|=FMODE_EXCL; if((filp->f_flags&O_ACCMODE)==3) filp->f_mode|=FMODE_WRITE_IOCTL; bdev=bd_acquire(inode); if(bdev==NULL) return-ENOMEM; filp->f_mapping=bdev->bd_inode->i_mapping; returnblkdev_get(bdev,filp->f_mode,filp); }
其次,文件系统的Superblock也是使用块设备:
structsuper_block{ ... structblock_device*s_bdev; ... } intinode_init_always(structsuper_block*sb,structinode*inode) { ... if(sb->s_bdev){ structbacking_dev_info*bdi; bdi=sb->s_bdev->bd_inode->i_mapping->backing_dev_info; mapping->backing_dev_info=bdi; } ... }
sb表示SuperBlock,s_bdev就是块设备。Superblock是文件系统的metadata(元数据),不属于文件,没有对应的inode,所以,对metadata操作所涉及的缓存页都只能利用块设备mapping,算入 buffers 的统计值内。
如果文件含有间接块(indirect blocks),因为间接块也属于metadata,所以走的也是块设备的mapping。查看源代码,果然如此:
ext4_get_blocks ->ext4_ind_get_blocks ->ext4_get_branch ->sb_getblk staticinlinestructbuffer_head* sb_getblk(structsuper_block*sb,sector_tblock) { return__getblk(sb->s_bdev,block,sb->s_blocksize); }
这样我们就知道了buffers 是块设备(block device)占用的缓存页,分为两种情况:
直接对块设备进行读写操作;
文件系统的metadata(元数据),比如 SuperBlock。
验证:
现在我们来做个测试,验证一下上述结论。既然文件系统的metadata会用到 buffers,我们用 find 命令扫描文件系统,观察 buffers 增加的情况:
#free totalusedfreesharedbufferscached Mem:3848656288950895914853162638962023340 -/+buffers/cache:6022723246384 Swap:203161202031612 #find/-nameabc.def #free totalusedfreesharedbufferscached Mem:3848656298405286460453203196122023348 -/+buffers/cache:6410923207564 Swap:203161202031612
再测试一下直接读取block device,观察buffers增加的现象:
#free totalusedfreesharedbufferscached Mem:3848656300694484171253163310202028648 -/+buffers/cache:6472763201380 Swap:203161202031612 #ddif=/dev/sda1of=/dev/nullcount=2000 2000+0recordsin 2000+0recordsout 1024000bytes(1.0MB)copied,0.026413s,38.8MB/s #free totalusedfreesharedbufferscached Mem:3848656300770484095253163318722028692 -/+buffers/cache:6471403201516 Swap:203161202031612
结论:
free 命令所显示的 buffers 表示块设备(block device)所占用的缓存页,包括直接读写块设备、以及文件系统元数据(metadata)如SuperBlock所使用的缓存页;而 cached 表示普通文件所占用的缓存页。
-
内存
+关注
关注
8文章
3019浏览量
74000 -
Linux系统
+关注
关注
4文章
593浏览量
27392 -
命令
+关注
关注
5文章
683浏览量
22011 -
Buffers
+关注
关注
0文章
13浏览量
10439
原文标题:【内存】buffers与cached的区别
文章出处:【微信号:嵌入式与Linux那些事,微信公众号:嵌入式与Linux那些事】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论