当前位置: 首页 > Linux kernrl > 正文

Linux kernel -页高速缓存和页回写 初探

Linux kernrl 探秘 页高速缓存和页回写 初探

前言

这是一个新的文章序列,名字就叫“Linux Kernel 探秘”  ,这里的文章分为两个部分,初探和再探。初探系列主要就是写写原理,不考虑实现。“再探”系列才考虑实现方式,毕竟我们的目的是将内核的优秀思想放到实际的使用中去。

不所出意外这就是内核系列本学期的最后一篇了,剩下的两周我需要准备考试了。不过没事不到一个月就要放假了,那个时候我会继续更新我与内核的故事。

页高速缓存

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

页回写

将页高速缓存中的变更数据刷新回磁盘的操作。

缓存手段

页高速缓存是由内存中的物理页面组成的,其内容对应磁盘上的物理块。页高速缓存能动态调整–它可以通过占用空闲内存以扩张大小,也可以自我收缩以缓解内存使用压力。

我们称整被缓存的设备为后备存储,因为缓存背后的磁盘才是所有缓存数据的归属。

系统在需要某些数据的时候首先会查找内存条上的高速页缓存,如果存在直接从内存读取,否则就调用IO操作从硬盘读取。

读取的多少需要看是谁先被读到,一般只缓存需要的部分。

写缓存

缓存不只是为了读取更快速,在写操作的时候也需要。

在写的时候一般有如下的几种方式,有些也已经不再使用了。

总共三种策略。

第一种:不缓存策略

这种策略是这样的:在需要发生写操作的时候,直接跳过缓存,直接写硬盘然后使高速缓存中的数据失效。然后从磁盘中重新获取数据,这种方法显然不好,所以也基本不用。

第二种:写透缓存

在发生写操作后立即同步更新缓存和硬盘的数据,这种方式很有利于保持缓存数据时刻和后备存储保持同步,所以缓存不需要失效,实现也简单。

第三种:回写

这也是Linux所使用的机制,使用这种策略,当发生写操作时,将被写的页表标记为“脏”的然后将它加入不活跃链表,然后由专门的线程周期性的将数据写回到磁盘上,这种方式可以减少很多额外的操作,所以使用它,但是代价是实现复杂度确实提高了很多。

 

缓存回收

当执行的程序很多时,我们就需要进行“清理”将一些不需要的页交换出去。

这里也有几种方式:

最近最少使用:

最少使用算法(LRU) 它跟踪所有的缓存页,根据时间它在内存中存在的时间长久作为主要的判别依据,每次尽量将存在内存时间最多的页换出,但是缺点是对于只访问了一次的文件实在效果不好。

双链表策略:

内核维护两条链表:活跃链表和非活跃链表。处于活跃链表上的被认为是热的并且不会被换出,而在非活跃链表上的页面则是可以被换出的。

当然这两个链表也是需要平衡的,如果活跃链表变的过多而超过了非活跃链表,那么活跃链表的头页面将会被重新移回到非活跃链表中,以便能再被回收。

Linux 页面高速缓存

内核使用address_space 来统计物理页,一个文件可以有多个虚拟地址但是只能有一个物理地址就是我们之前说的,在高速缓存的中只能有一个结构体标识。

struct address_space {
struct inode *host; /* owner: inode, block_device */

/*拥有的节点*/
struct radix_tree_root page_tree; /* radix tree of all pages */

/*包含所有页的radix 树*/
spinlock_t tree_lock; /* and lock protecting it */

/*保护树的锁*/
atomic_t i_mmap_writable;/* count VM_SHARED mappings */

/*VM_SHARED 计数*/
struct rb_root i_mmap; /* tree of private and shared mappings */

/*私有映射位图*/
struct rw_semaphore i_mmap_rwsem; /* protect tree, count, list *
/* Protected by tree_lock together with the radix tree */
unsigned long nrpages; /* number of total pages */

/*页总数*/
unsigned long nrshadows; /* number of shadow entries */
pgoff_t writeback_index;/* writeback starts here */

/*回写偏移*/
const struct address_space_operations *a_ops; /* methods */

/*操作表*/
unsigned long flags; /* error bits/gfp mask */

/*预读信息*/
spinlock_t private_lock; /* for use by the address_space */

/*保护这个结构体的自旋锁*/
struct list_head private_list; /* ditto */

/*私有链表*/
void *private_data; /* ditto */

/*私有数据*/
} __attribute__((aligned(sizeof(long))));

 

通常情况下这个结构体会和一个内核对象关联,一般是inode 如果是别的host 设置为NULL:

这里还有两个重要的数据结构,基数和页散列表。

 

flusher 线程

这线程就是在以下的情况下将缓存写入磁盘中。

1.空闲内存低于一个特定的阀值。

2.当脏页面在内存中存在的时间过长时。

3.当用户使用sync fsync 的时候,内核会按照要求执行回写操作。

 

当满足以下条件的时候回写停止

1。已经有指定的最小数目的页被写出到磁盘

2。空闲内存数回升,超过了阀值

 

 

本文固定链接: http://zmrlinux.com/2015/12/16/linux-kernel-%e9%a1%b5%e9%ab%98%e9%80%9f%e7%bc%93%e5%ad%98%e5%92%8c%e9%a1%b5%e5%9b%9e%e5%86%99-%e5%88%9d%e6%8e%a2/ | Kernel & Me

该日志由 root 于2015年12月16日发表在 Linux kernrl 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: Linux kernel -页高速缓存和页回写 初探 | Kernel & Me