脏页刷新机制总结

1、Buffer Cache和Page Cache

        一句话解释:Page Cache用于缓存文件的页数据,Buffer Cache用于缓存块设备(磁盘)的块数据。但由于磁盘都是由文件系统管理的,所以会导致数据会被缓存两次,因此现在Linux已经不再使用Buffer Cache。

2、读写操作

address_space中的a_ops定义了关于page和磁盘文件交互的一系列操作,它是由struct address_space_operations包含的一组函数指针组成的,其中最重要的就是readpage()和writepage()。

struct address_space_operations {
    int (*writepage)(struct page *page, struct writeback_control *wbc);
    int (*readpage)(struct file *, struct page *);
    /* Set a page dirty.  Return true if this dirtied it */
    int (*set_page_dirty)(struct page *page);
    int (*releasepage) (struct page *, gfp_t);
    void (*freepage)(struct page *);
    ...
}

之所以使用函数指针的方式,是因为不同的文件系统对此的实现会有所不同,比如在ext3中,page-->mapping-->a_ops-->writepage调用的就是ext3_writeback_writepage()。

struct address_space_operations ext3_writeback_aops = {         
    .readpage       = ext3_readpage,                 
    .writepage      = ext3_writeback_writepage,
    .releasepage    = ext3_releasepage,
    ...
}

readpage()会阻塞直到内核往用户buffer里填充满了请求的字节数,如果遇到page cache miss,那要等的时间就比较长了(取决于磁盘I/O的速度)。既然访问一次磁盘那么不容易,那干嘛不一次多预读几个page大小的内容过来呢?

是否采用预读(readahead)要看对文件的访问是连续的还是随机的,如果是连续访问,自然会对性能带来提升,如果是随机访问,预读则是既浪费磁盘I/O带宽,又浪费物理内存。

那内核怎么能预知进程接下来对文件的访问是不是连续的呢?看起来只有进程主动告知了,可以采用的方法有madvise()和posix_fadvise(),前者主要配合基于文件的mmap映射使用。advise如果是NORMAL,那内核会做适量的预读;如果是RANDOM,那内核就不做预读;如果是SEQUENTIAL,那内核会做大量的预读。

预读的page数被称作预读窗口(有点像TCP里的滑动窗口),其大小直接影响预读的优化效果。进程的advise毕竟只是建议,内核在运行过程中会动态地调节预读窗口的大小,如果内核发现一个进程一直使用预读的数据,它就会增加预读窗口,它的目标(或者说KPI吧)就是保证在预读窗口中尽可能高的命中率(也就是预读的内容后续会被实际使用到)。

Page cache缓存最近使用的磁盘数据,利用的是“时间局部性”原理,依据是最近访问到的数据很可能接下来再访问到,而预读磁盘的数据放入page cache,利用的是“空间局部性”原理,依据是数据往往是连续访问的。

同readpage()一样,writepage()的时候,如果要访问的内容不在page cache中,也要先从磁盘拷贝,类似于硬件cache中的write allocate机制。

Page cache这种内核提供的缓存机制并不是强制使用的,如果进程在open()一个文件的时候指定flags为O_DIRECT,那进程和这个文件的数据交互就直接在用户提供的buffer和磁盘之间进行,page cache就被bypass了,借用硬件cache的术语就是uncachable,这种文件访问方式被称为direct I/O,适用于用户使用自己设备提供的缓存机制的场景,比如某些数据库应用。

3、回写与同步

Page cache毕竟是为了提高性能占用的物理内存,随着越来越多的磁盘数据被缓存到内存中,page cache也变得越来越大,如果一些重要的任务需要被page cache占用的内存,内核将回收page cache以支持这些需求。

一个文件通常由text(code)和data组成,这两部分的属性是不同的,text是只读的,调入内存后不会被修改,page cache里的内容和磁盘上的文件内容始终是一致的,回收的时候只要将对应的所有PTEs的P位和PFN清0,直接丢弃就可以了, 不需要和磁盘文件同步,这种page cache被称为discardable的。

而data是可读写的,当data对应的page被修改后,硬件会将PTE中的Dirty位置1(参考这篇文章),Linux通过SetPageDirty(page)设置这个page对应的struct page的flags为PG_Dirty(参考这篇文章),而后将PTE中的Dirty位清0。

在之后的某个时间点,这些修改过的page里的内容需要同步到外部的磁盘文件,这一过程就是page writeback,和硬件cache的writeback原理是一样的,区别仅在于CPU的cache是由硬件维护一致性,而page cache需要由软件来维护一致性,这种page cache被称为syncable的。

①、触发条件

那什么时候才会触发page的writeback呢?分下面几种情况:

  • 从空间的层面,当系统中"dirty"的内存大于某个阈值时。该阈值以在dirtyable memory中的占比"dirty_background_ratio"(默认为10%),或者绝对的字节数"dirty_background_bytes"(2.6.29内核引入)给出。如果两者同时设置的话,那么以"bytes"为更高优先级。

此外,还有"dirty_ratio"(默认为20%)和"dirty_bytes"[注1],它们的意思是当"dirty"的内存达到这个数量(屋里太脏),进程自己都看不过去了,宁愿停下手头的write操作(被阻塞,同步),先去把这些"dirty"的writeback了(把屋里打扫干净)。

而如果"dirty"的程度介于这个值和"background"的值之间(10% - 20%),就交给后面要介绍的专门负责writeback的background线程去做就好了(专职的清洁工,异步)。

  • 从时间的层面,即周期性的扫描(扫描间隔用"dirty_writeback_interval"表示,以毫秒为单位),发现存在最近一次更新时间超过某个阈值的pages(该阈值用"dirty_expire_interval"表示, 以毫秒为单位)。

要想知道page的最近更新时间,最简单的方法当然是每个page维护一个timestamp,但这个开销太大了,而且全部扫描一次也会非常耗时,因此具体实现中不会以page为粒度,而是按照inode中记录的dirtying-time来算。

  • 用户主动发起sync()/msync()/fsync()调用时。

可通过/proc/sys/vm文件夹查看或修改以上提到的几个参数:

centisecs是0.01s,因此上图所示系统的的"dirty_writeback_interval"是5s,"dirty_expire_interval"是30s

来对比下硬件cache的writeback机制。对于硬件cache,writeback会在两种情况触发:

  • 内存有新的内容需要换入cache时,替换掉一个老的cache line。你说为什么page cache不也这样操作,而是要周期性的扫描呢?

替换掉一个cache line对CPU来说是很容易的,直接靠硬件电路完成,而替换page cache的操作本身也是需要消耗内存的(比如函数调用的堆栈开销),如果这个外部backing store是个网络上的设备,那么还需要先建立socket之类的,才能通过网络传输完成writeback,那这内存开销就更大了。所以啊,对于page cache,必须未雨绸缪,不能等内存都快耗光了才来writeback。

  • 以x86为例,软件调用WBINVD指令刷新整个cache,调用CLFLUSH刷新指定的cache line(参考这篇文章),分别类似于page cache的sync()和msync()。

②、执行线程

2.4内核中用的是一个叫bdflush的线程来专门负责writeback操作,因为磁盘I/O操作很慢,而现代系统通常具备多个块设备(比如多个disk spindles),如果bdflush在其中一个块设备上等待I/O操作的完成,可能会需要很长的时间,此时其他块设备还闲着呢,这时单线程模式的bdflush就成为了影响性能的瓶颈。而且,bdflush是没有周期扫描功能的,因此它需要配合kupdated线程一起使用。

于是在2.6内核中,bdflush和它的好搭档kupdated一起被pdflush(page dirty flush)取代了。 pdflush是一组线程,根据块设备的I/O负载情况,数量从最少2个到最多8个不等。如果1秒内都没有空闲的pdflush线程可用,内核将创建一个新的pdflush线程,反之,如果某个pdflush线程的空闲时间已经超过1秒,则该线程将被销毁。一个块设备可能有多个可以传输数据的队列,为了避免在队列上的拥塞(congestion),pdflush线程会动态的选择系统中相对空闲的队列。

这种方法在理论上是很优秀的,然而现实的情况是外部I/O和CPU的速度差异巨大,但I/O系统的其他部分并没有都使用拥塞控制,因此pdflush单独使用复杂的拥塞算法的效果并不明显,可以说是“独木难支”。

于是在更后来的内核实现中(2.6.32版本),干脆化繁为简,直接一个块设备对应一个thread,这种内核线程被称为flusher threads,线程名为"writeback",执行体为"wb_workfn",通过workqueue机制实现调度。

无论是内核周期性扫描,还是用户手动触发,flusher threads的writeback都是间隔一段时间才进行的,如果在这段时间内系统掉电了(power failure),那还没来得及writeback的数据修改就面临丢失的风险,这是page cache机制存在的一个缺点。writeback越频繁,数据因意外丢失的风险越低,但同时I/O压力也越大。

前面介绍的O_DIRECT设置并不能解决这个问题,O_DIRECT只是绕过了page cache,但它并不等待数据真正写到了磁盘上。open()中flags参数使用O_SYNC才能保证writepage()会等到数据可靠的写入磁盘后再返回,适用于某些不容许数据丢失的关键应用。O_SYNC模式下可以使用或者不使用page cache,如果使用page cache,则相当于硬件cache的write through机制。

注1:

在面向服务器应用的RHEL-8中,latency profile的"dirty_background_ratio"和"dirty_ratio"分别被设置为3%和10%,而throughput profile则将这个2个参数分别设置为10%和40%。

笔者对此调整的理解是:更频繁的writeback会影响到业务的throughput,但保持内存中有更多的clean page,可以避免在内存紧张时,试图获取内存的应用陷入direct reclaim,影响latency。

参考: 

https://www.cnblogs.com/gmpy/p/12657801.html

 Linux中的Page Cache [二] - 知乎

内存脏页参数介绍_vm.dirty_ratio_kwdecsdn的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/186778.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python Web开发基础知识篇

一,基础知识篇 本片文章会简单地说一些python开发web中所必须的一些基础知识。主要包括HTML和css基础、JavaScript基础、网络编程基础、MySQL数据库基础、Web框架基础等知识。 1,Web简介 Web,全称为World Wide Web,也就是WWW,万…

mysql索引分为哪几类,聚簇索引和非聚簇索引的区别,MySQL索引失效的情况有哪几种情况,MySQL索引优化的手段,MySQL回表

文章目录 索引分为哪几类?聚簇索引和非聚簇索引的区别什么是[聚簇索引](https://so.csdn.net/so/search?q聚簇索引&spm1001.2101.3001.7020)?(重点)非聚簇索引 聚簇索引和非聚簇索引的区别主要有以下几个:什么叫回…

Leetcode103 二叉树的锯齿形层序遍历

二叉树的锯齿形层序遍历 题解1 层序遍历双向队列 给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 提示&#xff1a…

激光塑料透光率检测仪进行材料质量监控

焊接质量检测是对焊接成果的检测,目的是保证焊接结构的完整性、可靠性、安全性和使用性。焊接质量检测通常包括外观检验、内部检查、无损检测以及试件制作与送检等步骤。通过这些检测方法,可以全面评估焊接质量,确保其符合设计要求和规范标准…

2023.11.25-istio安全

目录 文章目录 目录本节实战1、安全概述2、证书签发流程1.签发证书2.身份认证 3、认证1.对等认证a.默认的宽容模式b.全局严格 mTLS 模式c.命名空间级别策略d.为每个工作负载启用双向 TLS 2.请求认证a.JWK 与 JWKS 概述b.配置 JWT 终端用户认证c.设置强制认证规则 关于我最后 本…

GoLang Filepath.Walk遍历优化

原生标准库在文件量过大时效率和内存均表现不好 1400万文件遍历Filepath.Walk 1400万文件重写直接调用windows api并处理细节 结论 1400万文件遍历时对比 对比条目filepath.walkwindows api并触发黑科技运行时间710秒22秒内存占用480M38M 关键代码 //超级快的文件遍历 fun…

GPS 定位信息分析:航向角分析及经纬度坐标转局部XY坐标

GPS 定位信息分析(1) 从下面的数据可知,raw data 的提取和经纬度的计算应该是没问题的 在相同的经纬度下, x 和 y 还会发生变化,显然是不正确的 raw data:3150.93331124 11717.59467080 5.3 latitude: 31.8489 long…

【Java】智慧工地云平台源码(APP+SaaS模式)

在谈论“智慧工地”之前,我们首先得知道传统工地为什么跟不上时代了。 说起传统工地,总有一些很突出的问题:比如工友多且杂,他们是否入场、身体状况如何,管理人员只能依靠巡查、手工纪录来判断,耗时耗力&am…

ctfshow sql

180 过滤%23 %23被过滤,没办法注释了,还可以用’1’1来闭合后边。 或者使用--%0c-- 1%0corder%0cby%0c3--%0c--1%0cunion%0cselect%0c1,2,database()--%0c--1%0cunion%0cselect%0c1,2,table_name%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_…

多线程Thread(初阶三:线程的状态及线程安全)

目录 一、线程的状态 二、线程安全 一、线程的状态 1.NEW Thread:对象创建好了,但是还没有调用 start 方法在系统中创建线程。 2.TERMINATED: Thread 对象仍然存在,但是系统内部的线程已经执行完毕了。 3.RUNNABLE: 就绪状态&…

基于Python 中创建 Sentinel-2 RGB 合成图像

一、前言 下面的python代码将带您了解如何从原始 Sentinel-2 图像创建 RGB 合成图像的过程。 免费注册后,可以从 Open Access Hub 下载原始图像。 请注意,激活您的帐户可能需要 24 小时! 二、准备工作 (1)导入必要的库…

【Mybatis-Plus篇】Mybatis-Plus基本使用

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

跳转应用市场详情页market

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 未经允许不得转载 目录 一、导读二、概览三、跳转到各大厂商应…

思科模拟器操作命令

模式 思科模拟器常见的模式有 用户模式 能够操作的命令比较少 特权模式特权模式下面可以操作的比较多 全局模式 接口模式 用户模式进入特权模式: 命令enable 特权模式进行全局模式命令: configure terminal 退出命令 exit命令:返回上一层,即一步一步…

Windows核心编程 进程间通信

目录 进程间通信概述 发送消息 WM_COPYDATA DLL共享段 文件映射 文件相关API CreateFile ReadFile WriteFile CloseHandle SetFilePointerEx 设置文件指针 获取文件大小 GetFileSize 结构体 LARGE_INTEGER 文件映射用于读写文件数据 文件映射用于进程间通信(带文…

百度搜索框中的下拉提示关键词提取

效果图 代码有点多,绑定资源了 导出excel如下 贴心养眼背景图鼠标点击小爱心

pat实现基于邻接矩阵表示的深度优先遍历

void DFS(Graph G, int v) {visited[v] 1;printf("%c ", G.vexs[v]);for (int i 0; i < G.vexnum; i) {if (!visited[i] && G.arcs[v][i]) DFS(G, i);} }

C# 读写FDX-B(ISO11784/85)动物标签源码

本示例使用的发卡器&#xff1a;EM4305 EM4469 ISO11784/85协议125K低频FXD-B动物标签读写发卡器-淘宝网 (taobao.com) using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using S…

API 设计:使用 Node.js 和 Express.js 的综合教程

API&#xff08;应用程序编程接口&#xff09;设计涉及创建一个高效而强大的接口&#xff0c;允许不同的软件应用程序相互交互。 说明 本教程将指导您使用 Node.js 和 Express.js 作为核心技术来规划、设计和构建 API。但是&#xff0c;这些原则可以应用于任何语言或框架。我们…

APP软件外包开发需要注意的问题

在进行APP软件开发时&#xff0c;有一些关键问题需要特别注意&#xff0c;以确保项目的成功和用户满意度。以下是一些需要注意的问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 清晰的需求定义&a…