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;

/*

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

}

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;

/*

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

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” 表示普通文件所占用的缓存页。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值