从手册中,我只知道 mmap() 将文件映射到虚拟地址空间,因此可以随机访问该文件。但是,我不清楚映射文件是否立即加载到内存中?我猜内核按页管理映射的内存,它们是按需加载的,如果我只进行少量读写,则只加载几页。这是对的吗?
4 回答
不,是的,也许。这取决于。
调用mmap
通常仅意味着对于您的应用程序,映射文件的内容被映射到其地址空间,就好像文件已加载在那里一样。或者,就好像文件真的存在于内存中一样,就好像它们是一回事(假设您具有写访问权限,包括将更改写回磁盘)。
不多也不少。它没有加载东西的概念,应用程序也不知道这意味着什么。
应用程序并不真正了解内存之类的东西,尽管虚拟内存系统使它看起来像那样。应用程序可以“看到”(和访问)的内存可能对应于实际物理内存,也可能不对应,原则上这可以随时更改,无需事先警告,也没有明显的原因(对您的应用程序来说很明显)。
除了可能由于页面错误而经历小的延迟之外,应用程序(原则上)完全不知道发生了任何此类事情,并且几乎无法控制它1。
由于遇到故障,应用程序通常会根据需要从映射文件(包括主可执行文件!)加载页面。但是,操作系统通常会尝试推测性地预取数据以优化性能。
在实践中,调用mmap
将立即开始(异步)从映射的开头预取页面,直到达到特定的实现指定的大小。这意味着,原则上,对于小文件,答案是“是”,而对于大文件,答案是“否”。
但是,mmap
不会阻塞等待预读完成,这意味着您无法保证任何文件在mmap
返回后立即在 RAM 中(无论如何您都不能保证任何时候!)。就目前而言,答案是“也许”。
在 Linux 下,我上次查看时,默认预取大小为 31 个块(~127k)——但这可能已经改变,而且它是一个可调参数。随着预取区域附近或末尾的页面被触摸,更多的页面被异步预取。
如果您暗示MADV_RANDOM
过madvise
,预取“不太可能发生”,在 Linux 下,这会完全禁用预取。
另一方面,给出MADV_SEQUENTIAL
提示将从映射的开头开始“更积极地”异步预取(并且可能会更快地丢弃访问的页面)。在 Linux 下,“更积极”意味着正常数量的两倍。
给出MADV_WILLNEED
提示建议(但不保证)尽快加载给定范围内的所有页面(因为您说您要访问它们)。操作系统可能会忽略这一点,但在 Linux 下,它被视为命令而不是提示,直至进程的最大 RSS 限制和实现指定的限制(如果我没记错的话,是物理 RAM 量的 1/2 )。
请注意,这MADV_DONTNEED
可以说是在 Linux 下错误地实现了。提示不是以 POSIX 指定的方式解释的,即您暂时可以将页面换出,而是您的意思是要丢弃它们。这对只读映射页面没有太大影响(除了一个小的延迟,你说没关系),但这确实很重要对于其他一切。
特别是,MADV_DONTNEED
在操作系统懒惰地将不需要的页面写入磁盘后,使用 Thinking Linux 会释放不需要的页面,这不是事情的工作原理!您必须明确同步,或为惊喜做好准备。
在调用readahead
之前调用文件描述符mmap
(或者,之前已经读/写过文件),文件的内容实际上会立即在 RAM 中。
然而,这只是一个实现细节(统一的虚拟内存系统),并且会受到系统内存压力的影响。
调用mlock
将——假设它成功2——立即将请求的页面加载到 RAM 中。它会阻塞,直到所有页面都物理存在,并且您可以保证页面将保留在 RAM 中,直到您解锁它们。
1存在查询 (
mincore
) 特定范围内的任何或所有页面当前是否实际存在的功能,以及在没有任何硬性保证的情况下向操作系统提示您希望看到的情况的功能 ( madvise
),最后强制有限的页面子集出现在内存中的功能(mlock
)用于特权进程。
2它可能不会,因为缺乏特权和超过配额或存在的物理 RAM 量。
是的,mmap 创建了一个映射。它通常不会读取您映射到内存中的任何内容的全部内容。如果您希望这样做,您可以使用 mlock/mlockall 系统调用来强制内核将映射的内容读入 RAM(如果适用)。
是的。重点在于管理内存比将mmap
所有内容都放入内存更有效。
当然,在某些情况下,任何给定的实现都可能决定一次性读取整个文件更有效,但这对于调用mmap
.
默认情况下, mmap() 仅配置映射并返回(快速)。
Linux(至少)具有选项 MAP_POPULATE(参见“man mmap”),它完全符合您的问题。