pdflush 进程详解

1. 简介 

    由于页高速缓存的缓存作用,写操作实际上会被延迟。当页高速缓存中的数据比后台存储的数据更新时,那么该数据就被称做脏数据。在内存中累积起来的脏页最终必须被写回磁盘。在以下两种情况发生时,脏页被写回磁盘:

·当空闲内存低于一个特定的阈值时,内核必须将脏页写回磁盘,以便释放内存。
·当脏页在内存中驻留时间超过一个特定的阈值时,内核必须将超时的脏页写回磁盘,以确保脏页不会无限期地驻留在内存中。


上面两种工作的目的完全不同。实际上,在老内核中,这是由两个独立的内核线程(请看后面章节)分别完成的。但是在2.6内核中,由一群内核线程—pdflush后台回写例程—统一执行两种工作。说pdflush是“dirty page flush”的缩写是不正确的,不用去管这个让人混淆的名称,我们来看看这两个目标是如何具体实现的。


首先,pdflush线程在系统中的空闲内存低于一个特定的阈值时,将脏页刷新回磁盘。该后台回写例程的目的在于在可用物理内存过低时,释放脏页以重新获得内存。特定的内存阈值可以通过dirty_background_ratio sysctl系统调用设置。当空闲内存比阈值:dirty_ background_ratio还低时,内核便会调用函数wakeup_bdflush()唤醒一个pdflush线程,随后pdflush线程进一步调用函数background_writeout()开始将脏页写回磁盘。函数background_ writeout()需要一个长整型参数,该参数指定试图写回的页面数目。函数background_writeout()会连续地写出数据,直到满足以下两个条件:
·已经有指定的最小数目的页被写出到磁盘。
·空闲内存数已经回升,超过了阈值dirty_background_ratio。

上述条件确保了pdflush操作可以减轻系统中内存不足的压力。回写操作不会在达到这两个条件前停止,除非pdflush写回了所有的脏页,没有剩下的脏页可再被写回了。


满足第二个目标,pdflush后台例程会被周期性唤醒(和空闲内存是否过低无关),将那些在内存中驻留时间过长的脏页写出,确保内存中不会有长期存在的脏页。如果系统发生崩溃,由于内存处于混乱之中,所以那些在内存中还没来得及写回磁盘的脏页就会丢失,所以周期性同步页高速缓存和磁盘非常重要。在系统启动时,内核初始化一个定时器,让它周期地唤醒pdflush线程,随后使其运行函数wb_kupdate()。该函数将把所有驻留时间超过百分之dirty_expire_centisecs秒的脏页写回。然后定时器将再次被初始化为百分之dirty_expire_ centisecs秒后唤醒pdflush线程。总而言之,pdflush线程周期地被唤醒并且把超过特定期限的脏页写回磁盘。

2. 相关控制参数

系统管理员可以在/proc/sys/vm中设置回写相关的参数,也可以通过sysctl系统调用设置它们。

 

/proc/sys/vm/dirty_ratio

这个参数控制一个进程在文件系统中的文件系统写缓冲区的大小,单位是百分比,表示系统内存的百分比,表示当一个进程中写缓冲使用到系统内存多少的时候,再有磁盘写操作时开始向磁盘写出数据。增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,应该降低其数值,一般缺省是 40。更新方法

echo 30 >/proc/sys/vm/dirty_ratio (或则修改/etc/sysctl.conf文件,增加sys.vm.dirty_ratio=30 重起机器)
 
/proc/sys/vm/dirty_background_ratio

这个参数控制文件系统的pdflush进程,在何时刷新磁盘。单位是百分比,表示系统总内存的百分比,意思是当磁盘的脏数据缓冲到系统内存多少的时候,pdflush开始把脏数据刷新到磁盘。增大会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,应该降低其数值,一般缺省是10。
 
/proc/sys/vm/dirty_writeback_centisecs

Pdflush写后台进程每隔多久被唤醒并执行把脏数据写出到硬盘。单位是 1/100 秒。缺省数值是500,也就是 5 秒。如果你的系统是持续地写入动作,那么实际上还是降低这个数值比较好,这样可以把尖峰的写操作削平成多次写操作。设置方法如下:

echo 200 >/proc/sys/vm/dirty_writeback_centisecs
 
/proc/sys/vm/dirty_expire_centisecs

这个参数声明Linux内核写缓冲区里面的脏数据多“旧”了之后,pdflush 进程就开始考虑写到磁盘中去。单位是 1/100秒。缺省是 30000,也就是 30 秒的数据就算旧了,将会刷新磁盘。对于特别重载的写操作来说,这个值适当缩小也是好的,但也不能缩小太多,因为缩小太多也会导致IO提高太快。建议设置为 1500,也就是15秒算旧。

echo 1500 >/proc/sys/vm/ dirty_expire_centisecs

因此若没有调整这些参数,全部以缺省值,而且关闭sync-bin的话,那么最多丢失的数据是:

5秒种(dirty_writeback_centisecs)之内的,小于1.6G的数据(dirty_background_ratio,16G/10=1.6G)

当然,实际上5秒之内不太可能写1.6G的数据,因此最坏就是5秒钟之内的数据丢失。因此若要关闭sync-bin,又不想丢失太多数据的话,可以通过调整dirty_writeback_centisecs这个参数,如调整到 200(2秒),这样最多就丢2秒钟的数据。又可以提高数据的写能力。

 

kswapd和pdflush

首 先,它们存在的目的不同,kswap的作用是管理内存,pdflush的作用是同步内存和磁盘,当然因为数据写入磁盘前可能会换存在内存,这些缓存真正写 入磁盘由三个原因趋势:1.用户要求缓存马上写入磁盘;2.缓存过多,超过一定阀值,需要写入磁盘;3.内存吃紧,需要将缓存写入磁盘以腾出地方。上述原 因使得kswap和pdflush有交叉的地方,因此很多人混淆了它们。
它们相同的地方都是定期被唤醒,都是以守护进程(内核进程)的形式存在,kswap试图保证内存永远都是可满足用户要求的,为了实现这种承诺,它必须采取 一定的策略;pdflush试图保证内存和磁盘的数据是同步的,不会因为缓存的原因使内存和磁盘的数据不同步从而造成数据丢失或者损坏,为了实现这种承诺,它同样也要采取一定的策略。那么它们之间的交叉点在何处呢?比如,在用户要求内存不能被满足或者空闲内存的数量已经低于某一个值的时候,kswap被 唤醒,它必须为用户的要求提供服务,因此试图换出一部分正在使用的内存使之成为空闲内存以供用户使用,这时,磁盘缓存也是正在被使用的内存,因此,kswap需要将它们换出,这里的换出和匿名页面被换到交换分区是一样的概念,将磁盘缓存换到哪里呢?当然哪里来哪里去了。linux不区分匿名页面 对应的交换分区和真实文件的磁盘缓存对应的磁盘文件分区,实际上在将匿名页面写到交换分区的时候也是按照写文件的形式进行的,读源代码的时候就会发现有一个address_space_operations结构体,里面的readpage和writepage就是读写页面的回调函数,linux的这个实现 方式表明,写匿名页面和写ext2的缓存页面没有本质的区别,仅仅换一下那几个address_space_operations里面的回调函数就行。因此kswap也会将磁盘缓存回写到磁盘,和pdflush所作的工作一样,这就是它们交叉的地方,当然如果kswap已经将页面写入了磁盘,就会清除掉页 面的脏标志,这样,在pdflush扫描脏页的时候就不会二次回写了。
既然kswap和pdflush有联系,那么联系它们的纽带是什么?当然是内核中的lru链表了,本来需要pdflush写入磁盘的页面也许要通过 kswap写入,如何让kswap看到pdflush负责的页面呢?实际上linux并没有刻意关注这个事情,内核那么复杂,如果这么细致的考虑问题谁都 会发疯的。因此linux采用了更加宏伟的方式,就是将事情抽象,不再操心什么回写啊,内存释放之类的细节,而是抽象出了内存管理和缓存管理这些个模块,然后模块和模块之间建立一个耦合点,也可以理解成一个接口,这个东西就是lru链表,linux规定,凡是想纳入内存管理范畴的内存物理页面都要加入 lru链表,而kswap就是内存管理的执行者,它操作的正是这个链表,这样它就不需要别的什么了,只需要告诉大家,你想让我管理,别让我去找你,你自己加入lru链表吧,就这样而已。缓存管理模块当然想加入内存管理,因此所有的磁盘缓存页面都在加入缓存的同时加入了lru链表,这样缓存管理的执行者 pdflush和内存管理的执行者kswap就不需要直接交互商量事情了,一个lru链表解除了它们的耦合。
     linux中到处都体现了这样的思想,它看似一个嗷嗷宏大的内核,实际上是高度模块化的,你不要觉得内核中有些东西好像杂糅在一起而被搞的焦头烂额,实际上仔细看看代码就会发现它们之间的耦合点也就是一些很简单的结构,比如我前面文章提到的list_head或者kobject等等。不信的话再看看上面的 kswap和pdflush,如果你想让内存加入缓存管理,那么就设置它为脏(设置一个标志),并加入一棵radix树(本质上radix树和链表没有区 别,都是一个连接数据结构,早期的内核版本中的缓存就是链表结构连接起来的);如果你想让内存页面加入内存管理,那么就加入lru链表,如果你想两个都加 入呢?很简单,设为脏,加入radix树,再加入lru链表,之后就不用管了,kswap和pdflush会各司其职的,前者查lru,后者查radix 和标志位,它们之间唯一需要交互的就是,一方做完工作后要让另一方看到,这实际上不是它们之间的交互,而仅仅是一项工作的收尾工作,或者说是汇报工作,你做完一件事总得有点效果吧。

<think>我们之前讨论过fclose和close不会刷新内核缓冲区(页缓存),那么进程退出时是否会刷新内核缓冲区? 答案:不会。进程退出时(无论是正常退出还是异常退出),内核不会自动将页缓存中的数据刷新到磁盘。 解释: 1. 进程退出时会关闭所有打开的文件描述符(相当于对每个文件描述符调用close()),但close()本身不刷新内核缓冲区。 2. 内核缓冲区(页缓存)的管理独立于进程,由内核统一管理。数据写入页缓存后,由内核的刷新机制(如达到时间间隔、内存不足等)决定何时写入磁盘。 3. 因此,如果进程退出前有数据仅在内核缓冲区而未写入磁盘,这些数据不会因为进程退出而丢失,它们仍然存在于页缓存中,等待内核后续写入磁盘(除非系统崩溃)。 但是,有一种情况需要注意:如果进程使用了stdio库(FILE*),那么在进程正常退出(例如通过exit()或从main返回)时,会调用所有已打开流的fclose(),这会刷新stdio的用户缓冲区到内核缓冲区(但同样不会刷新内核缓冲区到磁盘)。 总结: - 进程退出不会导致内核缓冲区刷新到磁盘。 - 要确保数据持久化,必须显式调用fsync()或fdatasync()。 示例: 如果进程在写入数据后崩溃(没有调用fsync),数据可能还在页缓存中,系统重启后这部分数据可能丢失(如果系统崩溃的话)。 因此,进程退出时不会刷新内核缓冲区(即不会将数据写入磁盘),但会确保用户空间的缓冲区(如stdio缓冲区)被刷新到内核缓冲区(如果是通过正常退出调用fclose的话)。 注意:异常退出(如被信号杀死)可能不会执行fclose,因此用户空间的缓冲区也可能丢失。 所以,关键点: - 用户空间缓冲区:正常退出时,通过fclose刷新到内核缓冲区;异常退出时,可能丢失。 - 内核空间缓冲区:进程退出(无论正常还是异常)都不会刷新到磁盘,但数据仍在内核的页缓存中,由内核决定何时写回磁盘。 §§相关问题§§ 1. 如果进程异常终止(如被`kill -9`杀死),那么该进程打开的文件描述符对应的内核缓冲区中的数据会丢失吗? 2. 系统崩溃(如断电)与进程崩溃对内核缓冲区中的数据持久性有什么不同影响? 3. 除了显式调用`fsync()`,还有哪些方法可以确保数据在进程退出后立即持久化到磁盘? 4. 内核在什么条件下会自动将页缓存中的数据刷新到磁盘?有哪些参数可以调整这个行为? 5. 使用`mmap`进行文件写入时,进程退出会对映射区的数据造成什么影响?数据会刷新到磁盘吗?</think>### Linux 进程退出时的内核缓冲区处理机制 在 Linux 系统中,进程退出时对内核缓冲区(页缓存)的处理如下: #### 1. **核心结论** - **不会自动刷新内核缓冲区**:进程退出(无论是正常退出还是异常终止)**不会**触发内核缓冲区数据刷新到磁盘 - **缓冲区数据保留**:已写入内核缓冲区的数据会继续保留在页缓存中 - **数据持久化依赖**:磁盘写入由内核的刷新机制独立管理,与进程生命周期无关 #### 2. **详细行为分析** | 退出类型 | 用户空间缓冲区 | 内核缓冲区(页缓存) | |----------------|--------------------------|--------------------------| | **正常退出** | 自动刷新(通过 `exit()` 调用 `fclose()`) | ❌ 不刷新 | | **异常终止** | 可能丢失(如 `kill -9`) | ❌ 不刷新 | | **系统崩溃** | 全部丢失 | 全部丢失 | #### 3. **关键机制说明** 1. **文件描述符关闭**: - 进程退出时内核自动关闭所有打开的文件描述符(等效于 `close()`) - `close()` 仅释放描述符资源,**不刷新**关联的页缓存 2. **页缓存生命周期**: ```mermaid graph LR A[进程写入数据] --> B[进入内核页缓存] B --> C{进程退出} C -->|不触发| D[页缓存保留] D --> E[内核定期刷新] E --> F[磁盘持久化] ``` 3. **刷新触发条件**: - 内核线程 `pdflush`/`kworker` 定期刷新(默认 30 秒) - 页缓存达到内存压力阈值 - 显式调用 `sync()`/`fsync()` - 文件系统卸载(`umount`) #### 4. **数据丢失风险场景** ```c // 危险写入示例 FILE *fp = fopen("data.log", "a"); fprintf(fp, "Important transaction"); // 进入 stdio 缓冲区 // 缺少 fflush() 和 fsync() exit(0); // 正常退出:刷新 stdio 缓冲区到页缓存,但页缓存未刷新到磁盘 // 若此时系统崩溃,数据将丢失 ``` #### 5. **安全写入最佳实践** ```c // 安全写入流程 void write_guaranteed(const char* data) { FILE *fp = fopen("critical.dat", "a"); fprintf(fp, "%s", data); fflush(fp); // 刷新 stdio 缓冲区 fsync(fileno(fp)); // 强制页缓存到磁盘 fclose(fp); } ``` > **关键事实**:Linux 内核设计将缓冲区管理与进程生命周期解耦。页缓存是**系统级资源**,由内核统一管理,不受单个进程退出影响。数据最终持久化依赖于内核的异步刷新机制或显式同步调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值