在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,感觉是内存不够用了。其实不然,这是Linux内存管理的一个优秀的特征,主要特点是,物理物理内存有多大,Linux都将其充分利用,将一些程序调用过的硬盘数据读入内存(buffer/Cache),利用内存读写的高速特性来提供Linux系统的数据访问性能高。本章的主要内容学习内容如下:
- 什么是Page cache(what)
- 为什么需要page cache(why)
- Page cache是如何产生和释放的(how)
1. 什么是Page Cache
当程序去读文件,可以通过read也可以通过mmap去读,当你通过任何一种方式从磁盘读取文件时,内核都会给你申请一个Page cache,用来缓存磁盘上的内容。这样读过一次的数据,下次读取的时候就直接从Page cache里去读,提升了系统的整体性能。
对于Linux可以怎么来观察Page Cache呢?其实,在Linux上直接可以通过命令来看,他们的内容是一致的。
首先最简单的是free命令来看一下
首先我们来看看buffers和cached的定义
- Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
- Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘
buffer cache和page cache在处理上是保持一致的,但是存在概念上的差别,page cache是针对文件的cache,buffer是针对磁盘块数据的cache,仅此而已。
2. 为什么需要page cache
通过上图,我们可以直观的看到,标准的I/O和内存映射会先将数据写到Page Cache,这样做是通过减小I/O次数来提升读写效率。我们来实际的例子,我们先来生成一个1G的文件,然后通过把Page cache清空,确保文件内容不在内存中,一次来比较第一次和第二次读文件的差异。
# 1. 生成一个1G的文件
dd if=/dev/zero of=/home/dd.out bs=4096 count=1048576
# 2.第一次读取文件的耗时如下:
root@root-PC:~# time cat /home/dd.out &> /dev/null
real 0m1.109s
user 0m0.016s
sys 0m1.093s
# 23.清空Page Cache,先执行一下sync来将脏页同步到磁盘,在执行drop cache
sync && echo 3 > /proc/sys/vm/drop_caches
# 4. 第二次读取文件的耗时如下:
root@root-PC:~# time cat /home/dd.out &> /dev/null
real 0m36.018s
user 0m0.069s
sys 0m4.839s
通过这两次详细的过程,可以看出第一次读取文件的耗时远小于第二次耗时
- 因为第一次读取的时候,由于文件内容已经在生成文件的时候已经存在,所以直接从内存读取的数据
- 第二次会将缓存数据清掉,会从磁盘上读取内容,磁盘I/O比较耗时,内存相比磁盘会快很多
所以Page Cache存在的意义,减小I/O,提升应用的I/O速度。对于Page Cache方案,我们采用原则如下
- 如果不想增加应用的复杂度,我们优先使用内核管理的Page Cache
- 如果应用程序需要做精确控制,就需要不走Cache,因为Page Cache有它自身的局限性,就是对于应用程序太过于透明了,以至于很难有好的控制方法。
3. Page Cache是如何“诞生的”
Page Cache的产生有两种不同的方式
- Buffered I/O(标准I/O)
- Memory-Mapped I/O(储存映射IO)
这两种方式分别都是如何产生Page Cache的呢?
从图中可以看到,二者是都能产生Page Cache,但是二者还是有差异的
- 标准I/O是写的话用户缓存区(User page对应的内存),然后再将用户缓存区里的数据拷贝到内核缓存区(Pagecahe Page对应的内存);如果是读的话则是内核缓存区拷贝到用户缓存区,再从用户缓存区去读数据,也就是Buffer和文件内容不存在映射关系
- 储存映射IO,则是直接将Page Cache的Page给映射到用户空间,用户直接读写PageCache Page里的数据
从原理来说储存映射I/O要比标准的I/O效率高一些,少了“用户空间到内核空间互相拷贝”的过程。下图是一张简图描述这个过程:
- 首先,往用户缓冲区Buffer(用户空间)写入数据,然后,Buffer中的数据拷贝到内核的缓冲区(这个是PageCache Page)
- 如果内核缓冲区还没有这个page,就会发生Page Fault会去分配一个Page;如果有,就直接用这个PageCache的Page
- 拷贝结束后,该PageCache的Page是一个Dirty Page脏页,然后该Dirty Page中的内容会同步到磁盘,同步到磁盘后,该PageCache Page变味Clean Page并且继续存在系统中
我们可以通过手段来测试脏页,如下图所示
$ cat /proc/vmstat | egrep "dirty|writeback"
nr_dirty 44
nr_writeback 0
nr_writeback_temp 0
nr_dirty_threshold 1538253
nr_dirty_background_threshold 768187
- nr_dirty:表示系统中积压了多少脏页(单位为Page 4KB)
- nr_writeback则表示有多少脏页正在回写到磁盘中(单位为Page 4KB)
总结,
读过程,当内核发起一个读请求时候
- 先检查请求的数据是否缓存到page Cache中,如果有则直接从内存中读取,不访问磁盘
- 如果Cache中没有请求数据,就必须从磁盘中读取数据,然后内核将数据缓存到Cache中
- 这样后续请求就可以命中cache,page可以只缓存一个文件的部分内容,不需要把整个文件都缓存
写过程,当内核发起一个写请求时候
- 直接写到Cache中,内核会将被写入的Page标记为dirty,并将其加入到dirty list中
- 内核会周期性的将dirty list中的page回写到磁盘上,从而使磁盘上的数据和内存中缓存的数据一致
3. page cache是如何“死亡”
free命令中的buffer/cache中的是“活着”的Page Cache,那他们是什么时候被回收的呢?
回收的主要方式有两种
- 直接回收:
- 后台回收:
观察Page cache直接回收和后台回收最简单方便的方式,借助这个工具,可以明确观察内存回收行为
- pgscank/s: kswapd(后台回收线程)每秒扫面的Page个数
- pgscand/s: Application在内存申请过程中每秒直接扫描的Page个数
- pgsteal/s: 扫面的page中每秒被回收的个数
- %vmeff: pgsteal/(pgscank+pgscand),回收效率,越接近100说明系统越安全,越接近0,说明系统内存压力越大
- pgpgin/s 表示每秒从磁盘或SWAP置换到内存的字节数(KB)
- pgpgout/s: 表示每秒从内存置换到磁盘或SWAP的字节数(KB)
- fault/s: 每秒钟系统产生的缺页数,即主缺页与次缺页之和(major + minor)
- majflt/s: 每秒钟产生的主缺页数.
- pgfree/s: 每秒被放入空闲队列中的页个数
详细的可参考网页sar工具在监控性能方向的实践
4. 总结
本章的主要内容是学习了什么是page Cache,为什么需要page Cache,Page Cache是如何诞生的以及如何死亡,一下几个重点:
- Page Cache属于内核,不属于用户
- Page Cache对于应用提升IO效率而言是一个投入产出比较高的方案,存在是很有必要的
- Page Cache是在应用程序读写文件的过程中产生,所以在读写文件之前,你需要留意是否还有足够的内存来分配Page Cache
- Page Cache中的脏页很容易引起问题
- 在系统内存不足的时候,系统就会回收Page Cache来释放出来内存,可以通过sar或者/proc/vmstat来观察这个行为从而更好的判断问题是否跟回收有关
Page Cache是将磁盘中的数据缓存到内存中,较小磁盘I/O操作,从而提高性能。此外还要确保Page Cache中的数据更改能同步到磁盘上,这称之为Page回写(Page WriteBack)。一个inode对应一个Page cache对象,一个Page Cache对象保护多个物理Page。对磁盘的数据进行缓存,从而提高性能主要基于两个因素:
- 磁盘访问速度比内存慢几个数量级
- 访问过的数据,很大概率再次访问(局部性原理)