free命令显示的buffers与cached的区别

    xiaoxiao2023-03-25  5

    据说很少有人能说清楚 free 命令所显示的 “buffers” 与 “cached” 之间的区别:

    # free             total      used      free    shared    buffers    cached Mem:      3848656    2983016    865640      5312    324432    2024904 -/+buffers/cache:    633680    3214976 Swap:      2031612          0    2031612

    我们先列出结论,如果你对研究过程感兴趣可以继续阅读后面的段落: “buffers” 表示块设备(block device)所占用的缓存页,包括:直接读写块设备、以及文件系统元数据(metadata)比如SuperBlock所使用的缓存页; “cached” 表示普通文件数据所占用的缓存页。

    下面是分析过程:先从用 strace 跟踪 free 命令开始,看能不能发现它是如何计算 “buffers” 和 “cached” 的:

    # strace free ... open("/proc/meminfo",O_RDONLY)        =3 lseek(3,0,SEEK_SET)                  =0 read(3,"MemTotal:        3848656 kB\nMemF"...,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(),阅读得知:

    “Buffers” 来自于 nr_blockdev_pages() 的返回值。 “Cached” 来自于以下公式: global_page_state(NR_FILE_PAGES) – total_swapcache_pages – i.bufferram 以上计算cached的公式中,global_page_state(NR_FILE_PAGES) 来自 vmstat[NR_FILE_PAGES],表示所有的缓存页(page cache)的总和,它包括:

    Cached buffers 交换区缓存(swap cache) 这里简单解释一下swap cache:

    那些匿名内存页,比如用户进程通过malloc()申请的内存页是没有关联任何文件的(有别于backing storage基于磁盘文件的内存页),如果发生swapping换页,这类内存页会被写入交换区。从一个匿名内存页被确定要被换页开始,它就被计入了swap cache,但是不一定会被立刻写入物理交换区,因为Linux的原则是除非绝对必要,尽量避免I/O。所以swap cache中包含的是被确定要swapping换页、但是尚未写入物理交换区的匿名内存页。

    vmstat[NR_FILE_PAGES] 可以通过 /proc/vmstat 来查看,表示所有缓存页的总数量:

    # cat /proc/vmstat ... nr_file_pages587334 ...

    注意以上nr_file_pages是以page为单位,而不像free命令是以KB为单位,一个page等于4KB。

    直接修改 nr_file_pages 的内核函数是: __inc_zone_page_state(page, NR_FILE_PAGES) 和 __dec_zone_page_state(page, NR_FILE_PAGES), 一个用于增加,一个用于减少。

    先看”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字段的函数就是add_to_page_cache和__remove_from_page_cache:

    staticinline intadd_to_page_cache(structpage *page,                 structaddress_space *mapping,pgoff_t offset,gfp_t gfp_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; }   voidremove_from_page_cache(structpage *page) {         structaddress_space *mapping=page->mapping;         void(*freepage)(structpage *)=NULL;         structinode *inode=mapping->host;           BUG_ON(!PageLocked(page));           if(IS_AOP_EXT(inode))                 freepage=EXT_AOPS(mapping->a_ops)->freepage;           spin_lock_irq(&mapping->tree_lock);         __remove_from_page_cache(page);         spin_unlock_irq(&mapping->tree_lock);         mem_cgroup_uncharge_cache_page(page);           if(freepage)                 freepage(page); }

    这两个函数是通用的,block device 和 文件inode 都可以调用,至于更新的是块设备的(buffers)还是文件的(cached),取决于调用参数变量mapping:如果mapping对应的是文件inode,自然就不会影响到 “buffers”;如果mapping对应的是块设备,那么相应的统计信息会反映在 “buffers” 中。我们下面看看kernel中哪些地方会把块设备的mapping传递进来。

    搜索内核源代码发现,ext4_readdir 函数调用 page_cache_sync_readahead 时传递的参数是 sb->s_bdev->bd_inode->i_mapping,其中s_bdev就是块设备,也就是说在读目录(ext4_readdir)的时候可能会增加 “buffers” 的值:

    staticintext4_readdir(structfile *filp,                         void*dirent,filldir_t filldir) {   ...         structsuper_block *sb=inode->i_sb; ...                         if(!ra_has_index(&filp->f_ra,index))                                 page_cache_sync_readahead(                                         sb->s_bdev->bd_inode->i_mapping,                                         &filp->f_ra,filp,                                         index,1); ... }

    继续琢磨上面的代码,sb表示SuperBlock,属于文件系统的metadata(元数据),突然间一切恍然大悟:因为metadata不属于文件,没有对应的inode,所以,对metadata操作所涉及的缓存页都只能利用块设备mapping,算入 buffers 的统计值内。

    打个岔:ext4_readdir() 中调用 page_cache_sync_readahead() 显然是在进行预读(read-ahead),为什么read-ahead没有使用普通文件inode的mapping,而是使用了底层的块设备呢?从记载在补丁中的说明来看,这是一个权宜之计,看这里,所以不必深究了。

    举一反三,如果文件含有间接块(indirect blocks),因为间接块属于metadata,所以走的也是块设备的mapping。查看源代码,果然如此:

    ext4_get_blocks ->  ext4_ind_get_blocks     ->  ext4_get_branch         ->  sb_getblk   staticinline structbuffer_head * sb_getblk(structsuper_block *sb,sector_t block) {                               return__getblk(sb->s_bdev,block,sb->s_blocksize); }

    这样,我们就知道了,”buffers” 是块设备(block device)占用的缓存页,分为两种情况:

    直接对块设备进行读写操作; 文件系统的metadata(元数据),比如 SuperBlock。 验证: 现在我们来做个测试,验证一下上述结论。既然读取EXT4文件系统的目录会使用到 “buffers”,我们用 find 命令扫描文件系统,观察 “buffers” 增加的情况:

    # free             total      used      free    shared    buffers    cached Mem:      3848656    2889508    959148      5316    263896    2023340 -/+buffers/cache:    602272    3246384 Swap:      2031612          0    2031612   # find / -name abc.def   # free             total      used      free    shared    buffers    cached Mem:      3848656    2984052    864604      5320    319612    2023348 -/+buffers/cache:    641092    3207564 Swap:      2031612          0    2031612

    再测试一下直接读取block device,观察”buffers”增加的现象:

    # free             total      used      free    shared    buffers    cached Mem:      3848656    3006944    841712      5316    331020    2028648 -/+buffers/cache:    647276    3201380 Swap:      2031612          0    2031612   # dd if=/dev/sda1 of=/dev/null count=2000 2000+0records in 2000+0records out 1024000bytes(1.0MB)copied,0.026413s,38.8MB/s   # free             total      used      free    shared    buffers    cached Mem:      3848656    3007704    840952      5316    331872    2028692 -/+buffers/cache:    647140    3201516 Swap:      2031612          0    2031612

    结论: free 命令所显示的 “buffers” 表示块设备(block device)所占用的缓存页,包括直接读写块设备、以及文件系统元数据(metadata)如SuperBlock所使用的缓存页; 而 “cached” 表示普通文件所占用的缓存页。

    转载请注明原文地址: https://ju.6miu.com/read-1204015.html
    最新回复(0)