Linux内核之 页高速缓存与页回写

1、页高速缓存(cache)

是LINUX内核实现的一种主要磁盘缓存。它主要用来减少对磁盘的I/O操作。具体而言,通过把磁盘的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问

2、页高速缓存的价值

磁盘高速缓存的价值在两方面:

  • 访问磁盘的速度远低于访问内存的速度;

  • 数据一旦被访问,就很有可能在短期内再次被访问时间局部性原理),这些数据会被暂存在高速缓存中,实现快速命中

3、页高速缓存的实现理论

页高速缓存由RAM中的物理页组成。缓存中每一页对应着磁盘中的多个块

  • 内核开始一个操作,首先检查页高速缓存中是否有该数据。

  • 如果有,则直接读取。这一步叫做缓存命中;如果没有,则直接从磁盘读写,这叫未缓存命中。

  • 内核调度块I/O操作从磁盘中读取数据,并将全部数据或者部分数据放入页高速缓存中。注意:缓存谁是取决于谁被访问到。系统不一定将文件中全部放入内存,可能只是部分内容。

举个例子:

使用文本编辑器打开一个源程序时,该文件的数据就被调入内存。编辑该文件的过程中,越来越多的数据相继被调入内存页(空间局部性原理)。最后,当你编译它的时候,内核可以直接使用页高速缓存中的页,而不需要重新从磁盘读取该文件了。

4、页高速缓存的意义

减少对磁盘的访问,实现高速访问数据

5、页高速缓存的机制有哪些?

页高速缓存主要有3种机制来保证读、写缓存以及释放缓存,分别如下:

  • 读缓存

  • 写缓存

  • 缓存回收

读缓在第3点已经有描述了,此处不做赘述。主要讲下写缓存和缓存回收。

5.1 写缓存

内核需要从磁盘读数据时,如果在页高速缓存中没有读到数据,就会直接从磁盘中读取数据并将数据存入页高速缓存中,以保证下次命中。

而写缓存在写磁盘时会有3种策略:

(1)不缓存

这种做法也叫做直接I/O

数据写入磁盘时,不经过页高速缓存,而是直接将数据写入磁盘。

这种做法的缺点很明显,如果下次需要读这个数据时,因为页高速缓存无此数据,因此需要直接从磁盘读取,需要额外开销。

因此该策略很少使用。

(2)写透缓存

即写操作更新内存缓存,同时也更新磁盘文件。

该策略可以保持缓存一致性——缓存数据的同时在后备存储(磁盘)保持同步。

这个与不缓存策略比较有一点优势就是,读可以试图先读页高速缓存

(3)回写

程序执行写操作直接将数据写到缓存中,然后将页高速缓存中被写入的页面标记为“脏”,并且将这些“脏”页面加入到脏链表中。

最后,回写进程会将脏链表中的脏页刷新到磁盘中,最终做到保证磁盘和内存中数据的一致性。

目前主流写缓存策略就是回写策略

5.2 缓存回收

缓存回收,也叫缓存淘汰,作用有三个:

  • 缓存中的数据清除(即清除页的数据);

  • 为更重要的缓存项提供空闲物理页;

  • 为了收缩缓存大小,以减轻内存的压力。

缓存中的什么内容将被清除的策略或者什么样的干净页可以回收的策略,就叫做缓存策略。   

Linux的缓存回收是通过选择干净页(非脏页)进行简单地替换——注意:干净页也是有数据的,只不过这个页此时没有回写操作罢了。如果干净页不够的话,内核强制进行回写操作,以释放出更多的干净页。   

但是,哪些干净页是可以回收的呢?不能够随随便便的就回收一个干净页啊。

对于回收策略Linux内核提供了两种方法:

(1)最近最少使用

简称LRU。LRU回收策略需要跟踪每个页面的访问踪迹,以便回收最老时间戳的页面。

该算法的效果在于:缓存的数据越久未被访问,则越大可能不会再次被访问,而近期访问的,则很大可能会再次被访问。   

但是,LRU有一个致命的缺点——内核无法预测一个文件只会被访问一次后不会再被访问。

(2)双链策略   

这个是修改过后的LRU,内核维护的不再是一个LRU链表,而是两个:活跃链表和非活跃链表

处于活跃链表的页面被认为是‘热’的,且不会被换出(回收);而在非活跃链表上的也是会被回收的。

ps:写到这里,你是否记得MySQL缓存淘汰机制?它俩是如此的相似!而且,不止是MySQL,还有Redis、文件系统、Ceph等等都可以见到LRU和改进版的身影!论理解内核的重要性:)

5.3 脏数据什么时候被写到磁盘中?

脏数据:页高速缓存的数据比后台存储的数据更加新的时候,这些数据就叫做脏数据。

脏数据被回写到磁盘的时机有3种:

  • 空闲内存低于一个特定阈值时。内核必须回写数据以释放内存,因为缓存回收只能回收干净页。当干净页数量足够多时,内核就会收缩缓存,释放内存。该值可以通过修改/proc/sys/vm/dirty_background_ratio设定(这个值是百分比,入文件中数字为10,则表示占全部内存的10%)

  • 当脏页在内存中存储时间超过一个阈值后,内核必须将脏页回写入磁盘,确保脏页不会无限期驻留内存。

  • 用户进程显示调用sync()和fsync()系统调用时,内核会按要求将数据回写进磁盘。

以上工作均有一群flusher线程执行。

为什么是一群了?想象下,如果是单个线程,当回写操作很多时,就会造成拥塞。这是因为单一线程可能堵塞在某个设备的已拥塞请求队列(正在等待将请求提交给磁盘的I/O请求队列上),而其他设备的请求队列无法得到处理。

在2.6.32内核中,flusher线程取代了pdflush线程,pdflush线程个数一般为2-8个;而pdflush又是取代最早的bdflush和kupdated两个线程。

flusher线程的数目是根据磁盘数量变化的一般每个磁盘对应一个线程。

5.4 页面回写可以人为设置吗?

答案是可以的,上面讲脏数据写会磁盘时,就已经讲了一个例子了,这里做一下汇总。

变量功能
/proc/sys/vm/dirty_background_ratio空闲内存阈值,占全部内存的百分比。内存中的空闲页低于这个比例时,pdflush线程开始回写脏页
/proc/sys/vm/dirty_ratio空闲内存阈值,占全部内存的百分比。当一个进程产生的脏页达到这个比例时,开始回写
/proc/sys/vm/laptop_mode布尔值。用于控制膝上型计算机模式

6. 什么是膝上型计算机模式

这是一个特殊的回写策略,该策略主要目标是:硬盘转动的机械行为最小化,允许硬盘尽可能的长时间停滞,以此延长电池供电时间。

默认是关闭,可以设置开启。一般较少使用。

但页高速缓存和页回写是必须要搞清楚的,工作中很多情况都要考虑到这一点,比如写计划任务时,如果计划任务中有操作同一个文件的,那这里就会有一个坑~~~

7.磁盘缓存和磁盘缓冲的区别和联系是什么?缓存需要缓冲吗?缓冲又需要缓存吗?

区别:缓存(cache)是针对文件;而缓冲(buffer)是针对块设备的。   

页高速缓存缓存了缓冲的,这一部分就叫做缓冲区高速缓存,需要注意的是,缓冲区高速缓存是没有作为独立缓存的,而是作为页高速缓存的一部分。   

关于缓存和缓冲之间的联系与区别,可以参考合在页高速缓存里面的缓冲区高速缓存这篇文章。

最后我们捋一下磁盘、内存、文件系统与页、块之间的关系。

扇区:是块设备中最小的可寻址单元(常见大小512字节);是块设备的基本寻址和操作单元。

:是文件系统最小逻辑可寻址单元,文件系统的抽象,只能通过块访问文件系统。通常包含多个扇区。

页:内核把物理页作为内存管理的基本单元。块必须小于等于页,页可以包含多个块。

所以,三者的大小(或者说映射)关系是 页 >= 块 > 扇区。

我们今天的主题是页高速缓存,page cache,我们从这些概念可以看出,它缓存的对象是文件系统,即磁盘上的数据。

而在上一章我们讲述了一个概念:

缓冲区(buffer):磁盘块在内存中的表示,是内核操作块设备的逻辑单元。

所以,缓冲区也是可以缓存的,即缓冲区高速缓存,buffer cache。而现在的内核中它已经是页高速缓存的一部分了。

后记

准备写这篇文章有点犹豫,开始觉得这个主题较容易,可能相对内核其他知识还有MySQL原理,觉得不太值得一写。

现在写下来,终于明白了缓存与缓冲的区别。谁说不是一种收获呢?以前也其实存在疑问,还在网上想找英文版(参考书),但只是看到了英文版的目录。

想不到,在写作的过程中,借鉴别人的文章时,终于搞明白了。多写多看多交流!

当然还有一点,看书有疑问时,请看英文版!

参考资料:

《Linux内核设计与实现》原书第三版

https://blog.csdn.net/yjh314/article/details/78850571