「乾貨」linux free 命令輸出中 buffer 與 cache 的區別,有這篇就夠了

free 命令是Linux系統上檢視記憶體使用狀況最常用的工具,然而很少有人能說清楚 “buffers” 與 “cached” 之間的區別:

「乾貨」linux free 命令輸出中 buffer 與 cache 的區別,有這篇就夠了

我們先丟擲結論,如果你對研究過程感興趣可以繼續閱讀後面的段落:

“buffers” 表示塊裝置(block device)所佔用的快取頁,包括:直接讀寫塊裝置、以及檔案系統元資料(metadata)比如SuperBlock所使用的快取頁;

“cached” 表示普通檔案資料所佔用的快取頁。

下面是分析過程:

先用 strace 跟蹤 free 命令,看看它是如何計算 “buffers” 和 “cached” 的:

# strace free。。。open(“/proc/meminfo”, O_RDONLY) = 3lseek(3, 0, SEEK_SET) = 0read(3, “MemTotal: 3848656 kB\nMemF”。。。, 2047) = 1170。。。

顯然 free 命令是從 /proc/meminfo 中讀取資訊的,跟我們直接讀到的結果一樣:

# cat /proc/meminfoMemTotal: 3848656 kBMemFree: 865640 kBBuffers: 324432 kBCached: 2024904 kB。。。SwapTotal: 2031612 kBSwapFree: 2031612 kB。。。Shmem: 5312 kB。。。

那麼 /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_pages 587334。。。

注意以上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() 函式的返回值:

long nr_blockdev_pages(void){ struct block_device *bdev; long ret = 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); return ret;}

這段程式碼的意思是遍歷所有的塊裝置(block device),累加每個塊裝置的inode的i_mapping的頁數,統計得到的就是 buffers。顯然 buffers 是與塊裝置直接相關的。

那麼誰會更新塊裝置的快取頁數量(nrpages)呢?我們繼續向下看。

搜尋kernel原始碼發現,最終更新mapping->nrpages欄位的函式就是:

pagemap。h: add_to_page_cache

> filemap。c: add_to_page_cache_locked

> __add_to_page_cache_locked

> page_cache_tree_insert

和:

filemap。c: delete_from_page_cache

> __delete_from_page_cache

> page_cache_tree_delete

static inline int add_to_page_cache(struct page *page, struct address_space *mapping, pgoff_t offset, gfp_t gfp_mask){ int error; __set_page_locked(page); error = add_to_page_cache_locked(page, mapping, offset, gfp_mask); if (unlikely(error)) __clear_page_locked(page); return error;} void delete_from_page_cache(struct page *page){ struct address_space *mapping = page->mapping; void (*freepage)(struct page *); 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。

static int blkdev_open(struct inode * inode, struct file * filp){ struct block_device *bdev; /* * Preserve backwards compatibility and allow large file access * even if userspace doesn‘t ask for it explicitly。 Some mkfs * binary needs it。 We might want to drop this workaround * during an unstable branch。 */ 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; return blkdev_get(bdev, filp->f_mode, filp);}

其次,檔案系統的Superblock也是使用塊裝置:

struct super_block { 。。。 struct block_device *s_bdev; 。。。} int inode_init_always(struct super_block *sb, struct inode *inode){。。。 if (sb->s_bdev) { struct backing_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 static inline struct buffer_head *sb_getblk(struct super_block *sb, sector_t block){ return __getblk(sb->s_bdev, block, sb->s_blocksize);}

這樣我們就知道了”buffers” 是塊裝置(block device)佔用的快取頁,分為兩種情況:

直接對塊裝置進行讀寫操作;

檔案系統的metadata(元資料),比如 SuperBlock。

驗證:

現在我們來做個測試,驗證一下上述結論。既然檔案系統的metadata會用到 “buffers”,我們用 find 命令掃描檔案系統,觀察 “buffers” 增加的情況:

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

再測試一下直接讀取block device,觀察”buffers”增加的現象:

# free total used free shared buffers cachedMem: 3848656 3006944 841712 5316 331020 2028648-/+ buffers/cache: 647276 3201380Swap: 2031612 0 2031612 # dd if=/dev/sda1 of=/dev/null count=20002000+0 records in2000+0 records out1024000 bytes (1。0 MB) copied, 0。026413 s, 38。8 MB/s # free total used free shared buffers cachedMem: 3848656 3007704 840952 5316 331872 2028692-/+ buffers/cache: 647140 3201516Swap: 2031612 0 2031612

結論:

free 命令所顯示的 “buffers” 表示塊裝置(block device)所佔用的快取頁,包括直接讀寫塊裝置、以及檔案系統元資料(metadata)如SuperBlock所使用的快取頁;

而 “cached” 表示普通檔案所佔用的快取頁。

更多linux核心學習請觀看我的《linux核心開發100講》免費影片教程。

關注我,更多linux核心知識科普。