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

我們先丟擲結論,如果你對研究過程感興趣可以繼續閱讀後面的段落:
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: 3848656 kB
MemFree: 865640 kB
Buffers: 324432 kB
Cached: 2024904 kB
...
SwapTotal: 2031612 kB
SwapFree: 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()
函式的返回值:longnr_blockdev_pages(void)
{
structblock_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
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
(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;
}
voiddelete_from_page_cache(struct page *page)
{
structaddress_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
。staticintblkdev_open(struct inode * inode, struct file * filp)
{
structblock_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
也是使用塊裝置:structsuper_block {
...
structblock_device *s_bdev;
...
}
intinode_init_always(struct super_block *sb, struct inode *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
staticinline
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 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+0 records
in
2000+0 records out
1024000 bytes (1.0 MB) copied, 0.026413 s, 38.8 MB/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
表示普通檔案所佔用的快取頁。