页高速缓存和页回写

时间:2020-12-30 07:39:43

    页高速缓存是linux内核实现磁盘缓存。它主要用来减少对磁盘的I/O操作。具体地讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问。

    磁盘高速缓存之所以在任何现代操作系统中尤为重要源自两个因素:第一,访问磁盘的速度要远远低于访问内存的速度,因此,从内存访问数据比从磁盘访问速度要快,若从处理器L1和L2高速缓存访问则更快。第二,数据一旦被访问,就很有可能在短期内再次被访问到。这种在短期内集中访问同一片数据的原理称为临时局部原理。临时局部原理能保证:如果在第一次访问数据时缓存它,那就极有可能在短期内再次被高速缓存命中(访问到高速缓存中的数据)。正是由于内存访问要比磁盘访问快得多,再加上数据一次被访问后更可能再次被访问的特点,所以磁盘的内存缓存将给系统存储性能带来质的飞跃。

    页高速缓存是由内存中的物理页面组成的,其内容对应磁盘上的物理块。页高速缓存大小能动态调整--它可以通过占用内存以扩张大小,也可以自我收缩以缓解内存使用压力。我们称正被缓存的存储设备为后备存储,因为缓存背后的磁盘无疑才是所有缓存数据的归属。当内核开始一个读操作(比如进程发送一个read()系统调用),它首先会检查需要的数据是否在页高速缓存中。如果在,则放弃访问磁盘,而直接从内存中读取。这个行为称作缓存命中。如果数据没有在缓存中,称为缓存为命中,那么内核必须调用块I/O操作从磁盘去读取数据。然后内核将读来的数据放入也缓存中,于是任何后续相同的数据读取都可命中缓存了。

    在把一页数据写到块设备之前,内核首先检查对应的页是否已经在高速缓存中,如果不在,就要先在其中增加一个新项,并用要写到磁盘中的数据填充该项。I/O数据的传送并不是马上开始,而是要延迟几秒之后才对磁盘进行更新,从而使进程有机会对要写入的磁盘的数据做进一步的修改(就是内核执行延迟的写操作)。

    内核的代码和内核数据结构不必行磁盘读,也不必写入磁盘,因此,页高速缓存中的页可能是下面的类型:

  • 含有普通文件数据的页
  • 含有目录的页
  • 含有直接从块设备文件读出的数据的页
  • 含有用户态进程数据的页,但页中的数据已经被交换到磁盘
  • 属于特殊文件系统文件的页,如共享内存的进程间通信所使用的特殊文件系统shm  

    页高速缓存中的每个页所包含的数据肯定属于某个文件。这个文件(或者更准备地说是文件的索引节点)就称为页的所有者。几乎所有的文件读和写操作都依赖于页高速缓存。

    内核设计者实现页高速缓存主要为了满足下面两张需要:

  • 快速定位含有给定所有者相关数据的特定页。为了尽可能充分发挥页高速缓存的优势,对它应该采用高速的搜索操作。
  • 记录在读或写页中的数据是应当如何处理高速缓存中的每个页。例如,从普通文件、块设备文件或交换区读一个数据页必须用不同的实现方式,因此内核必须根据页的所有者选择适当的操作。

    页高速缓存中的信息单位显然是一个完整的数据页。一个页中包含的次盘口在物理上不一定是相邻的,所以不能用设备号和块号来识别它,取而代之的是,通过页的所有者和所有者数据中的索引来识别也高速缓存中的页。

    address_space对象:

    页高速缓存的核心数据结构式address_space对象,它是一个嵌入在页所有者的索引节点对象中的数据结构。高速缓存中的许多也可能属于同一个所有者,从而可能被连接到同一个address_space对象。该对象还在所有者的页和对这些页的操作之间建立起链接关系。

    每个页描述符都包括页链接到页高速缓存的两个字段mapping和index。mapping字段指向拥有页的索引节点的address_space对象,index字段表示在所有者的地址空间中页大小为单位的偏移量。也就是在所有者的磁盘映像中页的数据的位置。在页高速缓存中查找页时使用这两个字段。

 struct address_space {
           struct inode            *host;          /* owner: inode, block_device */(指向拥有该对象的索引节点的指针即指向相应文件或者是块设备的inode节点对象,可以通过该字段获取相应文件的inode节点对象。)
           struct radix_tree_root  page_tree;      /* radix tree of all pages */(所有页基树的根,即指向相应的基树。)
        spinlock_t              tree_lock;      /* and spinlock protecting it */
        unsigned int            i_mmap_writable;/* count VM_SHARED mappings */
        struct prio_tree_root   i_mmap;         /* tree of private and shared mappings */
        struct list_head        i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
        spinlock_t              i_mmap_lock;    /* protect tree, count, list */
        unsigned int            truncate_count; /* Cover race condition with truncate */
        unsigned long           nrpages;        /* number of total pages */(如一个文件或块设备中一般都拥有许多页,该字段就是address_space对象所对应的所有者所拥有的页的数量。即这个可以反映所有者的大小。)
        pgoff_t                 writeback_index;/* writeback starts here */
        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 */
        struct address_space    *assoc_mapping; /* ditto */
};


    基树:

    linux支持大到几个TB的文件。访问大文件时,也高速缓存中可能充满太多的文件也,以至于顺序扫描这些页要消耗大量的时间,为了实现页高速缓存的高效查找,linxu2.6采用了大量的搜索树,其中每个address_space对象对应一棵树。为了实现页高速缓存的高效查找而引人的一个数据结构。每个address_space对象对应于一颗基树。其中address_space对象中的字段page_tree就是指向相应基树的根。

 struct radix_tree_root{

unsigned int height;   //表示基树的高度,其中叶子层不算,根的下一层为第一层。对于32位的体系基树的最大层数为6层。因为每层有64个节点需要6位来标识,所以32/6=5.所以最多有5+1层。

int gfp_mask;

struct radix_tree_node *rnode;//指向树的第一层节点相应的数据结构。

};//该数据结构主要是用来描述基树的根节点的情况

struct radix_tree_node{

unsigned int count;//节点中非空指针的个数,一个节点对应有64个可以分配的元素。起初这个值就等于64,每当该节点分配了一个元素之后,count就会相应的减少。

void *slots[RADIX_TREE_MAP_SIZE];//指向的是页描述符的指针。(这是一个void型的指针,即既可以指向下层的叶子节点页描述符也可以指向下层的基树节点。)

unsigned long tags[RADIX_TREE_TAGS][RADIX_TREE_TAG_LONGS];
};//该结构描述的是基树中的各个节点的情况

总结:

inode节点对象、address_space以及基树和页描述之间的相互联系
     
  (1)一个inode节点对象对应一个address_space对象。其中inode节点对象的i_mapping和i_data字段指向相应的 address_space对象,而address_space对象的host字段指向对应的inode节点对象。
        (2)每个address_space对象对应一颗基树。他们之间的联系是通过address_space对象中的page_tree字段指向该address_space对象对应的基树。
        (3)一般情况下一个inode节点对象对应的文件或者是块设备都会包含多个页面的内容,所以一个inode对象对应多个page描述符。同一个文件拥有的所有page描述符都可以再该文件对应的基树中找到。
它们之间的关系可以用如下的图来简单的进行描述:

页高速缓存和页回写