我在Linux上使用XFS并且有一个内存映射文件,我每秒写一次。我注意到文件mtime(由watch ls --full-time
显示)会定期但不定期地更改。 mtimes之间的差距似乎在2到20秒之间,但并不一致。在系统上运行的其他东西很少 - 特别是我只编写了一个程序,加上一个读取。
相同的程序更频繁地写入其他一些mmapped文件,并且它们的mtime每30秒更改一次。
我没有使用msync()
(在调用时会更新mtime。)
我的问题:
答案 0 :(得分:5)
当你mmap
一个文件时,你基本上是在你的进程和内核的页面缓存之间直接共享内存 - 保存从磁盘读取的文件数据的相同缓存,或者等待写入磁盘。页面缓存中的页面与磁盘上的页面不同(因为它已被写入)被称为“脏”。
有一个内核线程在几个参数的控制下扫描脏页并将它们写回磁盘。一个重要的是dirty_expire_centisecs
。如果文件的任何页面脏了超过dirty_expire_centisecs
,那么该文件的所有脏页都将被写出。默认值为3000厘秒(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获取第一次写入的时间戳。已对此进行了讨论,但未接受a patch that would have fixed it。因此,使用mmap
时,mtimes可能会出错。 dirty_expire_centisecs
限制该错误,但仅限于部分,因为其他磁盘流量可能导致刷新必须等待,扩展写入窗口以进一步绕过mtime。