天天看點

buffers linux指令,free指令顯示的buffers與cached的差別

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

# free

total used free shared buffers cached

Mem: 3848656 2983016 865640 5312 324432 2024904

-/+ buffers/cache: 633680 3214976

Swap: 2031612 0 2031612

1

2

3

4

5

# free

totalusedfreesharedbufferscached

Mem:3848656298301686564053123244322024904

-/+buffers/cache:6336803214976

Swap:203161202031612

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

“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

...

1

2

3

4

5

6

# 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

...

1

2

3

4

5

6

7

8

9

10

11

# 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(),閱讀得知:

“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

...

1

2

3

4

# cat /proc/vmstat

...

nr_file_pages587334

...

注意以上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;

}

1

2

3

4

5

6

7

8

9

10

11

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字段的函數就是:

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);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

staticinlineintadd_to_page_cache(structpage*page,

structaddress_space*mapping,pgoff_toffset,gfp_tgfp_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;

}

voiddelete_from_page_cache(structpage*page)

{

structaddress_space*mapping=page->mapping;

void(*freepage)(structpage*);

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;

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);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

staticintblkdev_open(structinode*inode,structfile*filp)

{

structblock_device*bdev;

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;

returnblkdev_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;

}

...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

structsuper_block{

...

structblock_device*s_bdev;

...

}

intinode_init_always(structsuper_block*sb,structinode*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

static inline struct buffer_head *

sb_getblk(struct super_block *sb, sector_t block)

{

return __getblk(sb->s_bdev, block, sb->s_blocksize);

}

1

2

3

4

5

6

7

8

9

10

ext4_get_blocks

->ext4_ind_get_blocks

->ext4_get_branch

->sb_getblk

staticinlinestructbuffer_head *

sb_getblk(structsuper_block *sb,sector_tblock)

{

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

1

2

3

4

5

6

7

8

9

10

11

12

13

# free

totalusedfreesharedbufferscached

Mem:3848656288950895914853162638962023340

-/+buffers/cache:6022723246384

Swap:203161202031612

# find / -name abc.def

# free

totalusedfreesharedbufferscached

Mem:3848656298405286460453203196122023348

-/+buffers/cache:6410923207564

Swap:203161202031612

再測試一下直接讀取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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# free

totalusedfreesharedbufferscached

Mem:3848656300694484171253163310202028648

-/+buffers/cache:6472763201380

Swap:203161202031612

# dd if=/dev/sda1 of=/dev/null count=2000

2000+0recordsin

2000+0recordsout

1024000bytes(1.0MB)copied,0.026413s,38.8MB/s

# free

totalusedfreesharedbufferscached

Mem:3848656300770484095253163318722028692

-/+buffers/cache:6471403201516

Swap:203161202031612

結論:

free 指令所顯示的 “buffers” 表示塊裝置(block device)所占用的緩存頁,包括直接讀寫塊裝置、以及檔案系統中繼資料(metadata)如SuperBlock所使用的緩存頁;

而 “cached” 表示普通檔案所占用的緩存頁。