首頁 > 軟體

free命令顯示的buffers與cached的區別

2020-06-16 17:46:43

據說很少有人能說清楚 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 kBnMemF"...,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” 表示普通檔案所占用的快取頁。

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-01/127869.htm


IT145.com E-mail:sddin#qq.com