linux内核分析——页高速缓存和页回写

页高速缓存和页回写

从访问速度来看,内存访问速度高于磁盘访问,而缓存访问速度又高于内存。

缓存手段

页高速缓存是由内存中的物理页面组成,对应的是磁盘上的物理块,可以动态扩大缩小。当内核开始读操作时,会先检查数据是否子啊缓存中,不命中采取磁盘找。

现在的缓存一般焊接在CPU上

写缓存

Linux在调用写操作时,使用的策略是——回写。这种策略是直接写到缓存中,而存储不是立刻更新,而是将页高速缓存中被写入的页面标记成脏页,表示缓存与磁盘不同步。延迟写磁盘,能方便后续对该页面的操作。

缓存回收

Linux实现的是一种改良LRU算法,则使用双链。Linux维护的是两个链表——活跃链表和非活跃链表,非活跃链表的页面是可以被换出的,而两个链表要保持数量平衡。

Linux页高速缓存

由于页高速缓存中的页可能包含多个不连续的物理磁盘块。Linux页高速缓存使用一个新的结构来管理缓存项和页面io操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct address_space {
struct inode *host; /* owning inode */
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* page_tree lock */
unsigned int i_mmap_writable; /* VM_SHARED ma count */
struct prio_tree_root i_mmap; /* list of all mappings */
struct list_head i_mmap_nonlinear; /* VM_NONLINEAR ma list */
spinlock_t i_mmap_lock; /* i_mmap lock */
atomic_t truncate_count; /* truncate re count */
unsigned long nrpages; /* total number of pages */
pgoff_t writeback_index; /* writeback start offset */
struct address_space_operations *a_ops; /* operations table */
unsigned long flags; /* gfp_mask and error flags */
struct backing_dev_info *backing_dev_info; /* read-ahead information */
spinlock_t private_lock; /* private lock */
struct list_head private_list; /* private list */
struct address_space *assoc_mapping; /* associated buffers */
};

其中i_mmap字段是一个优先搜索树,它包含了在该结构体中所有共享的与私有的映射页面。

address_space 操作

a_ops域指向地址空间对象中的操作函数表,由adress_space_operations结构体表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct address_space_operations {
int (*writepage)(struct page *, struct writeback_control *);
int (*readpage) (struct file *, struct page *);
int (*sync_page) (struct page *);
int (*writepages) (struct address_space *, struct writeback_control *);
int (*set_page_dirty) (struct page *);
int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned);
int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);
int (*commit_write) (struct file *, struct page *, unsigned, unsigned);
sector_t (*bmap)(struct address_space *, sector_t);
int (*invalidatepage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);
};

这里面最重要的是读写——readpage()和writepage()。

对于readpage,Linux内核试图在页高速缓存中使用find_get_page(mapping, index)找到需要的数据,将一个adress_space对象和一个偏移量传给该方法。如果找不到会返回一个NULL,并且内核新分配一个页面,并之前搜索的页面加入到页高速缓存。

对于writepage,首先在cache中搜索需要的页,如果需要的页不在,则新分配一个空闲项;下一步,内核会创建一个写请求,接着数据被从用户空间拷贝到内核缓冲,最后写入磁盘。

基树

每个address_page对象都有唯一的基树,保存在page_tree结构体中。只要指定了文件偏移量,就可以在基树中迅速检索到希望的页面。

flusher线程

在内存中累积起来的脏页最终会被写回磁盘,当下列3种情况发生时,脏页被写回磁盘:

  • 当空闲内存低于阈值时;
  • 当脏页在内存中驻留时间过长;
  • 当用户进程调用sync()和fsync()时;

在Linux内核中由一群flusher线程执行这三种工作,flusher线程会被周期性唤醒,或者因为空闲内存过低被唤醒。