我试图弄清楚打开一个长度非常大的文件的内存映射是否是未定义的行为。我的用例是,我希望能够在附加文件(通过系统write
调用)时看到新数据,而不使用mremap
.系统调用的手册页mmap
指出,如果“我们不喜欢 addr、长度或偏移量(例如,它们太大,或未在页边界上对齐)”,则它返回 EINVAL。但这显然非常不清楚。具体来说,它没有明确说明内存映射大小超过文件大小时的行为。当我编写一个程序,打开一个比支持文件大得多的内存映射,向其中写入一个值,然后立即读回该值时,我发现该mmap
调用不会返回错误,并且该值实际上是正确写入/读取内存。但是,该文件并未更改。我在没有优化的情况下进行了编译,并使用了一小段内联汇编,它什么也不做,只是将有问题的值标记为破坏,以防止此加载和存储被优化。手册页指出SIGBUS
在这种情况下应该发出,但据我所知它不是。
虽然这意味着mmap
以这种方式使用可能适用于我在不重新创建内存映射的情况下跟踪文件更改的用例,但它确实提出了这样的问题:这是有意的还是偶然的。我不想依赖 Linux 内核无法保证的行为,特别是如果信息未正确同步到磁盘或从磁盘同步,可能会导致以后出现严重错误。这种行为是否在某处定义或保证?
答案1
答案就(隐藏)在mmap(2) 页面的NOTES 部分:
A file is mapped in multiples of the page size. For a file that
is not a multiple of the page size, the remaining memory is zeroed
when mapped, and writes to that region are not written out to the
file.
但是,该文本可能会更清晰一些,并且页面中的讨论SIGBUS
根本不清楚。我有改变了上面的文字要读:
A file is mapped in multiples of the page size. For a file that
is not a multiple of the page size, the remaining bytes in the
partial page at the end of the mapping are zeroed when mapped, and
modifications to that region are not written out to the file.
我已将描述更改为SIGBUS
:
SIGBUS Attempted access to a page of the buffer that lies beyond
the end of the mapped file. For an explanation of the
treatment of the bytes in the page that corresponds to the
end of a mapped file that is not a multiple of the page
size, see NOTES.
答案2
行为已指定,但未指定Linux 联机mmap
帮助页, 相当在 POSIX 中:
系统应始终对对象末尾的任何部分页面进行零填充。此外,系统绝不会写出对象最后一页超出其末尾的任何修改部分。起始地址范围内的引用帕并继续为伦对象末尾之后的字节到整个页面将导致 SIGBUS 信号的传送。
如果您的mmap
文件的MAP_SHARED
映射大于文件的大小,并尝试写入超出文件末尾的内存,则是SIGBUS
即使映射本身涵盖了范围(检查/proc/.../maps
),也可能会得到 a 。在上面的文字中,帕是 返回的地址mmap
,所以规范说的是访问内存中映射区域内但完全超出映射对象末尾的页面中的地址将导致SIGBUS
.在部分位于映射对象内的页面中进行读取和写入工作,这对应于通常由内存管理单元提供的保护。
除此之外,您的用例所需的行为未定义:
如果映射文件的大小在调用后发生变化映射() 作为对映射文件进行某些其他操作的结果,对与文件的添加或删除部分相对应的映射区域部分的引用的效果未指定。
还有一些与 Linux 上的页面缓存相关的其他微妙之处(请参阅手册页中的错误部分),并且这些可能会在上述之上产生令人惊讶的行为 - 在文件结尾之外但在映射的最后一个页面内写入的数据不会被写入文件,但可以保留到后续映射。
mmap
EINVAL
如果提供的值一般而言无效,且与所映射的文件没有特定关系,则在映射文件时返回:IE如果不满足对齐要求,或者偏移量和长度与内核可以管理的最大文件大小不兼容。