Linux系统内核为文件提供了一个缓存,目的是将一些磁盘上的数据缓存到RAM中,这样可以加快文件数据的访问——给定inode索引节点和文件页面偏移量时,可以快速地找到RAM中相应的文件页内容。这个能在称为页缓存(page cache)。使用页缓存的I/O操作称为buffer I/O,默认情况下内核都使用的是buffer I/O,但是有的应用(如数据库)不希望使用内核缓存,而是由应用提供缓存,这种由应用提供缓存的I/O称为direct I/O。设置了O_DIRECT
标志后,将跳过页缓存,直接写入用户缓部区。
Linux的页调整缓存能存储的页面范围很广,它缓存任何基于页的数据,包括普通文件、块设备文件和内存映射文件。在本文内为了简单起见只讨论普通磁盘文件的页缓存。
一个页缓存对应了一个文件,准确地说是一个inode索引节点,这个页缓存包含了数个物理页,每个物理页都缓存了这个文件的一部分数据,这个页缓存就用一个address_space
结构体来表示。不过address_space
不仅管理着这个文件的页缓存,也管理着这些缓存页(物理页)到进程空间的映射关系。address_space
结构体的定义如下:
//linux-3.13/include/linux/fs.h
struct address_space {
//该页缓存指向的文件的inode索引节点
struct inode *host; /* owner: inode, block_device */
//该页缓存中的所有缓存页通过一个由page结构体组成的基树组织在一直,目的是便于搜索
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* and lock protecting it */
unsigned int i_mmap_writable;/* count VM_SHARED mappings */
//指向一颗红黑树,节点是与这个文件有关的VMA结构体,这些VMA可以属于多个进程。如果文件没有被映射到内存空间,那么这个字段为空
struct rb_root i_mmap; /* tree of private and shared mappings */
struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
struct mutex i_mmap_mutex; /* protect tree, count, list */
/* Protected by tree_lock together with the radix tree */
//该页缓存中包含的缓存页数目
unsigned long nrpages; /* number of total pages */
pgoff_t writeback_index;/* writeback starts here */
//与address_space相关的操作函数,例如readpage()、writepage()等真正执行文件读写的函数
const struct address_space_operations *a_ops; /* methods */
unsigned long flags; /* error bits/gfp mask */
struct backing_dev_info *backing_dev_info; /* device readahead, etc */
spinlock_t private_lock; /* for use by the address_space */
struct list_head private_list; /* ditto */
void *private_data; /* ditto */
} __attribute__((aligned(sizeof(long))));
address_space
结构体和文件的对应关系:一个address_space
结构体和一个文件相对应,但是两个不同的address_space
上可能对应相同的磁盘数据,比如一个文件通过文件系统打开和使用块设备直接打开时。从address_space
可以知道一个文件“已被缓存的页帧”和“哪些进程的哪些VMA”包含了这些页帧。一个正在被访问的文件将拥有一个address_space
对象,即使这个文件暂时没有进程使用,页缓存也不会被回收,直到物理页帧不足的时候才被换出。
既然页缓存是为了减少对物理磁盘的访问,那么就必须提供必要的方法来判断一次文件I/O是否能通过页缓存而得到快速处理,也就是说判断一次文件I/O需要操作的数据是否在页缓存中。Linux中的每个文件都通过其对应的address_space
结构体中的基树page_tree
来有效地组织页缓存中的物理页,当指定文件及相应的偏移量的时候就能快速地判断该页是否已经存在于页缓存中。当一个文件载入内存的时候,其对应的页缓存是空的,随着文件的读写,磁盘中的数据被载入内存,并添加一个对应的缓存页到page_tree
对应的位置上。
在回收页帧而解除其映射的时候,需要找到全部引用该页帧的项,也就是找到所有引用了该address_space
的进程,这叫做反向映射(Reverse Mapping)。根据页帧用于匿名页还是文件映射页,反向映射的记录方式也有所不同。文件映射页相关的反向映射信息保存在address_space->i_mapp
中,匿名页的反向映射信息保存在vm_area_struct
结构体的anon_vma_node
和anon_vma
成员中。这里只介绍文件映射页,也就是该页缓存对应的是磁盘上的某个文件的情况。
Linux使用优先查找树来建立文件中的一段数据到进程的地址空间的映射关系。具体来说,address_space
使用i_mmap
维护一个优先树,该树包含了所有与此文件相关的vma,,而这些vma通过各自的mm_struct从而建立了到进程的联系。当需要解除一个页帧的映射时,先利用页帧的page->mapping
找到对应的address_space
,再利用address_space->i_mmap
字段找到这些vma优先搜索树的根,然后查找解除映射时哪些进程的哪个vma会受到影响,从而可以确定哪些进程的项需要修改。