文档章节

缓存和页回写

China_OS
 China_OS
发布于 2012/12/04 21:16
字数 2868
阅读 831
收藏 2
点赞 1
评论 0

     页高速缓存(cache)是linux内核实现磁盘缓存,主要用来减少对磁盘的IO操作,通过把磁盘的数据缓存在物理内存中,把对磁盘的访问变为对物理内存的访问。

     现代操作系统存在高速缓存的两个因素:访问磁盘的速度远远低于访问内存的速度。数据一旦被访问就很有可能在短期内被再次访问(临时局部性原理)。

缓存手段
     页高速缓存是由内存中的物理页面构成,其内容对应磁盘上的物理块,高速缓存大小可以动态调整,可以通过占用空闲内存进行扩张,也可以自我收缩以减轻内存的使用压力。当内核开始一个读操作,会首先检查需要的数据是否在页高速缓存中,如果在内存中,则直接访问内存,如果不在则,内核必须调度块IO操作从磁盘读取数据,然后将数据放入缓存中,系统并不一定是缓存整个文件,有可能是缓存部分内容,具体缓存谁取决于谁被访问。

写缓存,通常缓存一般有三种策略:

     1 不缓存(nowrite),高速缓存不去缓存任何写操作,直接跳过缓存写入磁盘,同时也使缓存的数据失效。
     2 写操作自动更新内存缓存,同时也更新磁盘文件,也称为写通透,因为写操作会立刻穿透缓存到磁盘中,这对于保存缓存一致性有好处。
     3 linux采用的回写策略,程序的写操作直接写入缓存中,但是不会立即和后端磁盘进行同步,而是将高速缓存中被写入数据的页面标记成dirty,并且将页面加入脏链表中,由一个后台进程定时把脏页面写入磁盘中。

     缓存既然可以被分配,那肯定也会被回收了,linux的缓存回收是通过选择干净的页进行简单的替换,如果缓存中没有足够的干净页面,内核将强制的进行回写操作,以便腾出更多的干净页。而最难的是决定什么页应该被回收,回收算法有两种,如下:

     最近最少使用算法(LRU),LRU回收策略跟踪每个页面的访问踪迹,以便能回收最老时间戳的页面,该策略的良好效果源自缓存的数据越久未被访问,则以后被访问的可能行越不大,但是对于很多很多文件被访问一次后不再访问,LRU则会效率底下。

     双链策略,linux实际使用的是一个经过改进的LRU,称为双链策略,它维护两个链表:活跃链表和非活跃链表。活跃链表中的不会被换出,而非活跃链表中的会被换出。两个链表都被伪LRU规则维护:页面从尾部加入,从头部移除。并且两个链表需要维护平衡,如果活跃链表过多,则会把活跃链表表头的页面加入非活跃链表的尾部,以便能被回收。双链表解决的传统LRU算法中对仅一次的访问困境。这种链表也称为LRU/2,更多的是LRU/n,表示有n个链表。

linux页高速缓存
     页高速缓存的是内存页面,缓存的页来自正规文件、块设备、内存映射文件等。在执行一个IO操作前,内核会检查数据是否已经在高速缓存中了。页高速缓存中的页可能包含了多个不连续的物理磁盘块,也正是由于页面中映射的磁盘块不一定连续,所以在页高速缓存中检测特定数据是否已被缓存就变得不那么容易了。linux页高速缓存的目标是缓存任何基于页的对象,包括各种类型的文件和各种内存映射。linux高速缓存使用address_space结构体管理缓存项和页IO操作。文件可以有多个虚拟地址,但是在物理内存中只能有一份。

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 */
};

     i_mmap字段是一个优先搜索树,它的搜索范围包含了在address_sapce中私有的和共享的页面。

     nrpages反应了address_space空间的大小,也就是页总数。address_space结构往往会和某些内核对象关联。通常情况下,会与一个索引节点(inode)关联,这时host域就会指向该索引节点。如果关联对象不是一个索引节点的话,比如address_space和swapper关联时,这是host域会被置为NULL。

     a_ops域指向地址空间对象中的操作函数表,这与VFS对象及其操作函数表关系类似。由address_space_operations结构体表示:

struct address_space_operations {
        int (*writepage)(struct page *, struct writeback_control *);
        int (*readpage) (struct file *, struct page *);
        int (*sync_page) (struct page *);
        int (*writepages) (struct address_space *, struct writeback_control *);
        int (*set_page_dirty) (struct page *);
        int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned);
        int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);
        int (*commit_write) (struct file *, struct page *, unsigned, unsigned);
        sector_t (*bmap)(struct address_space *, sector_t);
        int (*invalidatepage) (struct page *, unsigned long);
        int (*releasepage) (struct page *, int);
        int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);
};
      因为在任何页IO操作前内核都要检查页是否已经在页高速缓存中了,所以这种检查必须迅速,高效。否则得不偿失了。前边已经说过,也高速缓存通过两个参数address_space对象和一个偏移量进行搜索。每个address_space对象都有唯一的基树(radix-tree),它保存在page_tree结构体中。基树是一个二叉树,只要指定了文件偏移量,就可以在基树中迅速检索到希望的数据,页高速缓存的搜索函数find_get_page()要调用函数radix_tree_lookup(),该函数会在指定基树中搜索指定页面。

     在linux2.6之前,内核是通过全局散列表进行检索的,对于一个给定的值,会返回一个双向链表的入口对应于这个所给定的值,但是散列表有四个问题:
          1 使用全局保护锁,锁争用严重,导致性能受损
          2 散列表包含所有的缓存页,但是搜索只要和当前文件相关联的页,所以散列表包含的页面比搜索需要的页面大很多
          3 如果搜索失败,执行速度比希望的要慢很多,因为搜索需要遍历整个链表
          4 散列表消耗更多的内存

缓冲区高速缓存
     独立的磁盘块通过块IO缓冲也要被存入页高速缓存,一个缓冲是一个物理磁盘块在内存里的表示,缓冲的作用是映射内存中的页面到磁盘块,这样页高速缓存在块IO操作时也减少了磁盘访问,这个缓存通常称为缓冲区高速缓存,块IO操作一次操作一个单独的磁盘块,普遍的块IO操作是读写i节点,通过缓存,磁盘块映射到他们相关的页内存,并缓存到页高速缓存中。

flusher线程
     在页高速缓存的影响下,写操作会被延迟,而内存中的脏数据最终必须被写入磁盘,以下情况发生时脏数据会被写入磁盘:
          1 当空闲内存低于一个特定的阈值时,内核将脏页面写回磁盘以便释放内存。
          2 当脏页面在内存中驻留的时间超过一个特定的阈值时,内核必须将脏页面写回磁盘。
          3 用户进程调用sync()和fsync()系统调用时,内核会执行写回操作。

     在2.6内核中由一群内核线程执行这三种工作,首先flusher在系统空闲内存低于一个特定阈值时会将脏页面写回磁盘,这个特定的内存阈值可以通过dirty_background_ratio_sysctl系统调用设置。当内存空闲阈值比dirty_background_ratio还低时,内核调用flusher_threads()唤醒多个flusher线程,flusher线程调用bdi_writeback_all()将脏页面写回磁盘。为了达到第二个目标,flusher后台程序会被周期性的唤醒,将内存中驻留时间过长的脏页面写回磁盘,在系统启动时,内核会初始化一个定时器,让他周期性的唤醒flusher线程,随后使其运行函数wb_writeback(),把所有驻留时间超过dirty_expire_interval的脏页面写回磁盘,然后定时器再次被初始化为dirty_expire_interval秒后唤醒flusher线程。

     在2.6内核之前,flusher的工作是由bdflush和kupdated两个线程共同完成,bdflush内核线程在后台执行脏页面回写操作,因为只有在内存过低或者缓冲数量过大时bdflush才刷新缓冲,所以需要kupdated线程周期性的回写脏数据,bdflush和flusher的区别是:系统中只有一个bdflush线程,而flusher线程的数量是根据磁盘数量变化的,bdflush基于缓冲,将脏缓冲写入磁盘,flusher基于页面,它将脏页面写回磁盘,实际操作对象是页面而不是块,管理简单。但是2.6内核中bdflush和kupdated被pdflush替代了,pdflush线程数目是动态的,具体多少取决于系统的IO负载,pdflush与任何任务都无关,它是面向系统中的所有磁盘的全局任务,但是这样带来的问题是pdflush线程容易在拥塞的磁盘上绊住,flusher线程在2.6.32内核中取代了pdflush线程,针对每个磁盘独立的执行回写操作是其特性。

避免拥塞-使用多个线程
     bdflush最主要的一个缺点是只有一个线程,在回写任务很重时容易阻塞在某个设备的已拥塞请求队列中,而导致其他请求队列的任务无法处理。pdflush线程是动态变化的,每个线程尽可能的从每个超级块的脏页面链表中回收数据,pdflush避免了因为一个磁盘忙而导致其余磁盘饥饿的情况,在常规情况下这种策略效果不错,但是假如每个pdflush线程在同一个拥塞队列上挂起了怎么办?这时pdflush采用拥塞回避策略,主动尝试从那些没有拥塞的队列回写,从而防止欺负某一个忙碌的设备。flusher线程则是和具体块设备相关联,每个给定的线程从每个给定设备的脏页面链表回收数据,并回写到对应的磁盘,由于每个磁盘对应一个线程,所以不需要复杂的拥塞避免策略,降低了磁盘饥饿的风险。

© 著作权归作者所有

共有 人打赏支持
China_OS
粉丝 403
博文 438
码字总数 487778
作品 0
徐汇
技术主管
Linux文件读写机制及优化方式

缓存 缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机内存和磁盘,内存操作速度远远大于磁盘,如果每次调用read,write都去直接操作磁盘,一方面速度会被限制,...

linuxprobe
2016/09/25
7
0
buffer && cache

个人理解为: buffer 是指写操作的缓存 cache 是指读操作的缓存 内存页类型 Read Pages - 这些页通过MPF 从磁盘中读入,而且是只读.这些页存在于Buffer Cache中以及包括不能够修改的静态文件,...

China_OS
2012/06/13
0
0
如何更改操作系统文件缓存刷新策略?

环境说明 以下针对linux操作系统,在centos/RHEL 6、centos/RHEL 7上测试有效。 相关参数 和文件系统写缓存策略相关的主要是下面两个参数,其它相关参考可自行谷歌: /proc/sys/vm/dirty_rat...

党志强
07/04
0
0
PgSQL · 特性分析 · Write-Ahead Logging机制浅析

WAL机制简介 WAL即 Write-Ahead Logging,是一种实现事务日志的标准方法。WAL 的中心思想是先写日志,再写数据,数据文件的修改必须发生在这些修改已经记录在日志文件中之后。采用WAL日志的数...

阿里云RDS-数据库内核组
2017/03/03
0
0
《亿级流量网站架构核心技术》目录一览

举报   在2011年年底的时候笔者就曾规划写一本Spring的书,但是因为是Spring入门类型的书,框架的内容更新太快,觉得还是写博客好一些,因此就把写完的书稿放到了博客(jinnianshilongnia...

jinjiang2009
2017/03/14
0
0
记一次服务器IO过高处理过程

记一次服务器IO过高处理过程 一、背景 在一次上线升级后,发现两台tomcat服务器的IOwait一直超过100ms,高峰时甚至超过300ms,检查服务器发现CPU负载,内存的使用率都不高。问题可能出现在硬...

四京
2017/05/27
0
0
innodb关键特性和体系架构整理

Innodb关键特性: 1,插入缓冲(insert Buffer) 2,两次写(Double Write) 3,自适应哈希索引(Adaptive Hash Index) 4,异步IO(Async IO) 5,刷新紧邻页(Flush Neighbor Page) Innodb体系架构:...

落叶刀
2016/06/14
74
0
kswapd和pdflush

首 先,它们存在的目的不同,kswap的作用是管理内存,pdflush的作用是同步内存和磁盘,当然因为数据写入磁盘前可能会换存在内存,这些缓存真正写 入磁盘由三个原因趋势:1.用户要求缓存马上写...

晨曦之光
2012/04/10
432
0
存储方式与介质对性能的影响

摘要 数据的存储方式对应用程序的整体性能有着极大的影响。对数据的存取,是顺利读写还是随机读写?将数据放磁盘上还将数据放flash卡上?多线程读写对性能影响?面对着多种数据存储方式,我们...

晨曦之光
2012/03/09
180
0
linux的同步IO操作函数: sync、fsync与fdatasync

VFS(Virtual FileSystem)的存在使得Linux可以兼容不同的文件系统,例如ext3、ext4、xfs、ntfs等等,其不仅具有为所有的文件系统实现一个通用的外接口的作用,还具有另一个与系统性能相关的...

miscellanea
2015/11/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

LSM树(Log-Structured Merge Tree)存储引擎浅析

其实每一种数据库,它都是一种抽象的数据结构的具体实现。 随着rocksDB(facebook的),levelDB(google的),以及我们熟知的hbase,他们都是使用的LSM树结构的数据库。 它的核心思路其实非常...

算法之名
3分钟前
0
0
ARTS说明

湾曲日报,作者每天阅读5篇优质英文文章,从2014/08/06开始,到今天也1216期了. 阮一峰的每周分享,从2018/04/20开始,每周5分享一周作者认为值得分享的东西,现在最新是14期. 关于ARTS Algorithm ...

yysue
5分钟前
0
0
sql优化原则

批量查询/更新/获得表结构 1.sp_help table_name;-sqlserver==desc table_name-mysql 如: sp_help Student; sql优化: 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引...

xd03122049
13分钟前
0
0
python爬虫日志(6)小小实践

经过3天的学习,现在对简单的网页基本可以爬取想爬取的信息了,但还无法应对网站一些复杂的反爬虫措施。 今天利用目前为止所学的知识,试着爬取了煎蛋网几页图片并下载到本地。 #首先还是先导...

茫羽行
19分钟前
0
0
js中用oop思想封装轮播

用户可以自己设置:1、速度speed:fast,normal,slow 2、是否自动轮播:true,false 3、选择器(当然可以根据需求,增加,目前先封的这三个)仅供参考 觉得oop面向对象的思想比较有意思,前端...

琴妹
19分钟前
0
0
使用fastjson将json格式的数据转化为对象

1. 导入fastjson的jar包 <!-- 9.fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> 2. 常用几种类型的......

Lucky_Me
21分钟前
0
0
JDK11的ZGC小试牛刀

序 本文主要试一下JDK11的ZGC ZGC ZGC全称是Z Garbage Collector,是一款可伸缩(scalable)的低延迟(low latency garbage)、并发(concurrent)垃圾回收器,旨在实现以下几个目标: 停顿时间不超...

go4it
25分钟前
0
0
电信ss/ssr速度慢 电信国际出口速度慢 被QoS限速

很多人跟我反应,同一条线路,电信用户的国际出口速度很慢,而移动/联通用户却还不错,可能移动/联通可以流畅看1080P,而电信卡的连国外网页都打不开。明明电信的国际出口宽带是三家中最高的...

flyzy2005
30分钟前
0
0
java中equals,hashcode和==的区别

1、== java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型 byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比较的是他们的值。 2.引用类型...

小海bug
52分钟前
0
0
Win10专业版安装GIT后使用Git Bash闪退解决办法

百度后把过程和最终解决办法记录下来: 百度首先出来的解决办法如下: 来自:https://segmentfault.com/q/1010000012722511?sort=created 重启电脑 重新安装 安装到C盘 尝试网上的教程 \Git...

特拉仔
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部