linux内核设计与实现 - 16 页高速缓存和页写回

第16章 页高速缓存和页写回

高速缓存存在的原因:1. 磁盘和内存速度差好几个数量级;2. 数据局部性原理;

16.1 缓存手段

页高速缓存由内存中的物理页面组成,其内容对应磁盘上的物理块。

  1. 写缓存
    不缓存
    write-through
    write-back:将脏页标记为dirty,并加入脏页链表,然后由一个写回进程周期性将链表中页写回到磁盘

  2. 缓存回收
    缓存回收策略涉及缓存中数据如何清除。
    (1). 最近最少使用LRU
    回收最老时间戳的页面
    (2). 双链策略 LRU/n
    两个LRU,一个活跃链和非活跃链。活页缓存不会被换出,且两个链表需要维持平衡。

16.2 linux页高速缓存

16.2.1 address_space对象

在页高速缓存中的页可能包含了多个不连续的物理磁盘块。该结构是vm_area_struct的物理地址对等体,即文件可以有多个虚拟地址,但是只能在物理内存有一份。
在这里插入图片描述
i_mmap字段是一个优先搜索树,包含在address_space中所有共享的与私有的映射页面。
host:指向索引节点,如果关联对象不是一个索引,则host为null。

16.2.2 address_space操作

a_ops指向地址空间的操作函数表,每个后备存储都通过自己的address_space_operation描述自己如何与页高速缓存交互。
在这里插入图片描述
1. 页面读操作流程
(1). linux内核试图在页高速缓存中找到需要的数据,find_get_page(mapping, index)负责
(2). 若没有找到,page_cache_alloc_cold(mapping), add_to_page_cache_lru(page, mapping, index)内核分配一个新页面,然后将之前搜索的页加入高速缓存中。
(3). 需要的数据从磁盘被读入,再被加入页高速缓存. mapping->a_ops->readpage(file, page)
2. 页面写流程
当页被写了,VM仅需要调用SetPageDirty(page),即可,内核晚些时候会通过writepage()方法写出。
特定文件的写操作路径包括:
(1). page = __grab_cache_page(mapping,index,&cached_page,&lru_prec);在页高速缓冲中搜索需要的页,不在则分配。
(2). status = a_ops->prepare_write(file,page,offset,offset+bytes);创建一个写请求
(3). page_fault = filemap_copy_from_user(page, offset, buf, bytes);数据从用户空间拷贝到内核
(4). status = a_ops->commit_write(file, page, offset, offset + bytes);数据写入磁盘

16.2.3 基树

因为在任何页I/O操作前内核需要检查页是否已经在页高速缓存中,因此这种频繁的查找必须快。

页高速缓存通过address_space和一个偏移量进行搜索,每个address_space对象都有唯一的基树,页高速缓存的搜索函数find_get_page()要调用函数radix_tree_lookup()进行查找。

  1. 以前的页散列表
    以前是通过维护一个系统中所有页的全局散列表进行搜索。但有四个问题:
    (1). 由于单个全局锁保护散列表,锁争用严重
    (2). 散列表包含所有高速缓存的页,但搜索只需要和当前文件相关,因此搜索空间更大
    (3). 如果散列搜索失败,检索必须遍历指定散列键值对应的整个链表,慢
    (4). 散列表消耗更多内存。

16.3 缓冲区高速缓存

缓冲区高速缓存:块I/O操作通过缓存磁盘块减少块I/O操作。以前的内核中有两个独立的磁盘缓存:页高速缓存和缓冲区高速缓存,前者缓存页面,后者缓存缓冲区。(对于linux来说,2.4之前的内核中有两种缓存,一种是vfs的页高速缓存,另外一种是缓冲区高速缓存,实际上缓冲区对应于磁盘块,就是磁盘块在内存中的表示罢了,其中的数据也还是文件中的数据,只不过页高速缓存是按照页面管理的,而缓冲区高速缓存是按照块来管理的,两个缓存在数据本身上有一定的重合,这就造成了冗余
今天我们只有一个磁盘缓存,即页高速缓存。

16.4 flusher线程

脏页被写回磁盘的3种情况:

  1. 当空闲内存低于一个特定的阈值时
  2. 当脏页在内存中驻留时间超过特定阈值时
  3. 当用户进程调用sync()和fsync()系统调用时

由一群内核线程(flusher线程)执行这3种工作。

  1. 空闲内存比阈值dirty_background_ratio低时,内核调用flusher_threads()唤醒一个或多个线程,随后flusher线程调用bdi_writeback_all()开始将脏页写回,直到有指定的最小数目的页被写出到磁盘或达到阈值。
  2. 为达到第二个条件,flusher线程会被周期性唤醒。 内核初始化一个定时器,周期唤醒flusher线程,随后使其运行wb_writeback()刷回。

16.4.1 膝上型计算机模式

flusher会找准磁盘运转时机,将所有的物理磁盘I/O、刷新脏缓冲通通写回磁盘,不会专门为了写磁盘而主动激活磁盘运行。将两个阈值设置得更大。

16.4.2 历史上的bdflush、kupdated和pdflush

2.6版本之前,flusher线程工作由bdflush和kupdated两个线程完成。
bdflush:
bdflush和当前的flusher线程的两个主要区别:1. 系统中只有一个bdflush线程,而flusher线程数目是根据磁盘数量变化的问题,每个设备最多只有一个线程??)。 2. bdflush基于缓冲,flusher线程基于页面。
kupdated:
周期性写回脏页

pdflush:
集合bdflush和kupdated,pdflush与今天的flusher线程类似,主要区别在于pdflush线程数据是动态的,默认是2个到8个,具体取决于系统I/O的负载。pdflush线程与任何任务都无关,他们是面向系统所有磁盘的全局任务,这样实现简单,但导致pdflush线程很容易在拥塞的磁盘上绊住。采用每个磁盘一个刷新线程可以使得I/O操作同步执行,简化了拥塞逻辑,提高性能。

fluser线程:
在2.6.32后取代pdflush。区别:针对每个磁盘独立运行写回操作是其和pdflush的主要区别

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

bdflush的缺点:单线程,可能堵塞在某个设备的已拥塞请求队列上,而其他设备没法处理。因为磁盘的吞吐量有限。
pdflush缺点:虽然多线程,但是pdflush线程可能在同一个拥塞的队列上挂起,因此pdflush采用了拥塞回避策略,会主动尝试从哪些没有拥塞的队列写回页。
flusher线程:每个给定线程从每个给定设备的脏页链表收集数据,并写回对应磁盘。每个磁盘对应一个线程。提高I/O的公平性,降低饥饿风险。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值