LINUX内核设计与实现之页高速缓存和页回写

页高速缓存(cache):

LINUX内核实现的一种主要磁盘缓存.它主要用来减少对磁盘的I/O操作.具体而言,通过把磁盘的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问.

页高速缓存的价值:

磁盘高速缓存的价值在两方面:

一、访问磁盘的速度远低于访问内存的速度;

二、数据一旦被访问,就很有可能在短期内再次被访问,这些数据会被暂存在高速缓存中,实现快速命中.

页高速缓存的实现理论:

页高速缓存由RAM中的物理页组成.缓存中每一页对应着磁盘中的多个块.当内核开始执行一个页I/O操作时(磁盘操作),首先会检查需要的数据是否在高速缓存中,如果在,那么内核直接使用高速缓存的数据,从而避免访问磁盘.

举个例子:

使用文本编辑器打开一个源程序时,该文件的数据就被调入内存.编辑该文件的过程中,越来越多的数据相继被调入内存页.最后,当你编译它的时候,内核可以直接使用页高速缓存中的页,而不需要重新从磁盘读取该文件了.

因此,页高速缓存的意义在于:

减少对磁盘的访问,实现高速访问数据.

 

15.1 页高速缓存

LINUX页高速缓存的目标是缓存任何基于页(磁盘的物理页)对象,这包含各种类型的文件和类各种类型的内存映射.

LINUX页高速缓存使用address_space结构体描述符页高速缓存中的页面.该结构定义在文件<linux/fs.h>:

Address_space结构往往和某些内核对象关联.通常情况下,会与一个索引节点(inode)关联.此时host域便宜指向该索引节点.a_ops域指向地址空间对象中的操作函数表.address_space_operations结构体表示:

这里面的readpage()writepage()两个方法最为重要.

读操作步骤:

一、一个address_space对象和一个偏移量会被传递给readpage()方法,这两个参数用来在页高速缓存中搜索需要的数据:

Page = find_get_page(mapping,index);

Mapping是指定的地址空间;

Index是文件中指定位置.

如果搜索的页没在高速缓存中,内核将分配一个新页面,然后将其加入到页高速缓存中.示意代码如下:

Struct page *cached_page;

Int error;

 

Cached_page = page_cache_alloc_cold(mapping);

If(!cached_page)

/*分配内存出错*/

Error = add_to_page_cache_lru(cached_page,mapping,index,GFP_KERNEL);

/*页面被加入到页面高速缓存进出错*/

二、需要的数据从磁盘被读入,再被加入页高速缓存,然后返回给用户空间:

Error = mapping->a_ops->readpage(file,page);

 

写操作步骤:

对文件映射而言:

当页被修改了,VM仅需要调用SetPageDirty(page),然后在晚些时候通过writepage()方法把页写出.

对特定文件而言:

包含下面几个步骤:

Page = __grab_cache_page(mapping,index,&cached_page,&lrc_pvec);

Status = a_ops->prepare_write(file,page,offset,offset+bytes);

Page_fault = filemap_copy_frome_user(page,offset,buf,bytes);

Status = a_ops->commit_write(file,page,offset,offset+bytes);

上述几个步骤文字简析如下:

1.在页高速缓存中搜索需要的页,如果需要的页不在高速缓存中,那么内核在高速缓存中新分配一空闲项;

2.调用prepare_write()创建一个写请求;

3.把数据从用户空间拷贝到内核缓冲;

4.调用commit_write()函数将数据写入磁盘.

小结:

所有的页I/O操作(读和写)都执行上面这些步骤.这就保证了所有的页I/O操作必然通过页高速缓存进行的.对于读操作,如果在页高速缓存中未搜索到需要的页,则内核将从磁盘读入需要的页,然后将该页加到高速缓存中;对于写操作,页高速缓存更像一个存储平台,所有要被写出的页都要被加入页高速缓存中.

 

15.2 基树

因为在任何页I/O操作前内核都要检查页是否已经在页高速缓存中了,所以这种频繁进行的检查必须迅速、高效,否则搜索和检查页高速缓存的开销可能抵消页高速缓存的优势.LINUX实现迅速、高效的检查通过页高速缓存的两个参数--address_space对象和一个偏移量一一进行搜索.每个address_space对象都有唯一的基树(radix tree),它保存在page_tree结构体中.基树是一个二叉树,只要指定了文件偏移量,就可以在基树中迅速检索到希望的数据.换句话而言,基树是实现内核在页I/O操作对页高速缓存的一种优化的信息源.

 

15.3 缓冲区高速缓存

缓冲区高速缓存:

早期的内核存在两个独立的磁盘缓存:页高速缓存和缓冲区高速缓存.页高速缓存和缓冲区高速缓存.前者缓存页,后者缓存缓冲.一个磁盘块可以在两种缓存中同时存在,因此需要对两个缓存中的同一拷贝进行很麻烦的同步操作--还要消耗额外的内存.

现代的内核统一了这两种缓存,只有唯一的磁盘缓存--页高速缓存.而缓冲,现代的内核只用来作页映射块,来表示磁盘块的.

 

15.4 pdflush后台例程

Pdflush的意义:

页高速缓存的缓存作用,写操作实际会被延迟.当页高速缓存中的数据比后台存储的数据要新,.

Pdflush线程的目标:

.当空闲内存低于某特定阈值时,内核必须将脏页写回磁盘,并释放内存;

.当脏页在内存中那么该数据就被称为脏数据,这些数据必须回写到磁盘.pdflush线程便是扮演这个角色驻留时间超过一个特定的阈值时,内核必须将超时的脏页回写磁盘,确保脏页不会无限期驻留在内存中.

实现上述目标的流程:

第一个目标:当空闲内存低于某特定阈值时,内核必须将脏页写回磁盘,并释放内存.pdflush线程的目的在于在可用物理内存过低时,释放脏页以重新获得内存.空闲阈值内核用dirty_background_radio变量表示,可通过sysctl系统调用设置.当空闲内存比dirty_background_radio低时,内核便会调用wakeup_bdflush()唤醒一个pdflush线程,pdflush线程进一步调用函数background_writeout()开始将脏页写回磁盘.

第二个目标:定时回写脏页到磁盘.pdflush后台例程会被周期性唤醒(和空闲内存是否过低无关),将那些在内存中驻留时间过长的脏页写出,确保内存不会有长期存在的脏页.系统启动时,内核初始化一个定时器,周期性唤醒pdflush线程,pdflush线程调用wb_kupdate()函数,把所有驻留时间超过百分之dirty_expire_centisecs秒的脏页回写.

管理员可以在/proc/sys/vm设置回写相关参数,也可以通过sysctl系统调用设置它们.相关参数如下:

Pdflush线程的实现代码在文件mm/pdflush.c,回写机制的实现代码在文件mm/page-writeback.cfs/fs-writeback.c.

15.4.1 膝上型电脑模式

上述在的主要目标是将脏数据回写到磁盘中去,那么,回写的策略也有几种.膝上型电脑模式便是其中一种.

膝上型电脑模式策略主要意图是将硬盘转动的机械行为最小化,允许硬盘尽可能长时间停滞,以延长电池供电时间.该模式通过/proc/sys/vm/laptop_mode文件进行配置,往此文件写入1即可激活,默认为0.

15.4.2 bdflushkupdated

2.6内核版本前,pdflush线程的工作分别由bdflushkupdated两个线程共同完成.当可用内存过低时,bdflush内核线程在后台执行脏页回写操作.pdflush一样,它也有一组阈值参数,当系统中空闲内存消耗到特定阈值下,bdflush线程被wakeup_bdflush函数唤醒.bdflushpdflush的区别:

一、系统只有一个bdflush后台线程,pdflush线程的数目却可以动态变化;

二、Bdflush线程基于缓冲,它将脏缓冲回写到磁盘,pdflush基于页面,它将整个脏页写回磁盘.当然,缓冲也是存在于页面的,实际的I/O操作对象是整页,而不是块.页在内存中是更普遍和普通的概念,管理页比管理块要简单.

Kupdated线程其实是完成pdflush线程中的第二个目标--定时更新脏数据到磁盘.

15.4.3 避免拥塞的方法:使用多线程

上述中,pdflush线程数目是动态变化的.先分析下面这个场景:

早期的内核只有bdflush这一内核线程去管理内核空闲内存低于阈值时对脏数据回写到磁盘并释放内存.如果页回写任务比较重时,很容易造成拥塞.因此需要多个回写线程并发执行来解决这个瓶颈.

2.6内核通过使用多个pdflush线程来解决上述问题,每个pdflush线程可以相互独立地将脏页刷新到磁盘,不同的pdflush线程处理不同的设备队列.实现细则如下:

Pdflush线程数目可以根据系统的运行时间调整.如果所有已存在的pdflush线程持续工作1秒以上,内核就会创建一个新的pdflush线程.线程数量最多不超过MAX_PDFLUSH_THREADS(默认值是8).如果一个pdflush线程睡眠超过1,内核就终止该线程.线程数量不得少于MIN_PDFLUSH_THREADS(默认值是2).pdflush线程的数量取决于页回写和拥塞情况来动态调整.如果所有存在的pdflush线程都忙着回写数据,那么一个新的线程会被创建.如果拥塞情况消除,pdflush线程的数量会自动减少,以便节约内存.为了避免每一个线程都挂起在同一个拥塞队列上,这种情况下多个pdflush性能并不会比单个线程提高多少,反而会造成严重的内存浪费.因此,pdflush线程会积极试图回写那些不属于拥塞队列的页面.有效防止了多个线程在同一忙设备上纠缠.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值