目录
一、缓存手段
二、Linux 页高速缓存
三、flusher 线程
Linux 内核实现了一个被叫做页高速缓存(page cache)的磁盘缓存,它主要用来减少对磁盘的 I/O 操作。它是通过把磁盘中的数据缓存到内存中,把对磁盘的访问变为对物理内存的访问。
临时局部原理(temporal locality):如果在第一次访问数据时缓存它,那么就极有可能在短时间内再次访问到,因为程序内存在着循环;并且其相邻的数据也可能在短时间内被访问到,因为程序内存放的数据有很多是连续的(如数组)。
一、缓存手段
页高速缓存是由内存中的物理页组成的,其内容对应磁盘上的物理块。页高速缓存可以动态调整,它可以通过占用空闲内存以扩张大小,也可以自我收缩以缓解内存使用压力。
当进程发起一个 read() 系统调用,它会首先检查需要的数据是否在页高速缓存中,如果在则直接从内存中读取,而无需访问磁盘,此时缓存命中;如果没在缓存中,则内核必须调度块 I/O 操作从磁盘读取数据。
当进程发起一个 write() 系统调用时,缓存一般有三种策略:
- 不缓存(no-write),这种策略不缓存任何写操作,写的时候直接写入磁盘,然后使缓存中对应数据失效。一般不使用这种策略。
- 写透缓存(write-through cache),写操作会同时更新缓存和磁盘上的数据。这种策略对保持缓存一致性很有好处。
- 回写(write-back),写操作会直接更新缓存中的数据,但不会立即更新磁盘上的数据,而是将页高速缓存中被写入的页面标记成 “脏页”,并且被加入到脏页链表中,然后由一个回写进程周期性将脏页链表中的页写回到磁盘。当脏页被换出缓存时,则需要立即回写该页。Linux 所采用的就是这种策略。
当缓存满后,如何选择合适的页面换出是一种重要的工作,有如下两种策略:
- 最近最少使用策略(LRU): LRU 回收策略需要跟踪每个页面的访问时间,以便能回收最老时间戳的页面(最少被使用的页面)。LRU 是通过维护一个按照时间为序的页链表来实现的。
- 双链策略:Linux 采用的是一个修改过的 LRU,也叫双链策略。这种策略要维护两个链表,活跃链表和非活跃链表。处于活跃链表上的页面被认为是访问较多的且不会被换出,而在非活跃链表上的页面则是可以被换出的。这种双链方式也被称为 LRU/2。
二、Linux 页高速缓存
在页高速缓存中的页可能包含了多个不连续的物理磁盘块。Linux 页高速缓存使用了一个新对象管理缓存项和页 I/O 操作,这个对象是 address_space 结构体。文件可以有多个虚拟地址(可以被多个 vm_area_struct 结构体标识),但在物理内存只能有一份(只能有一个 address_space 结构体)。
三、flusher 线程
由于页高速缓存的缓存作用,写操作实际上会被延迟。当页高速缓存中的数据比后台存储的数据更新的时候,该数据就被称作脏数据。在以下三种情况发生时,脏页被写回磁盘:
- 当空闲内存低于一个特定的阈值时,内核必须将脏页写回磁盘以释放内存。
- 当脏页在内存中驻留时间超过一个特定的阈值时,内核必须将超时的脏页写回磁盘,以确保脏页不会无限期地驻留在内存中。
- 当用户进程调用 sync() 和 fsync() 系统调用时,内核会按照要求执行回写操作。
在 2.6 内核中,由一群内核线程(flusher 线程)执行这三种操作。flusher 线程基于页面,它将整个脏页写回磁盘。