假设我想对一个可能非常大的文件执行顺序写入。
如果我对一个巨大的区域进行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上测试),当映射为NOT private时,MADV_DONTNEED
可以正常工作,例如mmap
没有MAP_PRIVATE
标志。我不确定以前版本的Linux内核的行为是什么。
来自Linux手册页项目madvise
手册页的4.15版本:
MADV_DONTNEED
操作成功后,指定区域内存访问的语义发生变化:该范围内页面的后续访问将成功,但会导致从底层映射文件的最新内容重新填充内存内容(用于共享文件映射、共享匿名映射和基于shmem的技术,如System V共享内存段)或匿名私有映射的零填充按需页面。
Linux在Linux 4.5 中的BSD系统中添加了一个具有相同行为的新标志MADV_FREE
它只是在需要时将页面标记为可释放,但不会立即释放它们,从而可以重用内存范围,而不会再次导致页面出错。
关于为什么用于私有映射的MADV_DONTNEED
在未来访问时可能导致零填充页面,请观看@Damon的回答评论中提到的Bryan Cantrill的咆哮。剧透:它来自Tru64 UNIX。
如前所述,MADV_DONTNEED
不是您的朋友。从Linux 5.4开始,您可以使用MADV_COLD
告诉内核,当内存有压力时,它应该调出内存。这似乎正是在这种情况下所需要的。
点击此处阅读更多信息:https://lwn.net/Articles/793462/
首先,madv_sential支持主动预读,所以您不需要它。其次,操作系统无论如何都会懒散地将脏文件烘焙的内存写入磁盘,即使您什么都不做。但是madv_dontned会指示它立即释放内存(您称之为"各种操作系统资源")。第三,目前尚不清楚用于顺序写入的mmapping文件是否有任何优势。只写(2)可能会更好地为您服务(但要使用缓冲区——手动或stdio)。