1. 缓存手段
页高速缓存由内存物理页面组成,内容为磁盘上的物理块。
其大小可动态调整。页高速缓存可减少I/O操作次数。
1.1. 写缓存
写磁盘时,有3种策略:
- 不缓存:直接写入磁盘,并使缓存失效
- 自动更新缓存,并写入磁盘文件(即write-through cache)
- 回写(Linux采用的):写操作先写入缓存,但不会立即写入磁盘,而会将页面标记为“脏”,并添加到链表中,回写进程会周期性将其写入磁盘,并消除“脏”标志
1.2. 缓存回收/替换
Linux对非脏页面进行回收/替换,增加可用页数。替换的策略有多种,如:
- LRU
- 双链策略(LRU/2):维护2个链表——活跃链表和非活跃链表,前者不能被换出,后者可被换出。2个链表会动态平衡,如活跃链表的页面可被移到非活跃链表中。2个链表由伪LRU构造。
- 多链策略(LRU/n),类似于LRU/2
2. Linux页高速缓存
2.1. address_space
它和vm_area_struct
类似,但是表示的是物理地址(VMA表示的是虚拟地址),其定义在<linux/fs.h>
中。
其中i_mmap
字段是一个优先搜索树,包含了在该结构体中所有共享的与私有的映射页面。
通常address_space
会和一个inode
或者swapper
关联。
2.2. 操作
由address_space_operations
结构体表示,如:
- 写回页面:
writepage(struct page*, struct writeback_control*)
- 读取页面:
readpage(struct file*, struct page*)
- 设置脏页:
set_page_dirty(struct page*)
- …
对于读缓存页面:
- 使用
find_get_page(mapping, index)
找到需要的数据,传入adress_space
和一个offset
。- 如果找不到会返回一个
NULL
,并且内核新分配一个页面,并将之前搜索的页面加载到页高速缓存
- 如果找不到会返回一个
对于写缓存页面:
- 首先搜索需要的页
- 如果需要的页不在,则新分配一个空闲项;
- 然后创建一个写请求
- 接着数据被从用户空间拷贝到缓冲中,标记为脏页
- 最后写入磁盘(被回写进程)
2.3. Radix树
每个address_space
都有一个唯一的Radix树(page_tree
域)。
它是一个二叉树,是一种压缩前缀树(前缀树是Trie,它比Trie更节省空间),只要指定文件偏移,就能迅速找到所需的页。
它定义在<linux/radix_tree.h>
中。
3. 缓冲区高速缓存
之前的Linux有,这样会有2个缓冲区,造成浪费,且同步开销变大,因此现在被统一了,只有页高速缓存。
4. flusher
线程
它将脏页写回磁盘。
在下面3种情况下,发生页面写回:
- 空闲内存低于一个阈值(低于阈值后,调用
flusher_threads()
通知并唤醒) - 脏页驻留时间高于一个阈值(通过周期性唤醒实现)
- 显式调用
sync()
和fsync()
内核通过多个flusher
线程解决回写拥塞,每个线程可独立将脏页写回磁盘,且不同flusher
线程处理不同的设备队列。