假设,假设我想对一个可能非常大的文件执行顺序写入。
如果我 mmap() 一个巨大的区域并在整个区域上进行 madvise(MADV_SEQUENTIAL),那么我可以以相对有效的方式写入内存。 这我已经工作得很好了。
现在,为了在我编写时释放各种操作系统资源,我偶尔会对已写入的小块内存执行 munmap() 。 我担心的是 munmap() 和 msync() 会阻塞我的线程,等待数据被物理提交到磁盘。 我根本无法放慢我的作家的速度,所以我需要找到另一种方法。
在已经写入的小内存块上使用 madvise(MADV_DONTNEED) 会更好吗? 我想告诉操作系统将该内存延迟写入磁盘,而不是阻止我的调用线程。
madvise() 的手册页有这样的说法,但相当含糊:
MADV_DONTNEED
Do not expect access in the near future. (For the time being, the
application is finished with the given range, so the kernel can free
resources associated with it.) Subsequent accesses of pages in this
range will succeed, but will result either in re-loading of the memory
contents from the underlying mapped file (see mmap(2)) or
zero-fill-on-demand pages for mappings without an underlying file.
为了你好,请远离
MADV_DONTNEED
。 Linux 不会将此视为在写回页面后将其丢弃的提示,而是立即将其丢弃。这不被认为是一个错误,而是一个深思熟虑的决定。
讽刺的是,原因是非破坏性 MADV_DONTNEED
的功能已经由
msync(MS_INVALIDATE|MS_ASYNC)
给出,另一方面 MS_ASYNC
不会启动 I/O(事实上,它什么也不做,遵循推理脏页写回无论如何都可以正常工作),fsync
总是阻塞,并且sync_file_range
可能阻塞如果你超过了一些模糊的限制并且被文档认为“极其危险”,无论这意味着什么。 无论哪种方式,您都必须先按
msync(MS_SYNC)
,或
fsync
(均会阻塞),或sync_file_range
(可能会阻塞),然后按fsync
,否则您会因
MADV_DONTNEED
而丢失数据。如果您无法承受可能的阻塞,那么遗憾的是您别无选择,只能在另一个线程中执行此操作。
对于最新的 Linux 内核(刚刚在 Linux 5.4 上测试),当映射为 MADV_DONTNEED
可以按预期工作,例如
mmap
没有 MAP_PRIVATE
标志。我不确定以前版本的 Linux 内核的行为是什么。
从 Linux 手册页项目的
madvise
手册页 4.15 版本开始:成功执行
MADV_DONTNEED
操作后,指定区域中内存访问的语义将发生更改:该范围内页面的后续访问将成功,但会导致,与从最新内容重新填充内存内容底层映射文件(用于共享文件映射、共享匿名映射和基于 shmem 的技术,例如 System V 共享内存段)
或用于匿名私有映射的按需填零页面。Linux 添加了一个新标志MADV_FREE
Linux 4.5 中的 BSD 系统具有相同的行为
它只是将页面标记为可在需要时释放,但不会立即释放它们,从而可以重用内存范围,而不会再次产生页面故障的成本。可能会导致将来访问时页面填充为零,请观看 @Damon 答案的评论中提到的为什么私有映射的
MADV_DONTNEED
Bryan Cantrill 的咆哮
。剧透:它来自 Tru64 UNIX。 正如已经提到的,
MADV_COLD
告诉内核在存在内存压力时应该调出该内存。这似乎正是这种情况下想要的。
在这里阅读更多内容:https://lwn.net/Articles/793462/首先, madv_sequential 可以实现积极的预读,所以你不需要它。 其次,操作系统无论如何都会将脏文件烘焙内存写入磁盘,即使您什么也不做。但 madv_dontneed 会指示它立即释放内存(您称之为“各种操作系统资源”)。第三,尚不清楚用于顺序写入的映射文件有什么优势。你可能会通过 write(2) 得到更好的服务(但使用缓冲区 - 手动或 stdio)。