【Linux】Linux Page Cache页面缓存的原理

Page cache(页面缓存)是计算机操作系统中的一种机制,用于将频繁访问的数据从磁盘存储到内存中,以便更快地访问。当程序从磁盘请求数据时,操作系统会检查该数据是否已经存在于页面缓存中。如果存在,数据可以直接从内存中获取,这比从磁盘访问要快得多。如果数据不在页面缓存中,它将从磁盘中获取并存储在缓存中供将来使用。

既然Page Cache是一种缓存,那么缓存必然带来以下问题:

  1. 占用内存多大空间?
  2. 内存空间满了怎么办,淘汰策略?
  3. 内存中的数据何时写入磁盘?
  4. 数据如何持久化、一致性如何保障?
  5. 内存中的数据是否会丢失?
  6. 持久化的触发时机?

【Linux】Linux Page Cache页面缓存的原理_第1张图片

page与Page Cache的关系

从磁盘中读取文件后写入Page Cache中,是将整个文件都写入呢,还是只写用到的那一部分?

page是内存管理分配的基本单位,Page Cache由多个page构成。page在操作系统中通常为4KB大小(32bits/64bits),而 Page Cache的大小则为4KB的整数倍。

操作系统为基于Page Cache的读缓存机制提供预读机制(PAGE_READAHEAD),例如用户线程仅仅请求读取磁盘上文件A的offset为0-3KB范围内的数据,由于磁盘的基本读写单位为block(4KB),于是操作系统至少会读0-4KB的内容,这恰好可以在一个page中装下。

但是操作系统出于局部性原理会选择将磁盘块offset[4KB,8KB)、[8KB,12KB)以及[12KB,16KB)都加载到内存,于是额外在内存中申请了3个page。

下面我们用一个例子来演示一下:

准备一个文本文件ooxx.txt,大小为,需大于4*4=16k:

$ ll ooxx.txt
-rw-rw-r-- 1 vagrant vagrant 41211 Dec 29 05:45 ooxx.txt

使用read命令读取ooxx.txt文件的第一行:

$ read a < ooxx.txt

$ echo $a
bash

那么ooxx.txt前4页的内容将会被缓存在Page Cache中,我们可以使用hcache命令来查看查询文件或者目录有多少页被载入Page Cache中:

$ hcache ooxx.txt
+----------+----------------+------------+-----------+---------+
| Name     | Size (bytes)   | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| ooxx.txt | 41211          | 11         | 4         | 036.364 |
+----------+----------------+------------+-----------+---------+

我们可以看到实际上内核使用readahead机制完成了16KB数据的读取,并放入了Page Cache中。

缺页中断

磁盘的数据怎么写入Page Cache?

操作系统以page为单位管理内存,当进程发现需要访问的数据不在内存时,操作系统可能会将数据以页的方式加载到内存中。上述过程被称为缺页中断,当操作系统发生缺页中断时,就会通过系统调用将page再次读到内存中。

缺页中断的作用

缺页中断是内存管理中的重要机制,具有以下重要作用:

  1. 页面调入内存:当应用程序访问的页面不在内存中时,缺页中断会触发操作系统将缺失的页面从磁盘加载到内存中。这样做的好处是,不需要将整个文件或数据集一次性全部加载到内存中,而是按需加载,只加载应用程序实际需要的页面。这样可以节省磁盘I/O开销,避免了无谓的磁盘读取操作,提高了系统的性能。

  2. 页面替换策略:当内存中的页面已满时,如果应用程序访问一个新的页面,操作系统需要选择一个页面进行替换,将其换出到磁盘上,为新的页面腾出空间。缺页中断提供了一个时机,让操作系统可以根据页面替换算法选择合适的页面进行替换,以保证内存中的页面是最有用的页面,提高内存的利用率。

  3. 延迟写策略:在写回策略下,当应用程序对一个脏页进行写操作时,脏页的写回操作不会立即进行,而是推迟到后续的某个时刻进行。这样可以将多次写操作合并为一次写操作,减少了磁盘写入的次数,降低了磁盘I/O开销。

  4. 页面预读取:为了进一步减少磁盘I/O开销,操作系统可以利用缺页中断的时机进行页面预读取。当应用程序访问一个页面时,操作系统可以预先加载一些相邻的页面到内存中,以提前满足应用程序的访问需求。这样可以减少后续的缺页中断和磁盘读取操作,提高了系统的响应速度。

  5. 权限检查和处理:缺页中断还可以用于检查应用程序对页面的访问权限。操作系统可以根据页面的访问权限(例如只读、读写等)来判断应用程序的访问是否合法,并根据需要进行相应的处理。

缺页中断的处理流程

当发生缺页中断时,操作系统会按照以下步骤来处理:

  1. 中断处理程序:当发生缺页中断时,CPU会暂停当前应用程序的执行,并跳转到操作系统的中断处理程序。这个中断处理程序负责处理缺页中断。

  2. 中断处理程序的执行:中断处理程序会首先保存当前应用程序的上下文信息,包括寄存器的值和程序计数器等。然后,它会根据缺页中断的原因进行相应的处理。

  3. 页面调入内存:如果缺页中断是由于应用程序访问的页面不在内存中引起的,操作系统会触发页面调入内存的过程。它会根据页面表中的信息,确定需要调入的页面的位置和大小,并从磁盘上读取相应的页面数据。

  4. 磁盘I/O操作:在页面调入内存的过程中,操作系统会进行磁盘I/O操作,将需要的页面数据从磁盘读取到内存中。这包括了磁盘寻道、读取数据和数据传输等操作。

  5. 更新页表:当页面数据成功加载到内存中后,操作系统会更新页表,将该页面的状态设置为已加载,并将页面的物理地址映射到对应的虚拟地址。

  6. 恢复上下文和重新执行应用程序:当页面调入内存完成后,中断处理程序会恢复之前保存的应用程序的上下文信息,并将控制权返回给应用程序。应用程序会继续执行之前被中断的指令,但这次访问的页面已经在内存中了,不会再触发缺页中断。

Page Cache的工作原理

Page Cache的工作原理如下:

  1. 当应用程序打开一个文件并读取其中的数据时,操作系统会将文件的内容读取到内存中,并将其缓存为一个或多个页(通常是4KB大小的页)。

  2. 这些缓存的页被存储在一个被称为Page Cache的内存区域中,它是内核管理的一部分。

  3. 当应用程序再次访问相同的文件时,操作系统首先检查Page Cache中是否存在这些页的副本。如果存在,操作系统会直接从Page Cache中返回数据,而不需要再次访问磁盘。

  4. 如果应用程序对文件进行写操作,数据会被写入到Page Cache中的脏页(dirty page)。脏页表示该页的内容已经被修改,但还没有写回到磁盘。

  5. 当系统内存压力较大或需要释放内存时,内核会触发缓存刷新(cache flushing)操作,将脏页从Page Cache写回到磁盘中。

Page Cache的回写策略

回写策略是为了确保缓存数据的一致性和避免数据丢失而设计的。当Page被标记为dirty时,表示该Page中的数据已经被修改过,这些数据需要被写回到磁盘上的,Linux提供了以下两种回写策略:

  • Write Through(写穿):向用户层提供特定接口,应用程序可主动调用接口来保证文件一致性;

  • Write back(写回):系统中存在定期任务(表现形式为内核线程),周期性地同步文件系统中文件脏数据块,这是默认的 Linux一致性方案;

Write Through写穿

Write Through(写透)是将数据写入Page Cache后,立即将数据写入磁盘设备。这样可以确保数据在发生故障或系统崩溃时不会丢失,因为数据已经被持久地写入磁盘。

在JAVA中使用如下的代码实现Write Through写穿:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class WriteThroughTest {
    public static void main(String[] args) throws IOException {
        File file = new File("/home/vagrant/testfileio/ooxx.txt");
        FileOutputStream out = new FileOutputStream(file);
        out.write("hello".getBytes(StandardCharsets.UTF_8));
        out.getFD().sync(); // 立即刷入磁盘
    }
}

我们可以使用strace命令追踪上面程序执行过程中产生的系统调用:

openat(AT_FDCWD, "/home/vagrant/testfileio/ooxx.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(4, "hello", 5)                    = 5
fsync(4)                                = 0
close(4)

可以系统调用中包含fsync,该命令会把文件描述符fd引用的文件修改过的元数据和数据立即写回到存储设备。

Write back写回

在Write Back模式下,写入操作只会将数据写入Page cache,并不立即写入磁盘设备。而是在后续的时间点或特定条件下,将脏页(已被修改但尚未写入磁盘)批量写回磁盘。

在JAVA中使用Write back写回只需要将上面例子中的立即刷入磁盘代码注释即可:

// out.getFD().sync()

这种回写策略下,数据的一致性完全依赖操作系统的自身机制,那么操作系统何时将Page Cache中的脏页数据写入磁盘中呢?

Page Cache写回时机

Linux内核提供了以下参数来优化Page Cache的性能和行为,我们可以通过这些参数来窥探Page Cache的写回时机。

$ sudo sysctl -a | grep "dirty"
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
vm.dirtytime_expire_seconds = 43200

具体参数说明:

  • vm.dirty_background_bytes:设置了系统内存中可以保持脏数据的最大字节数。当系统内存中的脏数据超过这个值时,Linux会开始触发后台刷新(异步刷新)将脏数据写入磁盘。

  • vm.dirty_background_ratio:设置了系统内存中可以保持脏数据的最大比例,默认为10%。

  • vm.dirty_bytes:设置了系统内存中允许累积的脏数据的最大字节数。当脏数据超过这个值时,Linux会触发前台刷新(同步刷新),直到将脏数据写入磁盘为止。

  • vm.dirty_ratio:设置了系统内存中允许累积的脏数据的最大比例,默认为20%。

  • vm.dirty_expire_centisecs:该参数指定了脏数据在内存中能够存活的时间,单位为百分之一秒。当脏数据在内存中超过这个时间后,系统会将其异步写入磁盘中,默认值为3000(30秒)。

  • vm.dirty_writeback_centisecs:表示系统在多长时间内进行一次脏数据的后台写回操作。它的单位是百分之一秒(centiseconds),默认值为500,即系统每5秒钟进行一次后台写回操作。

  • vm.dirtytime_expire_seconds:代表内存中脏数据的允许存储时间,单位为秒。当脏数据在内存中存储的时间超过这个时间,系统会将其写入磁盘,以释放内存。

跟pagecache相关的系统调用

内核提供了下面这些把Page Cache中内容写入磁盘的系统调用:

  • sync:将所有未写的系统缓冲区数据写入磁盘,不需要带任何参数。

  • syncfs:syncfs需要一个文件描述符,只将文件描述符指向的文件相关的文件系统的缓冲区数据写入磁盘。

  • fsync:将文件描述符fd引用的文件修改过的元数据和数据写入磁盘。

  • fdatasync:fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

手动清除Page Cache

$ sync && echo 1 > /proc/sys/vm/drop_caches

表示先执行sync命令,将所有缓存数据写入磁盘中,然后将“1”写入到/proc/sys/vm/drop_caches文件中,表示清空页缓存。

$ sync && echo 2 > /proc/sys/vm/drop_caches

表示先执行sync命令,将所有缓存数据写入磁盘中,然后将“2”写入到/proc/sys/vm/drop_caches文件中,表示清空目录缓存和页缓存。

$ sync && echo 3 > /proc/sys/vm/drop_caches

表示先执行sync命令,将所有缓存数据写入磁盘中,然后将“3”写入到/proc/sys/vm/drop_caches文件中,表示清空目录缓存、页缓存和inode缓存。

hcache工具的使用

hcache是一个用于分析和显示Page Cache统计信息的命令行工具。它可以提供关于Page Cache的详细信息,包括脏页和干净页的数量、Page Cache的大小、缓存命中率等。

hcache是基于pcstat的,pcstat可以查看某个文件是否被缓存和根据进程pid来查看都缓存了哪些文件。hcache在其基础上增加了查看整个操作系统Cache和根据使用Cache大小排序的特性。

安装:

$ wget https://silenceshell-1255345740.cos.ap-shanghai.myqcloud.com/hcache

$ chmod 755 hcache && mv hcache /usr/local/bin/

根据进程pid来查看都缓存了哪些文件

$ hcache -pid $$
+-----------------------------------------------------+----------------+------------+-----------+---------+
| Name                                                | Size (bytes)   | Pages      | Cached    | Percent |
|-----------------------------------------------------+----------------+------------+-----------+---------|
| /lib/x86_64-linux-gnu/libtinfo.so.5.9               | 170784         | 42         | 42        | 100.000 |
| /lib/x86_64-linux-gnu/ld-2.27.so                    | 179152         | 44         | 44        | 100.000 |
| /lib/x86_64-linux-gnu/libnss_compat-2.27.so         | 39744          | 10         | 10        | 100.000 |
| /lib/x86_64-linux-gnu/libnss_files-2.27.so          | 47568          | 12         | 12        | 100.000 |
| /lib/x86_64-linux-gnu/libnsl-2.27.so                | 97176          | 24         | 24        | 100.000 |
| /lib/x86_64-linux-gnu/libnss_nis-2.27.so            | 47576          | 12         | 12        | 100.000 |
| /usr/lib/locale/locale-archive                      | 3004224        | 734        | 734       | 100.000 |
| /lib/x86_64-linux-gnu/libc-2.27.so                  | 2030928        | 496        | 496       | 100.000 |
| /lib/x86_64-linux-gnu/libdl-2.27.so                 | 14560          | 4          | 4         | 100.000 |
| /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache | 26376          | 7          | 7         | 100.000 |
| /bin/bash                                           | 1113504        | 272        | 272       | 100.000 |
+-----------------------------------------------------+----------------+------------+-----------+---------+

hcache命令可以帮助您深入了解系统的Page Cache使用情况,以便进行性能分析和优化。

你可能感兴趣的:(操作系统,linux,缓存,pagecache,页面缓存,缓冲区)