Page Cache产生的两种方式
Buffered I/O(标准 I/O):
写操作会先写用户缓冲区(Userpace Page),再将用户缓冲区的数据拷贝到内核缓冲区(Pagecache Page)
读操作会先从内核缓冲区拷贝到用户缓冲区,再从用户缓冲区读数据,也就是 buffer 和文件内容不存在任何映射关系。
Memory-Mapped I/O(存储映射 I/O):
直接将 Pagecache Page 给映射到用户地址空间,用户直接读写 Pagecache Page 中内容,减少数据在用户态和内核态中的拷贝,从而提高IO效率。
模拟Page Cache的产生过程 - 标准I/O
testPCIncrease.sh
创建一个文件,通过计算创建前后active+inactive的差值来得出page cache的大小。
#!/bin/sh
#这是我们用来解析的文件
MEM_FILE="/proc/meminfo"
#这是在该脚本中将要生成的一个新文件
NEW_FILE="/home/terry/pc.write.out"
#我们用来解析的Page Cache的具体项
active=0
inactive=0
pagecache=0
IFS=' '
#从/proc/meminfo中读取File Page Cache的大小
function get_filecache_size()
{
items=0
while read line
do
if [[ "$line" =~ "Active:" ]]; then
read -ra ADDR <<<"$line"
active=${ADDR[1]}
let "items=$items+1"
elif [[ "$line" =~ "Inactive:" ]]; then
read -ra ADDR <<<"$line"
inactive=${ADDR[1]}
let "items=$items+1"
fi
if [ $items -eq 2 ]; then
break;
fi
done < $MEM_FILE
}
#读取File Page Cache的初始大小
get_filecache_size
let filecache="$active + $inactive"
#写一个新文件,该文件的大小为1048576 KB
dd if=/dev/zero of=$NEW_FILE bs=1024 count=1048576 &> /dev/null
#文件写完后,再次读取File Page Cache的大小
get_filecache_size
#两次的差异可以近似为该新文件内容对应的File Page Cache
#之所以用近似是因为在运行的过程中也可能会有其他Page Cache产生
let size_increased="$active + $inactive - $filecache"
#输出结果
echo "File size 1048576KB, File Cache increased" $size inc
总结
在创建一个文件的过程中,代码中 /proc/meminfo 里的Active(file) 和 Inactive(file) 这两项会随着文件内容的增加而增加,它们增加的大小跟文件大小是一致的(这里之所以略有不同,是因为系统中还有其他程序在运行),且增加的 Page Cache 是 Inactive(File) 这一项。
Page Cache产生过程
1.往用户缓冲区 buffer(这是 Userspace Page) 写入数据。
2.将buffer 中的数据拷贝到内核缓冲区(这是 Pagecache Page),如果内核缓冲区中还没有这个 Page,就会发生 Page Fault 会去分配一个 Page,拷贝结束后该 PagecachePage 是一个 Dirty Page(脏页)。
3.该 Dirty Page 中的内容会同步到磁盘,同步到磁盘后,该 Pagecache Page 变为 Clean Page 并且继续存在系统中。
Page Cache的三个生长周期
Alloc Page 可以理解为 Page Cache 的“诞生”
Dirty Page 理解为Page Cache 的婴幼儿时期(最容易生病的时期)
Clean Page 理解为 Page Cache的成年时期(在这个时期就很少会生病了)。
Tips: 如果是读文件产生的 Page Cache,它的内容跟磁盘内容是一致的,所以它一开始是 Clean Page,除非改写了里面的内容才会变成 Dirty Page(返老还童)。
如何监控脏页(婴儿期) nr_dirty 表示当前系统中积压了多少脏页,nr_writeback 则表示有多少脏页正在回写到磁盘中,他们两个的单位都是 Page(4KB)。
[root@yz117 ~]# cat /proc/vmstat | egrep "dirty|writeback"
nr_dirty 3
nr_writeback 0
nr_writeback_temp 0
nr_dirty_threshold 228873
nr_dirty_background_threshold 76228
Page Cache的两种消亡方式
直接回收和后台回收
free 命令中的 buff/cache 中的这些可以看成“活着”的 Page Cache,只要有足够可回收的page cache,就可以继续分配内存,不会发生OOM。
如何观察PC的回收过程
sar
[root@yz117 ~]# sar -B 1
Linux 4.18.0-305.19.1.el8_4.x86_64 (yz117) 05/14/2022 _x86_64_ (2 CPU)
04:52:38 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
04:52:39 PM 0.00 0.00 3891.00 0.00 2810.00 0.00 0.00 0.00 0.00
04:52:40 PM 0.00 26.00 3868.00 0.00 2840.00 0.00 0.00 0.00 0.00
04:52:41 PM 0.00 0.00 3864.00 0.00 2810.00 0.00 0.00 0.00 0.00
04:52:42 PM 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
04:52:43 PM 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
04:52:44 PM 0.00 0.00 14.00 0.00 35.00 0.00 0.00 0.00 0.00
04:52:45 PM 0.00 36.00 0.00 0.00 2.00 0.00 0.00 0.00 0.00
04:52:46 PM 0.00 0.00 6.00 0.00 94.00 0.00 0.00 0.00 0.00
04:52:47 PM 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
04:52:48 PM 0.00 0.00 0.00 0.00 58.00 0.00 0.00 0.00 0.00
04:52:49 PM 0.00 0.00 0.00 0.00 74.00 0.00 0.00 0.00 0.00
指标含义
pgscank/s : kswapd(后台回收线程) 每秒扫描的 page 个数。
pgscand/s: Application 在内存申请过程中每秒直接扫描的 page 个数。
pgsteal/s: 扫描的 page 中每秒被回收的个数。
%vmeff: pgsteal/(pgscank+pgscand), 回收效率,越接近 100 说明系统越安全,越接
近 0 说明系统内存压力越大。
几个注意点
Page Cache产生的时机
Page Cache 是在应用程序读写文件的过程中产生的,所以在读写文件之前我们需要留意是否还有足够的内存来分配 Page Cache
Page Cache 中的脏页很容易引起问题,我们可以对其进行监控
cat /proc/vmstat | egrep "dirty|writeback"
在系统可用内存不足的时候就会回收 Page Cache 来释放出来内存,我们可以通过sar 或者 /proc/vmstat 来观察这个行为从而更好的判断问题是否跟回收有关