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秒钟的数据。又可以提高数据的写能力。
首 先,它们存在的目的不同,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 和标志位,它们之间唯一需要交互的就是,一方做完工作后要让另一方看到,这实际上不是它们之间的交互,而仅仅是一项工作的收尾工作,或者说是汇报工作,你做完一件事总得有点效果吧。