Chinaunix首页 | 论坛 | 博客
  • 博客访问: 325011
  • 博文数量: 100
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 665
  • 用 户 组: 普通用户
  • 注册时间: 2015-02-02 12:43
文章分类

全部博文(100)

文章存档

2015年(100)

我的朋友

分类: LINUX

2015-08-13 13:02:55

Linux的预读架构如图所示,Linux内核会将它最近访问过的文件页面缓存在内存中一段时间,这个文件缓存被称为pagecache。如图1所示。一般的read()操作发生在应用程序提供的缓冲区与pagecache之间。而预读算法则负责填充这个pagecache。应用程序的读缓存一般都比较小,比如文件拷贝命令cp的读写粒度就是4KB;内核的预读算法则会以它认为更合适的大小进行预读 I/O,比如16-128KB

 

 

1 pagecache为中心的读和预读

我们分析如下情况,用户程序调用read函数对设备进行读操作,文件系统会调用相应的方法:generic_file_aio_read() -->do_generic_file_read(),在do_generic_file_read()函数中,使用find_get_page()cache中查找该page。如果没有找到,则调用page_cache_sync_readahead进行适当的预读,预读之后一般page就可以找到了。

页面标志位:PG_readahead,它是“请作异步预读”的一个提示。在每次进行新预读时,算法都会选择其中的一个新页面并标记之。预读规则为:

当读到缺失页面(missing page),进行同步预读;

当读到预读页面(PG_readahead page),进行异步预读

在读取方向上剩余页数为async_size时,进行异步预读。

 

我们来看以下例子:

 

当进程打开一个文件,想要读取第一页,而该页不在缓存中,这时内核采用同步预读page_cache_sync_readahead()读取了若干页(图中为4页),并将预读窗口中的第二页标记为PG_readahead。

进程现在继续读取接下来的各页,当读取到有PG_readahead标志的第二页时,内核触发一个异步预读操作page_cache_async_readahead(),在后台读取若干页。因为这时候缓存中还有两页可用,不必匆忙读取,所以不需要一个同步预读操作。

现在将重复这种做法。由于异步预读又将预读窗口中的一页标记为PG_readahead,在进程遇到该页时,又进行一次异步预读,以此类推。

以上说到预读若干页,那么预读窗口的最优长度到底是几页呢?预读粒度太小的话,达不到应有的性能提升效果;预读太多,又有可能载入太多程序不需要的页面,造成资源浪费。为此,Linux内核设置了一个file_ra_state数据结构,关联到每个file实例,记录每个文件的预读状态。

 

struct file_ra_state{

pgoff_tstart;                        /*预读的起始位置 */

unsigned intsize;                /*预读的页数,即预读窗口长度 */

unsigned intasync_size;        /* 阈值,在读取方向上剩余页数为该值时,启动异步预读*/

 

unsigned intra_pages;                /*预读窗口最大长度 */

intmmap_miss;                        /*Cache miss stat for mmap accesses */

loff_tprev_pos;                /*前一次读取时,最后的访问位置*/

};

用于实现预读的函数之间的关联如下图所示:

 

ondemand_readahead()函数在确定预读窗口长度之后,调用ra_submit()ra_submit()是对函数__do_page_cache_readahead()的封装,于是预读的技术性问题是由后者完成的。

get_init_ra_size()get_next_ra_size()是辅助ondemand_readahead()判断需要读入多少当前不需要的页的辅助函数。其中get_init_ra_size()根据进程请求的页数为一个文件确定最初的预读窗口长度,而get_next_ra_size()为后来的读取计算长度,即此时已经有一个先前的预读窗口存在,函数根据前一个预读窗口长度计算新的预读窗口长度。两个函数都会确保预读窗口长度不超过特定于文件的上限值。该上限值通常设置为VM_MAX_READAHEAD * 1024 /PAGE_CACHE_SIZE,在页长度为4K的系统上,相当于32页。两个函数的结果如下图所示:

 

 

不难看出,初始值一般是最近的2的整数幂的值,而新的窗口长度是原长度的两倍。

 

何时构建一个新的预读窗口?何时是顺序读取?ondemand_readahead()函数进行如下判断:

1)当前偏移量在前一个预读窗口末尾,或在触发阈值的点上时,内核识别为顺序读取,使用get_next_ra_size()为后来的读取计算长度;

2)若是随机读,则直接调用__do_page_cache_readahead()进行读取,而不破坏当前的预读状态;

3)若是遇到预读标志,则使用get_next_ra_size()来计算新的预读窗口长度;

4)如果以上情况都不是,内核则判定为对文件的第一次读取,这时用get_init_ra_size()建立一个新的预读窗口。

阅读(3725) | 评论(0) | 转发(0) |
0

上一篇:do_exit()函数

下一篇:用户ID

给主人留下些什么吧!~~