高速缓存存在的原因:1. 磁盘和内存速度差好几个数量级;2. 数据局部性原理;
页高速缓存由内存中的物理页面组成,其内容对应磁盘上的物理块。
写缓存
不缓存
write-through
write-back:将脏页标记为dirty,并加入脏页链表,然后由一个写回进程周期性将链表中页写回到磁盘。
缓存回收
缓存回收策略涉及缓存中数据如何清除。
(1). 最近最少使用LRU
回收最老时间戳的页面
(2). 双链策略 LRU/n
两个LRU,一个活跃链和非活跃链。活页缓存不会被换出,且两个链表需要维持平衡。
在页高速缓存中的页可能包含了多个不连续的物理磁盘块。该结构是vm_area_struct的物理地址对等体,即文件可以有多个虚拟地址,但是只能在物理内存有一份。
i_mmap字段是一个优先搜索树,包含在address_space中所有共享的与私有的映射页面。
host:指向索引节点,如果关联对象不是一个索引,则host为null。
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);数据写入磁盘
因为在任何页I/O操作前内核需要检查页是否已经在页高速缓存中,因此这种频繁的查找必须快。
页高速缓存通过address_space和一个偏移量进行搜索,每个address_space对象都有唯一的基树,页高速缓存的搜索函数find_get_page()要调用函数radix_tree_lookup()进行查找。
缓冲区高速缓存:块I/O操作通过缓存磁盘块减少块I/O操作。以前的内核中有两个独立的磁盘缓存:页高速缓存和缓冲区高速缓存,前者缓存页面,后者缓存缓冲区。(对于linux来说,2.4之前的内核中有两种缓存,一种是vfs的页高速缓存,另外一种是缓冲区高速缓存,实际上缓冲区对应于磁盘块,就是磁盘块在内存中的表示罢了,其中的数据也还是文件中的数据,只不过页高速缓存是按照页面管理的,而缓冲区高速缓存是按照块来管理的,两个缓存在数据本身上有一定的重合,这就造成了冗余)
今天我们只有一个磁盘缓存,即页高速缓存。
脏页被写回磁盘的3种情况:
由一群内核线程(flusher线程)执行这3种工作。
flusher会找准磁盘运转时机,将所有的物理磁盘I/O、刷新脏缓冲通通写回磁盘,不会专门为了写磁盘而主动激活磁盘运行。将两个阈值设置得更大。
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的主要区别。
bdflush的缺点:单线程,可能堵塞在某个设备的已拥塞请求队列上,而其他设备没法处理。因为磁盘的吞吐量有限。
pdflush缺点:虽然多线程,但是pdflush线程可能在同一个拥塞的队列上挂起,因此pdflush采用了拥塞回避策略,会主动尝试从哪些没有拥塞的队列写回页。
flusher线程:每个给定线程从每个给定设备的脏页链表收集数据,并写回对应磁盘。每个磁盘对应一个线程。提高I/O的公平性,降低饥饿风险。