复合页
Linux 内核在 5.16 版本 12 中对其内存管理系统进行了一次巨大的重构,引入了 folio —— 复合页的概念,folio 本质上就是一组 pages,之所以需要 folio 是因为随着计算机硬件的飞速发展,计算机处理数据的速度也越来越快,有很多压测数据都表明当内核使用更大的数据块的时候其 I/O 性能会更高,内核中有很多需要操作比 page 更大的数据块的场景
struct page
返璞归真,删繁就简,去除了之前的那些扩展,回归单一的语义 —— 代表单个内存页,然后将 page 内嵌到 folio 结构体中,以 folio 表示一个复合页 (folio 也可以表示一个单页)。
Page Cache 和 Buffer Cache
Buffer Cache
是对粒度更细的设备块的缓存,而 Page Cache
是基于虚拟内存的页单元缓存,因此还是会基于 Buffer Cache
,也就是说如果是缓存文件内容数据就会在内存里缓存两份相同的数据,存在冗余和不一致的问题。在 Linux 2.4 版本之后,kernel 就将两者进行了统一,Buffer Cache
不再以独立的形式存在,而是以融合的方式存在于 Page Cache
中:
写回缓存(writeback)
当 Page Cache 中的某些缓存页被更新之后,这些内存页会被标记为 PG_dirty
,也就是"脏"数据。内核需要某种机制来确保内存中的 dirty pages 能被写回到磁盘:
- 用户进程手动调用
sync()
/fsync()
/fdatasync()
等系统调用同步地将当前的脏页写入磁盘。 - 内核通过 flusher 线程定期自动将脏页写回到磁盘。
- 当 Page Cache 中的 dirty pages 比例超过一个特定的阀值时,会挤占其他用户进程的可用内存,这时就需要让 Page Cache 释放掉一部分内存来救急,但是脏页所占的内存是不能直接就回收的,否则会造成缓存和磁盘的数据不一致,这时内核就会将当前的脏页写回磁盘,然后清理掉这些内存页的
PG_dirty
标志,使其变成"干净的"内存页,此时 Page Cache 就可以按照特定的策略 (比如 LRU 算法) 选出一部分页面来释放掉,收缩自身占用的内存。 - 当脏页在内存中的驻留时间超过一个特定的阀值时,内核会将这些"超时的"脏页写回磁盘,以确保这些脏页不会因为停留在内存中太久而发生某种意外丢失。
- 当 Page Cache 中的 dirty pages 比例超过一个特定的阀值时,会挤占其他用户进程的可用内存,这时就需要让 Page Cache 释放掉一部分内存来救急,但是脏页所占的内存是不能直接就回收的,否则会造成缓存和磁盘的数据不一致,这时内核就会将当前的脏页写回磁盘,然后清理掉这些内存页的
手动写回
- sync & syncfs:将系统中的所有脏页都回写到磁盘
- fsync & fdatasync:只对某个文件内容(后者)或者文件内容和元数据(后者)刷盘
- sync_file_range:指定文件范围而不是整个文件去刷盘
- O_SYNC & O_DSYNC & O_RSYNC标记:
O_SYNC
:每次读文件写操作都会阻塞直到数据被写入磁盘,相当于每次写操作之后都调用fsync()
。O_DSYNC
:和O_SYNC
一样,但是只会把文件数据写入磁盘,只有在某些特定的文件元信息会影响后续的读写操作的时候才会将其一起写入磁盘,相当于fdatasync()
。O_RSYNC
:只影响读操作,需要和O_SYNC
或O_DSYNC
一起用。当设置了这个标志之后,调用read()
的时候就会阻塞直到文件中的所有最近更新的数据都被写回磁盘之后才返回,也就是确保读操作取回的数据永远是最新的。实际上 Linux 并没有严格按照 POSIX 标准实现这个标志,而是在内核中简单地定义O_RSYNC
为O_SYNC
。
读缓存
预读策略