综述

Page cache是通过将磁盘中的数据缓存到内存中,从而减少磁盘I/O操作,从而提高性能。此外,还要确保在page cache中的数据更改时能够被同步到磁盘上,后者被称为page回写(page writeback)。一个inode对应一个page cache对象,一个page cache对象包含多个物理page。

对磁盘的数据进行缓存从而提高性能主要是基于两个因素:第一,磁盘访问的速度比内存慢好几个数量级(毫秒和纳秒的差距)。第二是被访问过的数据,有很大概率会被再次访问。

Page Cache

Page cache由内存中的物理page组成,其内容对应磁盘上的block。page cache的大小是动态变化的,可以扩大,也可以在内存不足时缩小。cache缓存的存储设备被称为后备存储(backing store),注意我们在block I/O中提到的:一个page通常包含多个block,这些block不一定是连续的。

读Cache

当内核发起一个读请求时(例如进程发起read()请求),首先会检查请求的数据是否缓存到了page cache中,如果有,那么直接从内存中读取,不需要访问磁盘,这被称为cache命中(cache hit)。

如果cache中没有请求的数据,即cache未命中(cache miss),就必须从磁盘中读取数据。然后内核将读取的数据缓存到cache中,这样后续的读请求就可以命中cache了。page可以只缓存一个文件部分的内容,不需要把整个文件都缓存进来。

写Cache

当内核发起一个写请求时(例如进程发起write()请求),同样是直接往cache中写入,后备存储中的内容不会直接更新。内核会将被写入的page标记为dirty,并将其加入dirty list中。内核会周期性地将dirty list中的page写回到磁盘上,从而使磁盘上的数据和内存中缓存的数据一致。

Cache回收

Page cache的另一个重要工作是释放page,从而释放内存空间。cache回收的任务是选择合适的page释放,并且如果page是dirty的,需要将page写回到磁盘中再释放。理想的做法是释放距离下次访问时间最久的page,但是很明显,这是不现实的。下面先介绍LRU算法,然后介绍基于LRU改进的Two-List策略,后者是Linux使用的策略。

LRU算法

LRU(least rencently used)算法是选择最近一次访问时间最靠前的page,即干掉最近没被光顾过的page。原始LRU算法存在的问题是,有些文件只会被访问一次,但是按照LRU的算法,即使这些文件以后再也不会被访问了,但是如果它们是刚刚被访问的,就不会被选中。

Two-List策略

Two-List策略维护了两个list,active list 和 inactive list。在active list上的page被认为是hot的,不能释放。只有inactive list上的page可以被释放的。首次缓存的数据的page会被加入到inactive list中,已经在inactive list中的page如果再次被访问,就会移入active list中。两个链表都使用了伪LRU算法维护,新的page从尾部加入,移除时从头部移除,就像队列一样。如果active list中page的数量远大于inactive list,那么active list头部的页面会被移入inactive list中,从而位置两个表的平衡。

Page Cache在Linux中的具体实现

address_space结构

内核使用address_space结构来表示一个page cache,address_space这个名字起得很糟糕,叫page_ache_entity可能更合适。下面是address_space的定义

struct address_space {
struct inode *host; /* owning inode */
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* page_tree lock */
unsigned int i_mmap_writable; /* VM_SHARED ma count */
struct prio_tree_root i_mmap; /* list of all mappings */
struct list_head i_mmap_nonlinear; /* VM_NONLINEAR ma list */
spinlock_t i_mmap_lock; /* i_mmap lock */
atomic_t truncate_count; /* truncate re count */
unsigned long nrpages; /* total number of pages */
pgoff_t writeback_index; /* writeback start offset */
struct address_space_operations *a_ops; /* operations table */
unsigned long flags; /* gfp_mask and error flags */
struct backing_dev_info *backing_dev_info; /* read-ahead information */
spinlock_t private_lock; /* private lock */
struct list_head private_list; /* private list */
struct address_space *assoc_mapping; /* associated buffers */
};

其中 host域指向对应的inode对象,host有可能为NULL,这意味着这个address_space不是和一个文件关联,而是和swap area相关,swap是Linux中将匿名内存(比如进程的堆、栈等,没有一个文件作为back store)置换到swap area(比如swap分区)从而释放物理内存的一种机制。page_tree保存了该page cache中所有的page,使用基数树(radix Tree)来存储。i_mmap是保存了所有映射到当前page cache(物理的)的虚拟内存区域(VMA)。nrpages是当前address_space中page的数量。

address_space操作函数

address_space中的a_ops域指向操作函数表(struct address_space_operations),每个后备存储都要实现这个函数表,比如ext3文件系统在fs/ext3/inode.c中实现了这个函数表。

内核使用函数表中的函数管理page cache,其中最重要的两个函数是readpage() 和writepage()

readpage()函数

readpage()首先会调用find_get_page(mapping, index)在page cache中寻找请求的数据,mapping是要寻找的page cache对象,即address_space对象,index是要读取的数据在文件中的偏移量。如果请求的数据不在该page cache中,那么内核就会创建一个新的page加入page cache中,并将要请求的磁盘数据缓存到该page中,同时将page返回给调用者。

writepage() 函数

对于文件映射(host指向一个inode对象),page每次修改后都会调用SetPageDirty(page)将page标识为dirty。(个人理解swap映射的page不需要dirty,是因为不需要考虑断电丢失数据的问题,因为内存的数据断电时默认就是会失去的)内核首先在指定的address_space寻找目标page,如果没有,就分配一个page并加入到page cache中,然后内核发起一个写请求将数据从用户空间拷入内核空间,最后将数据写入磁盘中。(对从用户空间拷贝到内核空间不是很理解,后期会重点学习Linux读、写文件的详细过程然后写一篇详细的blog介绍)

Buffer Cache

在Block I/O的文章中提到用于表示内存到磁盘映射的buffer_head结构,每个buffer-block映射都有一个buffer_head结构,buffer_head中的b_assoc_map指向了address_space。在Linux2.4中,buffer cache和 page cache之间是独立的,前者使用老版本的buffer_head进行存储,这导致了一个磁盘block可能在两个cache中同时存在,造成了内存的浪费。2.6内核中将两者合并到了一起,使buffer_head只存储buffer-block的映射信息,不再存储block的内容。这样保证一个磁盘block在内存中只会有一个副本,减少了内存浪费。

Flusher线程群(Flusher Threads)

Page cache推迟了文件写入后备存储的时间,但是dirty page最终还是要被写回磁盘的。

内核在下面三种情况下会进行会将dirty page写回磁盘:

  • 用户进程调用sync() 和 fsync()系统调用
  • 空闲内存低于特定的阈值(threshold)
  • Dirty数据在内存中驻留的时间超过一个特定的阈值

线程群的特点是让一个线程负责一个存储设备(比如一个磁盘驱动器),多少个存储设备就用多少个线程。这样可以避免阻塞或者竞争的情况,提高效率。当空闲内存低于阈值时,内核就会调用wakeup_flusher_threads()来唤醒一个或者多个flusher线程,将数据写回磁盘。为了避免dirty数据在内存中驻留过长时间(避免在系统崩溃时丢失过多数据),内核会定期唤醒一个flusher线程,将驻留时间过长的dirty数据写回磁盘。

Page Cache与Page回写的更多相关文章

  1. page cache 与 page buffer 转

    page cache 与 page buffer 标签: cachebuffer磁盘treelinux脚本 2012-05-07 20:47 2905人阅读 评论(0) 收藏 举报  分类: 内核编程 ...

  2. 在page cache中的页,如果当时没有进程read或者write,引用计数到底该为多少

    在一次偶然的机会,在研究如何降低pagecache占用的过程中,走查了 invalidate_mapping_pages的代码: 通过调用 __pagevec_lookup 在radix树中收集一部分 ...

  3. Linux中的Buffer Cache和Page Cache echo 3 > /proc/sys/vm/drop_caches Slab内存管理机制 SLUB内存管理机制

    Linux中的Buffer Cache和Page Cache echo 3 > /proc/sys/vm/drop_caches   Slab内存管理机制 SLUB内存管理机制 http://w ...

  4. Page Cache的落地问题

    除非特别说明,否则本文提到的写操作都是 buffer write/write back. 起因 前几天讨论到一个问题:Linux 下文件 close成功,会不会触发 “刷盘”? 其实这个问题根本不用讨 ...

  5. Page cache和Buffer cache[转1]

    http://www.cnblogs.com/mydomain/archive/2013/02/24/2924707.html Page cache实际上是针对文件系统的,是文件的缓存,在文件层面上的 ...

  6. 【转】Linux Page Cache的工作原理

    1 .前言 自从诞生以来,Linux 就被不断完善和普及,目前它已经成为主流通用操作系统之一,使用得非常广泛,它与Windows.UNIX 一起占据了操作系统领域几乎所有的市场份额.特别是在高性能计算 ...

  7. 工作于内存和文件之间的页缓存, Page Cache, the Affair Between Memory and Files

    原文作者:Gustavo Duarte 原文地址:http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait ...

  8. page cache和buffer cache

    因为要优化I/O性能,所以要理解一下这两个概念,这两个cache着实让我迷糊了好久,通过查资料大概明白了两者的区别,试着说下. page cache:文件系统层级的缓存,从磁盘里读取的内容是存储到这里 ...

  9. page cache 与free

    我们经常用free查看服务器的内存使用情况,而free中的输出却有些让人困惑,如下: 先看看各个数字的意义以及如何计算得到: free命令输出的第二行(Mem):这行分别显示了物理内存的总量(tota ...

随机推荐

  1. ASP.Net Core项目在Mac上使用Entity Framework Core 2.0进行迁移可能会遇到的一个问题.

    在ASP.Net Core 2.0的项目里, 我使用Entity Framework Core 2.0 作为ORM. 有人习惯把数据库的连接字符串写在appSettings.json里面, 有的习惯写 ...

  2. javascript 常见数组操作( 1、数组整体元素修改 2、 数组筛选 3、jquery 元素转数组 4、获取两个数组中相同部分或者不同部分 5、数组去重并倒序排序 6、数组排序 7、数组截取slice 8、数组插入、删除splice(需明确位置) 9、数组遍历 10、jQuery根据元素值删除数组元素的方)

    主要内容: 1.数组整体元素修改 2. 数组筛选 3.jquery 元素转数组 4.获取两个数组中相同部分或者不同部分 5.数组去重并倒序排序 6.数组排序 7.数组截取slice 8.数组插入.删除 ...

  3. 【Zara原创】SqlSugar4轻量级ORM框架的使用指南

    前言:sqlSugar出生已经有3年之久了,从1.0到现在的4.x的版本,为了以后方便使用SqlSugar,所以特意花了2个小时来叙述它. 关于SqlSugar 性能:性能最好的ORM之一,具有超越D ...

  4. C++STL模板库适配器之优先级队列

    目录 适配器之优先级队列 一丶优先级队列简介(priority_queue) 二丶优先级队列代码演示 1.优先级队列代码以及使用简介 适配器之优先级队列 一丶优先级队列简介(priority_queu ...

  5. Nagios 监控系统架构

    Nagios 监控系统架设全攻略 简介: Nagios 全名为(Nagios Ain’t Goona Insist on Saintood),最初项目名字是 NetSaint.它是一款免费的开源 IT ...

  6. springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

    前言 开心一刻 女儿: “妈妈,你这么漂亮,当年怎么嫁给了爸爸呢?” 妈妈: “当年你爸不是穷嘛!‘ 女儿: “穷你还嫁给他!” 妈妈: “那时候刚刚毕业参加工作,领导对我说,他是我的扶贫对象,我年轻 ...

  7. SpringCloud系列——Zuul 动态路由

    前言 Zuul 是在Spring Cloud Netflix平台上提供动态路由,监控,弹性,安全等边缘服务的框架,是Netflix基于jvm的路由器和服务器端负载均衡器,相当于是设备和 Netflix ...

  8. 解决虚拟机连接不上外网,不能互相ping通

    0.解决虚拟机连接不上外网,不能互相ping通 直接在linux系统下,编辑修改如下文件: //编辑文件/etc/sysconfig/network-scripts/ifcfg-eth0 [root@ ...

  9. 【.NET Core项目实战-统一认证平台】第十三章 授权篇-如何强制有效令牌过期

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上一篇我介绍了JWT的生成验证及流程内容,相信大家也对JWT非常熟悉了,今天将从一个小众的需求出发,介绍如何强制令牌过期的思路和实现过程. ...

  10. 用EF的三种方式(SqlServer数据库和Oracle数据库)

    SqlServer数据库 1.DB First 现有DB,生成edmx文件 贴一下生成的model //------------------------------------------------ ...