引入
free命令可以显示整个系统的内存使用情况:
# 注意不同版本的free输出可能会有所不同
$ free
total used free shared buff/cache available
Mem: 8169348 263524 6875352 668 1030472 7611064
Swap: 0 0 0
如上,这个界面中包含了物理内存Mem和交换分区Swap的具体使用情况,比如总内存、已用内存、缓存、可用内存等。其中缓存是Buffer和Cache两部分的总和。
这⾥的⼤部分指标都⽐较容易理解,但 Buffer和 Cache可能不太好区分。从字⾯上来说,Buffer是缓冲区,⽽Cache是缓存,两者都是数据在内存中的临时存储。那么,这两种“临时存储”有什么区别呢?
注:下文,Buffer和Cache都⽤英⽂来表示,避免跟⽂中的“缓存”⼀词混淆。⽽⽂中的“缓存”,则通指内存中的临时存储。
free数据的来源
Buffer和Cache还是我们⽤free获得的指标。因此,我们可以man free:
buffers
Memory used by kernel buffers (Buffers in /proc/meminfo)
cache Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
buff/cache
Sum of buffers and cache
从free的⼿册中,你可以看到 buffer 和 cache 的说明。
- Buffers是内核缓冲区用到的内存,对应的是/proc/meminfo 中的 Buffers 值。
- Cache 是内核⻚缓存和Slab⽤到的内存,对应的是 /proc/meminfo 中的 Cached 与 SReclaimable 之和。
这⾥的说明告诉我们,这些数值都来⾃ /proc/meminfo,但更具体的 Buffers、Cached和SReclaimable 的含义,还是没有说清楚。
要弄明⽩它们到底是什么,我估计你第⼀反应就是去百度或者 Google⼀下。虽然⼤部分情况下,⽹络搜索能给出⼀个答案。
但是,且不说筛选信息花费的时间精⼒,对你来说,这个答案的准确性也是很难保证的。
要注意,⽹上的结论可能是对的,但是很可能跟你的环境并不匹配。最简单来说,同⼀个指标的具体含义,就可能因为内核版本、性能⼯具版本的不同⽽有挺⼤差别。
那么,有没有更简单、更准确的⽅法,来查询它们的含义呢?
proc文件系统
/proc是Linux内核提供的一种特殊文件系统,是用户和内核交互的接口。比如,用户可以从/proc中查询内核的运行状态和配置选项,查询进程的运行状态、统计数据等,当然,你可以通过/proc来修改内核的配置等。
proc文件系统同时也是很多性能同居的最终数据来源,比如free就是通过读取/prco/meminfo,得到内存的使用情况。
执⾏ man proc ,你就可以得到 proc ⽂件系统的详细⽂档。
注意这个⽂档⽐较⻓,你最好搜索⼀下(⽐如搜索 meminfo),以便更快定位到内存部分。
Buffers %lu
Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
Cached %lu
In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
...
SReclaimable %lu (since Linux 2.6.19)
Part of Slab, that might be reclaimed, such as caches.
SUnreclaim %lu (since Linux 2.6.19)
Part of Slab, that cannot be reclaimed on memory pressure.
通过这个文档,我们可以看到:
- Buffers是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多块写合并成单次大的写等
- Cached是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
- SReclaimable 是 Slab 的⼀部分。Slab包括两部分,其中的可回收部分,⽤ SReclaimable 记录;⽽不可回收部分,⽤SUnreclaim 记录。
好了,我们终于找到了这三个指标的详细定义。不过,知道这个定义就真的理解了吗?这⾥我给你提了两个问题,你先想想能不能回答出来。
- 第一个问题,Buffer的文档没有提到这是磁盘读数据还是写数据的缓存,⽽在很多⽹络搜索的结果中都会提到 Buffer 只是对将要写⼊磁盘数据的缓存。那反过来说,它会不会也缓存从磁盘中读取的数据呢?
- 第⼆个问题,⽂档中提到,Cache 是对从⽂件读取数据的缓存,那么它是不是也会缓存写⽂件的数据呢?
为了解答这两个问题,接下来,我将⽤⼏个案例来展示, Buffer 和 Cache 在不同场景下的使⽤情况。
案例
准备
- 机器配置:2 CPU,8GB 内存。
- 预先安装 sysstat 包,如 apt install sysstat。
之所以要安装sysstat,是因为我们要用到vmstat,来观察buffer和cache的变化情况。虽然从/proc/meminfo里也可以读到相同的结果,但是还是vmstat的结果更加直观。
另外,这⼏个案例使⽤了 dd 来模拟磁盘和⽂件的 I/O,所以我们也需要观测 I/O 的变化情况。
上⾯的⼯具安装完成后,你可以打开两个终端,连接到 Ubuntu 机器上。
准备环节的最后⼀步,为了减少缓存的影响,记得在第⼀个终端中,运⾏下⾯的命令来清理系统缓存:
# 清理⽂件⻚、⽬录项、Inodes等各种缓存
$ echo 3 > /proc/sys/vm/drop_caches
这⾥的 /proc/sys/vm/drop_caches ,就是通过 proc ⽂件系统修改内核⾏为的⼀个示例,写⼊ 3 表示清理⽂件⻚、⽬录项、Inodes等各种缓存。
场景1:磁盘和⽂件写案例
我们先来模拟第一个场景。首先,在第一个中断,运行下面命令:
# 每隔1秒输出1组数据
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7743608 1112 92168 0 0 0 0 52 152 0 1 100 0 0
0 0 0 7743608 1112 92168 0 0 0 0 36 92 0 0 100 0 0
输出界⾯⾥, 内存部分的 buff 和 cache ,以及 io 部分的 bi 和 bo 就是我们要关注的重点。
- buff和cache就是我们前面看到的buffers和cache,单位是kb
- bi和bo则分别表示块设备读取和写入的大小,单位为块/秒。因为Linux中块的大小是1KB,所以这个单位也就是等价于KB/s
正常清空下,空闲系统中,你应该看到的是,这几个值在多次结果中一直保持不变。
接下来,在第二个终端执行dd命令,通过读取随机设备,生成一个50MB大小的文件:
$ dd if=/dev/urandom of=/tmp/file bs=1M count=500
然后再回到第⼀个终端,观察Buffer和Cache的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7499460 1344 230484 0 0 0 0 29 145 0 0 100 0 0
1 0 0 7338088 1752 390512 0 0 488 0 39 558 0 47 53 0 0
1 0 0 7158872 1752 568800 0 0 0 4 30 376 1 50 49 0 0
1 0 0 6980308 1752 747860 0 0 0 0 24 360 0 50 50 0 0
0 0 0 6977448 1752 752072 0 0 0 0 29 138 0 0 100 0 0
0 0 0 6977440 1760 752080 0 0 0 152 42 212 0 1 99 1 0
...
0 1 0 6977216 1768 752104 0 0 4 122880 33 234 0 1 51 49 0
0 1 0 6977440 1768 752108 0 0 0 10240 38 196 0 0 50 50 0
通过观察vmstat的输出,我们发现,在dd命令运行时,Cache在不停地增长,而Buffer基本保持不变。
再进一步观察IO的情况,你会看到:
- 在cache刚开始增长时,块设备IO很少,bi只出现了一次488KB/s,bo则只有一次4KB。而过一段时间后,才会出现大量的块设备写,比如bo 变成了122880。
- 当dd命令结束后,Cache不再增长,但块设备写还会持续一段时间,并且,多次IO写的结果加起来,才是dd要写的500M的数据
把这个结果,跟我们刚刚了解到的Cache的定义做个对⽐,你可能会有点晕乎。为什么前⾯⽂档上说 Cache 是⽂件读的⻚缓
存,怎么现在写⽂件也有它的份?
这个疑问,我们暂且先记下来,接着再来看另⼀个磁盘写的案例。两个案例结束后,我们再统⼀进⾏分析。
不过,对于接下来的案例,我必须强调⼀点:
下⾯的命令对环境要求很⾼,需要你的系统配置多块磁盘,并且磁盘分区 /dev/sdb1 还要处于未使⽤状态。如果你只有⼀块磁
盘,千万不要尝试,否则将会对你的磁盘分区造成损坏。
如果你的系统符合标准,就可以继续在第⼆个终端中,运⾏下⾯的命令。清理缓存后,向磁盘分区/dev/sdb1 写⼊2GB的随机
数据:
# ⾸先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 然后运⾏dd命令向磁盘分区/dev/sdb1写⼊2G数据
$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048
然后,再回到终端⼀,观察内存和I/O的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 7584780 153592 97436 0 0 684 0 31 423 1 48 50 2 0
1 0 0 7418580 315384 101668 0 0 0 0 32 144 0 50 50 0 0
1 0 0 7253664 475844 106208 0 0 0 0 20 137 0 50 50 0 0
1 0 0 7093352 631800 110520 0 0 0 0 23 223 0 50 50 0 0
1 1 0 6930056 790520 114980 0 0 0 12804 23 168 0 50 42 9 0
1 0 0 6757204 949240 119396 0 0 0 183804 24 191 0 53 26 21 0
1 1 0 6591516 1107960 123840 0 0 0 77316 22 232 0 52 16 33 0
从这⾥你会看到,虽然同是写数据,写磁盘跟写⽂件的现象还是不同的。写磁盘时(也就是bo⼤于 0 时),Buffer和Cache都
在增⻓,但显然Buffer的增⻓快得多。
这说明,写磁盘⽤到了⼤量的Buffer,这跟我们在⽂档中查到的定义是⼀样的。
对⽐两个案例,我们发现,写⽂件时会⽤到 Cache 缓存数据,⽽写磁盘则会⽤到 Buffer 来缓存数据。所以,回到刚刚的问题,虽然文档上只提到,cache是文件读的缓存,但实际上,cache也会缓存写文件时的数据
场景2:磁盘和⽂件读案例
了解了磁盘和⽂件写的情况,我们再反过来想,磁盘和⽂件读的时候,⼜是怎样的呢?
我们回到第⼆个终端,运⾏下⾯的命令。清理缓存后,从⽂件/tmp/file中,读取数据写⼊空设备:
# ⾸先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运⾏dd命令读取⽂件数据
$ dd if=/tmp/file of=/dev/null
然后,再回到终端⼀,观察内存和I/O的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 1 0 7724164 2380 110844 0 0 16576 0 62 360 2 2 76 21 0
0 1 0 7691544 2380 143472 0 0 32640 0 46 439 1 3 50 46 0
0 1 0 7658736 2380 176204 0 0 32640 0 54 407 1 4 50 46 0
0 1 0 7626052 2380 208908 0 0 32640 40 44 422 2 2 50 46 0
观察vmstat的输出,你会发现读取文件时(也就是bi大于0时),buffer保持不变,而cache则在不停的增长。这跟我们查到的定义“cache是对文件读的页缓存”是一致的
那么,磁盘读⼜是什么情况呢?我们再运⾏第⼆个案例来看看。
⾸先,回到第⼆个终端,运⾏下⾯的命令。清理缓存后,从磁盘分区 /dev/sda1中读取数据,写⼊空设备:
# ⾸先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运⾏dd命令读取⽂件
$ dd if=/dev/sda1 of=/dev/null bs=1M count=1024
然后,再回到终端⼀,观察内存和I/O的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7225880 2716 608184 0 0 0 0 48 159 0 0 100 0 0
0 1 0 7199420 28644 608228 0 0 25928 0 60 252 0 1 65 35 0
0 1 0 7167092 60900 608312 0 0 32256 0 54 269 0 1 50 49 0
0 1 0 7134416 93572 608376 0 0 32672 0 53 253 0 0 51 49 0
0 1 0 7101484 126320 608480 0 0 32748 0 80 414 0 1 50 49 0
观察 vmstat 的输出,你会发现读磁盘时(也就是bi⼤于0时),Buffer和Cache都在增⻓,但显然Buffer的增⻓快很多。这说明读磁盘时,数据缓存到了 Buffer 中。
即:读文件时数据会缓存到cache中,而读磁盘时数据会缓存到buffer中。
从而,虽然⽂档提供了对Buffer和Cache的说明,但是仍不能覆盖到所有的细节。⽐如说,今天我们了解到的这两点:
- buffer既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”
- Cache既可以⽤作“从⽂件读取数据的⻚缓存”,也可以⽤作“写⽂件的⻚缓存”。
那么我们应该怎么理解内存中的buffer和cache呢?
Buffer是对磁盘数据的缓存,而Cache是对文件数据的缓存,它们会用在读请求中,也会用在写请求中
为什么要使用Buffer和Cache缓存
- 从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作
- 从读的角度来说,既可以加速读取那些需要频繁访问的数据,也降低了频繁IO对磁盘的压力
总结
磁盘和文件的区别
-
磁盘是一个块设备,可以划分为不同的分区;在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件。
-
其实 Linux 中“⼀切皆⽂件”,⽽⽂章中提到的“⽂件”是普通⽂件,磁盘是块设备⽂件,这些⼤家可以执⾏ “ls -l <路径>” 查看它们的区别(输出的含义如果不懂请 man ls 查询)。
# ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 12⽉ 12 10:17 /dev/sda1
# ls -ld /root/
drwx------ 12 root root 4096 12⽉ 26 11:48 /root/
- 在读写普通文件时,会经过文件系统,由文件系统负责和磁盘交互;而读写磁盘或者分区时,会跳过文件系统,也就是所谓的“裸IO”,这两种读写方式所使用的缓存是不同的,也就是文中所讲的的 Cache 和 Buffer 区别。
理论上,一个文件读首先到Block Buffer,然后到Page Cache。有了文件系统才有了Page Cache。
在老的Linux上这两个Cache是分块的。那这样对于文件数据,会被Cache两次。这种方案虽然简单但是低效。后期Linux把这两个Cache统一了。对于文件,Page Cache指向Block Buffer,对于非文件则是Block Buffer。这样就如⽂件实验的结果,⽂件操作,只影响Page Cache,Raw操作,则只影响Buffer. ⽐如⼀此VM虚拟机,则会越过File System,只接操作 Disk, 常说的Direct IO.
Linux⾥的块设备,可以直接访问(⽐如数据库应⽤程序),也可以存储⽂件系统然后被访问。
是否绕开⽂件系统,直接对磁盘进⾏读写会更快呢?
- 去掉缓存的话,⽂件系统⽐磁盘⼜多了⼀层,所以有可能⽐直接磁盘读写慢。
- 但⽂件系统也有缓存,所以⼤部分情况下不绕开会更快
关于内存回收
- LRU回收的是缓存,Swap换出的是不可回收的内存,⽐如进程的堆内存