写入内存映射文件后什么会更新 mtime?
What updates mtime after writing to memory mapped files?
我在 Linux 上使用 XFS,并且有一个内存映射文件,我每秒写入一次。我注意到文件 mtime(由 watch ls --full-time
显示)周期性但不规律地变化。 mtimes 之间的差距似乎在 2 到 20 秒之间,但并不一致。系统上几乎没有其他东西 运行——尤其是我只有一个程序在写入文件,外加一个读取。
同一个程序更频繁地写入其他一些映射文件,并且它们的 mtime 每 30 秒更改一次。
我没有使用 msync()
(调用时会更新 mtime)。
我的问题:
- 什么更新了 mtime?
- 更新间隔是否可配置?
- 为什么有些 mtimes 每 30 秒正好更新一次,而有些我写得不频繁的文件却有更新的(不规则但总是小于 30 秒)mtimes?
当你 mmap
一个文件时,你基本上是在你的进程和内核的页面缓存之间直接共享内存 - 相同的缓存保存已从磁盘读取或等待写入的文件数据到磁盘。页面缓存中不同于磁盘上的页面(因为它已被写入)称为 "dirty"。
有一个内核线程在几个参数的控制下扫描脏页并将它们写回磁盘。一个重要的是 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
的情况下,答案是内核将映射的页面设置为只读。这并不意味着您不能编写它们,而是意味着您第一次编写时,它会在内存管理单元中触发异常,该异常由内核处理。异常处理程序(至少)做了四件事:
- 将页面标记为脏页,以便将其写回。
- 更新文件 mtime。
- 将页面标记为可读写,以便写入成功。
- 跳回到程序中写入
mmap
ed 页面的指令,这次成功。
因此,当您将数据写入干净的页面时,会导致 mtime 更新,但也会导致页面变为读写,以便进一步写入不会导致异常(或 mtime 更新)注1。但是,当脏页刷新到磁盘时,它会变得干净,并且还会再次变为 "read-only",因此对其进行的任何进一步写入都将触发另一次最终的磁盘写入,以及另一次 mtime 更新。
现在,有了一些假设,我们就可以开始拼图了。
首先,dirty_background_ratio
和 dirty_ratio
可能不会发挥作用。如果您的写入速度快到足以触发后台刷新,那么您很可能会在所有文件上看到 "irregular" 行为。
其次,"irregular"文件与“30秒”文件的区别在于页面访问模式。我推测 "irregular" 文件正在以某种追加模式或循环缓冲区方式写入,这样你每隔几秒就开始写入一个新页面。每次弄脏以前未触及的页面时,都会触发 mtime 更新。但是对于显示 30 秒模式的文件,您只写入一页(可能它们的长度为一页或更少)。在这种情况下,mtime 在第一次写入时更新,然后直到文件刷新到磁盘超过 dirty_expire_centisecs
,即 30 秒。
注 1: 这种行为在技术上是错误的。这是不可预测的,但标准允许一定程度的不可预测性。但他们确实要求 mtime 是 在最后一次写入文件 时或之后,并且在 msync
时或之前(如果有的话)。如果页面在刷新到磁盘之前的间隔内被多次写入,则不会发生这种情况——mtime 获取 first 写入的时间戳。这已被讨论,但 a patch that would have fixed it 未被接受。因此,当使用 mmap
时,mtimes 可能会出错。 dirty_expire_centisecs
某种程度上限制了该错误,但只是部分限制,因为其他磁盘流量可能导致刷新必须等待,从而扩展 window 以进一步绕过 mtime 的写入。
我在 Linux 上使用 XFS,并且有一个内存映射文件,我每秒写入一次。我注意到文件 mtime(由 watch ls --full-time
显示)周期性但不规律地变化。 mtimes 之间的差距似乎在 2 到 20 秒之间,但并不一致。系统上几乎没有其他东西 运行——尤其是我只有一个程序在写入文件,外加一个读取。
同一个程序更频繁地写入其他一些映射文件,并且它们的 mtime 每 30 秒更改一次。
我没有使用 msync()
(调用时会更新 mtime)。
我的问题:
- 什么更新了 mtime?
- 更新间隔是否可配置?
- 为什么有些 mtimes 每 30 秒正好更新一次,而有些我写得不频繁的文件却有更新的(不规则但总是小于 30 秒)mtimes?
当你 mmap
一个文件时,你基本上是在你的进程和内核的页面缓存之间直接共享内存 - 相同的缓存保存已从磁盘读取或等待写入的文件数据到磁盘。页面缓存中不同于磁盘上的页面(因为它已被写入)称为 "dirty"。
有一个内核线程在几个参数的控制下扫描脏页并将它们写回磁盘。一个重要的是 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
的情况下,答案是内核将映射的页面设置为只读。这并不意味着您不能编写它们,而是意味着您第一次编写时,它会在内存管理单元中触发异常,该异常由内核处理。异常处理程序(至少)做了四件事:
- 将页面标记为脏页,以便将其写回。
- 更新文件 mtime。
- 将页面标记为可读写,以便写入成功。
- 跳回到程序中写入
mmap
ed 页面的指令,这次成功。
因此,当您将数据写入干净的页面时,会导致 mtime 更新,但也会导致页面变为读写,以便进一步写入不会导致异常(或 mtime 更新)注1。但是,当脏页刷新到磁盘时,它会变得干净,并且还会再次变为 "read-only",因此对其进行的任何进一步写入都将触发另一次最终的磁盘写入,以及另一次 mtime 更新。
现在,有了一些假设,我们就可以开始拼图了。
首先,dirty_background_ratio
和 dirty_ratio
可能不会发挥作用。如果您的写入速度快到足以触发后台刷新,那么您很可能会在所有文件上看到 "irregular" 行为。
其次,"irregular"文件与“30秒”文件的区别在于页面访问模式。我推测 "irregular" 文件正在以某种追加模式或循环缓冲区方式写入,这样你每隔几秒就开始写入一个新页面。每次弄脏以前未触及的页面时,都会触发 mtime 更新。但是对于显示 30 秒模式的文件,您只写入一页(可能它们的长度为一页或更少)。在这种情况下,mtime 在第一次写入时更新,然后直到文件刷新到磁盘超过 dirty_expire_centisecs
,即 30 秒。
注 1: 这种行为在技术上是错误的。这是不可预测的,但标准允许一定程度的不可预测性。但他们确实要求 mtime 是 在最后一次写入文件 时或之后,并且在 msync
时或之前(如果有的话)。如果页面在刷新到磁盘之前的间隔内被多次写入,则不会发生这种情况——mtime 获取 first 写入的时间戳。这已被讨论,但 a patch that would have fixed it 未被接受。因此,当使用 mmap
时,mtimes 可能会出错。 dirty_expire_centisecs
某种程度上限制了该错误,但只是部分限制,因为其他磁盘流量可能导致刷新必须等待,从而扩展 window 以进一步绕过 mtime 的写入。