页高速缓存和页回写

  • 1 页高速缓存
  • 2 基树
  • 3 缓冲区高速缓存
  • 4 pdflush后台例程
    • 膝上型电脑模式
    • bdflush和kupdated
    • 避免拥塞的方法:使用多线程

页高速缓存(cache)是Linux内核实现的一种主要磁盘缓存,通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变成对物理内存的访问,主要用来减少对磁盘的I/O操作。它由RAM中的物理页组成,缓存中每一项对应着磁盘中的多个块,每当内核开始执行一个页I/O操作时,首先会检查需要的数据是否在高速缓存中,如果在,那么内核就直接使用高速缓存中的数据,从而避免访问磁盘。

高速缓存的价值在于两个方面:第一,访问磁盘的速度要远远低于访问内存的速度,因为,在内存访问数据比从磁盘访问速度更快。第二,数据一旦被访问,就很有可能在短期内再次被访问到。如果在第一次访问数据时缓存它,那就极有可能在短期内再次被高速缓存命中。

1 页高速缓存

页高速缓存缓存的是页。Linux页高速缓存的目标是缓存任何基于页的对象,这包含各种类型的文件和各种类型的内存映射。为了满足普遍性的要求,Linux页高速缓存使用address_space结构体描述页高速缓存中的页面。该结构体定义在文件linux/fs.h中。

struct address_space {struct inode       *host;      /* owner: inode, block_device */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 */atomic_t     truncate_count; /* Cover race condition with truncate */unsigned long       nrpages;    /* number of total pages */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 */
} __attribute__((aligned(sizeof(long))));

i_mmap字段是一个优先搜索树,它的搜索范围包含了在address_space中所有共享的与私有的映射页面。优先搜索树是一种将堆与radix树结合的快速检索树。address space空间大小由nrpages字段描述,表示共有多少页。
address_space结构往往会和某些内核对象关联,通常情况下会与一个索引节点关联,这时host域就会指向该索引节点,如果关联对象不是一个索引节点的话,host就被设置为NULL。
a_ops域指向地址空间对象中的操作函数表,操作函数表定义在linux/fs.h文件中,由address_space_operations结构体表示:

struct address_space_operations {int (*writepage)(struct page *page, struct writeback_control *wbc);int (*readpage)(struct file *, struct page *);int (*sync_page)(struct page *);/* Write back some dirty pages from this mapping. */int (*writepages)(struct address_space *, struct writeback_control *);/* Set a page dirty */int (*set_page_dirty)(struct page *page);int (*readpages)(struct file *filp, struct address_space *mapping,struct list_head *pages, unsigned nr_pages);/** ext3 requires that a successful prepare_write() call be followed* by a commit_write() call - they must be balanced*/int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);int (*commit_write)(struct file *, struct page *, unsigned, unsigned);/* Unfortunately this kludge is needed for FIBMAP. Don't use it */sector_t (*bmap)(struct address_space *, sector_t);int (*invalidatepage) (struct page *, unsigned long);int (*releasepage) (struct page *, int);ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,loff_t offset, unsigned long nr_segs);
};

2 基树

每个address_space对象都有唯一的基树(radix tree),它保存在page_tree结构体中。基树是一个二叉树,只要指定了文件偏移量,就可以在基树中迅速检索到希望的数据,页高速缓存的搜索函数find_get_page()要调用函数radix_tree_loopup(),该函数会在指定基树中搜索指定页面。

基树核心代码的通用形式可以在文件lib/radix-tree.c中找到,要想使用基树,需要包含头文件linux/radix_tree.h

3 缓冲区高速缓存

现在的Linux系统中已经不再有独立的缓冲区高速缓存了。但在2.2版本的内核中,存在两个独立的磁盘缓存:页高速缓存和缓冲区高速缓存。前者缓存页,后者缓存缓冲。两种缓存并不同一:一个磁盘块可以在两种缓存中同时存在,因此需要对缓存中的同一拷贝进行很麻烦的同步操作。
在2.4版本的内核开始,统一了这两种缓存,现在Linux只有唯一的页高速缓存。

4 pdflush后台例程

由于页高速缓存的缓存作用,写操作实际上会被延迟,当页高速缓存中的数据比磁盘存储的数据更新时,这时候页高速缓存中的数据被称为脏数据,脏数据所在的页被称为脏页,这些脏页最终必须被写回磁盘。在以下两种情况发送时,脏页被写回磁盘:

  • 当空闲内存低于一个特定的阈值时,内核必须将脏页写回磁盘,以便释放内存。
  • 当脏页在内存中驻留时间超过一个特定的阈值时,内核必须将超时的脏页写回磁盘,以确保脏页不会无限期地驻留在内存中。

上面两种工作的目的完全不同。在老内核中,这是由两个独立的内核线程分别完成(bdflush和kupdated两个线程)的,但是在2.6内核中,由一群内核线程,pdflush后台回写线程同一执行两种工作。首先,pdflush线程在系统中的空闲内存低于一个特定的阈值时,将脏页刷新回磁盘。此目的是在可用物理内存过低时,释放脏页以重新获得内存。特定的内存阈值可以通过dirty_backgriud_radio sysctl系统调用设置。当空闲内存比阈值dirty_background_ratio低时,内核便会调用wakeup_bdflush()唤醒一个pdflush线程。随后pdflush线程进一步调用函数background_writeout()开始将脏页回写磁盘。函数background_writeout()需要一个长整型参数,该参数指定试图写回的页面数目。函数background_writeout会连续写出数据,直到满足以下两个条件:

  • 已经有指定的最小数目的页被写出到磁盘
  • 空闲内存数已经回升,超过了阈值dirty_background_ratio

上述条件确保了pdflush操作可以减轻系统中内存不足的压力,回写操作不会在达到这两个条件前停止,除非pdflush写回了所有的脏页,没有剩下的脏页可以写回了。

为了满足第二个目标,pdflush后台例程会被周期性唤醒,将那些在内存驻留时间过长的脏页写出,确保内存中不会有长期存在的脏页。在系统启动时,内核初始化一个定时器,让它周期地唤醒pdflush线程,随后使其运行函数wb_kupdate()。该函数将把所有驻留时间超过百分之drity_expire_centisece秒的脏页写回。然后定时器将再次被初始化为百分之drity_expire_centisece秒后唤醒pdflush线程。

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

膝上型电脑模式

膝上型电脑模式是一种特殊的页回写策略,该策略主要目的是将磁盘转动的机械行为最小化,允许磁盘尽可能长时间停滞,以此延长电池供电时间。该模式可通过/proc/sys/vm/laptop_mode文件进行配置,通常,该文件内容为0,膝上电脑模式关闭,如果需要启动,则向配置文件写入1.

bdflush和kupdated

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

bdflush和pdflush之间主要有两个区别。第一个是系统中只有一个bdflush线程,而pdflush线程的数目可以动态改变;第二个是bdflush线程基于缓冲,它将脏缓冲写回磁盘,pdflush基于页,它将整个脏页写回磁盘。

因为只有在内存过低和缓冲数量过大时,bdflush才刷新缓冲,所以kupdate线程被引入,以便周期性地写回脏页。

bdflush和kupdate内核线程现在完全被pdflush线程取代了。

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

bdflush仅仅只有一个线程,因此很有可能在页回写任务很重时,造成阻塞,这是因为单一的线程很可能堵塞在某个设备的已阻塞请求队列上,而其他设备的请求队列却没法得到处理。

2.6内核通过使用多个pdflush线程来解决上述问题。每个线程可以相互独立地将脏页刷新回磁盘,而且不同的pdflush线程处理不同的设备队列。

通过一个简单的算法,pdflush线程的数目可以根据系统的运行时间进行调整,如果所有已存在的pdflush线程都已经持续工作1秒以上,内核就会创建一个新的pdflush线程。线程数量最多不能超过MAX_PDFLUSH_THREADS,默认值是8.如果一个pdflush线程睡眠超过1秒,内核就会终止该线程,线程的数量最少不得小于MIN_PDFLUSH_THREADS,默认值是2.pdflush线程数量取决于页回写的数量和阻塞情况,动态调整。

这种方式看起来很理想,但是如果每一个pdflush线程都挂起在同一个阻塞的队列上会怎么样?在这种情况下,多个pdflush线程的性能并不会比单个线程提高多少,反而会造成严重的内存浪费。为了克服这种负面影响,pdflush线程利用阻塞避免策略,它们会积极地试图写回那些不属于阻塞队列的页面。这样一来,pdflush通过分派回写工作,阻止多个线程在同一个忙设备纠缠。所以pdflush线程很忙,此时会有一个新的pdflush线程被创建,它们才是真正的繁忙。

Linux内核设计与实现---页高速缓存和页回写相关推荐

  1. Linux内核设计与实现(十)| 页高速缓存和页回写

    文章目录 页高速缓存和页回写 1.缓存手段 1.1 写缓存 1.2 缓存回收 2.Linux页高速缓存 2.1 address_space对象 2.2 address_space操作 2.3 基树 2 ...

  2. linux内核页高速缓存,《Linux内核设计与实现》读书笔记(十六)- 页高速缓存和页回写(示例代码)...

    主要内容: 缓存简介 页高速缓存 页回写 1. 缓存简介 在编程中,缓存是很常见也很有效的一种提高程序性能的机制. linux内核也不例外,为了提高I/O性能,也引入了缓存机制,即将一部分磁盘上的数据 ...

  3. 《Linux内核设计与实现》读书笔记(十六)- 页高速缓存和页回写

    主要内容: 缓存简介 页高速缓存 页回写 1. 缓存简介 在编程中,缓存是很常见也很有效的一种提高程序性能的机制. linux内核也不例外,为了提高I/O性能,也引入了缓存机制,即将一部分磁盘上的数据 ...

  4. 初探内核之《Linux内核设计与实现》笔记下

    定时器和时间管理 系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要. 主要内容: 系统时间 定时器 定时器相关 ...

  5. 《Linux内核设计与实现》读书笔记 - 目录 (完结)

    读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个<Linux内核设计与实现>读书笔记 的目录: < ...

  6. 读《Linux内核设计与实现》我想到了这些书

          从题目中可以看到,这篇文章是以我读<Linux内核设计与实现>而想到的其他我读过的书,所以,这篇文章的主要支撑点是<Linux内核>.       开始读这本书已经 ...

  7. 读 Linux内核设计与实现 我想到了这些书

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴!     ...

  8. Linux内核设计与实现学习笔记目录

    **注:**这是别人的笔记,我只是把目录抄过来 <Linux内核设计与实现学习笔记> 1.<Linux内核设计与实现>读书笔记(一)-内核简介 2.<Linux内核设计与 ...

  9. 《Linux内核设计与实现》读书笔记(十二)- 内存管理

    内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决). 所有内核的内存管理必须要简洁而且高效. 主要内容: 内存的管理单元 获取内存的方法 ...

最新文章

  1. Microbiome:南土所褚海燕组揭示长期施肥抑制根际微生物固氮的作用机制
  2. 【Android 文件管理】分区存储 ( 分区存储机制 和 文件索引数据 )
  3. MySQL的优化与执行
  4. How to install python packages
  5. Java进阶04 RTTI
  6. linux上dig命令,Linux dig命令(示例代码)
  7. python-去重的三种方式-成员判断-索引判断-集合
  8. 黑莓Torch 9800,了无新意落后主流机型
  9. 中国有什么拿得出手的开源软件产品?|原力计划
  10. 机器学习(Machine Learning)
  11. nlp基础—11.条件随机场模型(CRF)模型补充
  12. 第7章 EL表达式和JSTL
  13. 求最长公共子串,简单易懂
  14. verilog语法进阶
  15. Endnote导入中文文献格式
  16. 付款码支付-微信和支付宝付款码类型标识
  17. JAVA单车管理系统计算机毕业设计Mybatis+系统+数据库+调试部署
  18. python中else是指什么意思_python中elif什么意思?
  19. JS高级程序设计精简版(第五章:引用类型)附思维导图
  20. Appium-Long Press(长按)

热门文章

  1. js对象数组(JSON) 根据某个共同字段分组
  2. 我的前端工具集(五)提示工具之模态窗提示
  3. 微信浏览器返回刷新,监听微信浏览器返回事件,网页防复制,移动端禁止图片长按和vivo手机点击img标签放大图片
  4. 关于盒模型的一点总结
  5. CentOS6.4 Install FTP
  6. 【RTOS】基于V7开发板的uCOS-III,uCOS-II,RTX4,RTX5,FreeRTOS原版和带CMSIS-RTOS V2封装层版全部集齐...
  7. 小程序 封装table组件
  8. java面向对象中的抽象,类与对象
  9. 微信小程序常见问题集合(长期更新)
  10. ellen 纽奥良大学演讲