我在Linux上使用XFS,并有一个每秒写入一次的内存映射文件。 我注意到文件mtime(由watch ls --full-time
)周期性地但不规则地改变。 m次之间的差距似乎在2到20秒之间,但并不一致。 系统上运行的很less,特别是只有一个程序可以写入文件,另外还有一个读数。
相同的程序写得更频繁一些其他mmapped文件,他们的mtime每30秒只更改一次。
我不使用msync()
(这将更新时调用时间)。
我的问题:
当你mmap
一个文件时,你基本上直接在你的进程和内核的页面缓存之间共享内存,这个缓存保存着从磁盘读取的文件数据,或者等待写入磁盘。 页面缓存中的页面与磁盘上的页面不同(因为它已被写入)被称为“脏”。
有一个内核线程可以在多个参数的控制下扫描脏页并将其写回磁盘。 一个重要的是dirty_expire_centisecs
。 如果某个文件的任何页面已经被脏了比dirty_expire_centisecs
更长的时间,那么该文件的所有脏页面都将被写出。 默认值是3000厘cs(30秒)。
另一组变量是dirty_writeback_centisecs
, dirty_background_ratio
和dirty_ratio
。 dirty_writeback_centisecs
控制内核线程检查脏页面的频率,默认值为500(5秒)。 如果脏页面的百分比(作为可用于缓存的内存的一小部分)小于dirty_background_ratio
则不会发生任何事情; 如果它比dirty_background_ratio
,那么内核将开始写一些页面到磁盘。 最后,如果脏页面的百分比超过了dirty_ratio
,那么任何试图写入的进程都将被阻塞,直到脏数据量减少。 这确保了未写入的数据量不会无限增加。 最终,产生数据的过程比磁盘写入数据的速度要慢,以便与磁盘的速度相匹配。
mtime如何得到更新的问题与内核如何知道页面是否脏的问题有关。 在mmap
的情况下,答案是内核将映射的页面设置为只读。 这并不意味着你不能编写它们,但这意味着你第一次这样做,就会触发由内核处理的内存管理单元中的异常。 异常处理程序(至少)有四件事情:
mmap
ed页面的指令,这次成功。 因此,当您将数据写入干净的页面时,会导致mtime更新,但也会导致页面变为可读写状态,以便进一步写入不会导致异常(或mtime更新) 注释1 。 但是,当脏页面被刷新到磁盘时,它变得干净,并且也变成“只读”,所以进一步的写入将触发另一个最终的磁盘写入,也是另一个mtime更新。
所以,现在,有一些假设,我们可以开始把拼图拼凑在一起。
首先, dirty_background_ratio
和dirty_ratio
可能不会起作用。 如果您的写入速度足够快以触发背景冲洗,那么您很可能会在所有文件中看到“不规则”行为。
其次,“不规则”文件和“30秒”文件之间的区别在于页面访问模式。 我推测,“不规则”的文件正在以某种附加模式或循环缓冲的方式写入,以至于你每隔几秒就开始写一个新的页面。 每当你肮脏的以前未触及过的页面,它会触发一个mtime更新。 但是,对于显示30秒模式的文件,您只能写入一个页面(也许它们的长度是一页或更少)。 在这种情况下,mtime会在第一次写入时更新,然后不会再次通过超过dirty_expire_centisecs
(即30秒)将文件刷新到磁盘。
注1:这在技术上是错误的。 这是不可预测的,但标准允许某种程度的不可预测性。 但是他们确实要求mtime 在最后一次写入文件的时候或之后 ,以及在msync
(如果有的话)之前或之前。 在刷新到磁盘之前的时间间隔内多次写入页面的情况下,这不是发生了什么 – mtime获取第一次写入的时间戳。 这已经被讨论过了,但是修补它的补丁不被接受。 因此,在使用mmap
,mtimes可能会出错。 dirty_expire_centisecs
限制错误的种类,但只是部分限制,因为其他磁盘流量可能导致flush必须等待,延长写入的窗口以进一步绕过mtime。