【記憶體】buffers與cached的區別

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

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

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

下面是分析過程:

先用 strace 跟蹤 free 命令,看看它是如何計算 bufferscached 的:
# 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 中的 BuffersCached 又是如何得來的呢?這回沒法偷懶,只能去看原始碼了。原始碼檔案是: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 cachepage 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 表示除去 buffersswap 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),累加每個塊裝置的inodei_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表示SuperBlocks_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 表示普通檔案所佔用的快取頁。
推薦閱讀點選標題可跳轉

相關文章