在将共享内存对象映射到内存后,使用ftruncate是否安全?

8
  • shm_open()函数
  • 使用预定义大的length参数调用mmap()函数
  • 多次调用fork()函数
  • 可以任意调用ftruncate()函数

这样做的目的是确保由fork()函数生成的每个进程都有一个共享段在相同的地址上。然而,我不想一直占用内存,而是动态调整大小(大小范围为0 - 大length)。

这样做可行吗?会出现未定义行为吗?

3个回答

9
没问题。您可以随时截断底层文件,但是如果您访问文件边界之外的内存,则可能会收到 SIGBUS 信号。因此,您需要非常小心,不要触及当前文件长度之外的内存(或者捕获 SIGBUS 并处理它)。
来自man 2 mmap

使用映射区域可能导致以下信号:

SIGBUS 尝试访问与文件不对应的缓冲区一部分(例如超出文件末尾的情况,包括另一个进程截断文件的情况)。


这是我假设的情况,但是munmap可能会触发对脏页的写入,从而引发SIGBUS信号。 - Brian Cain
标记为已接受,因为它回答了我的问题。但是Damon的方法更易于管理(我可以在mmap范围的中间卸载页面)。 - Lorenzo Pistone
@nneonneo,我认为这不太对。我相信你需要在ftruncate之后进行mremap以更新映射。尽管如此,您仍应最初分配大尺寸以防止mremapENOMEM而失败。 - jleahy
@jleahy:实际上,我的观点是你不需要使用mremap,但访问文件末尾之后的内存可能会导致崩溃。在实践中,使用mremap更安全。 - nneonneo
@nneonneo 如果另一个进程共享文件映射,会怎样呢?那么一个进程可以通过将文件截短并在无辜进程中触发SIGBUS来对另一个进程发起拒绝服务攻击(Linux肯定不允许这种情况发生吧?) - Eloff
@Eloff:另一个进程根本不应该对文件具有(写入)访问权限。如果它确实有这样的权限,那么你可以做各种更糟糕的事情(例如,在内存中覆盖代码段以强制其他程序执行任意代码)。 - nneonneo

1

不要调整它的大小。

我不想一直占用内存

这就是内核使用虚拟内存为您完成的工作。只要您不使用 mlock()MAP_LOCKED,它将根据需要/适当地进行分页。


好的,RAM以及交换空间。我可能会在映射区域的中间某个位置使用一些内容(强制内核提交它),但之后我可以在其余运行时间里忘记它。因此,我认为我需要一种明确告诉系统我不再使用该地址空间部分的方法。 - Lorenzo Pistone
你可以使用 madvise(),但要注意行为没有保证。 - Brian Cain
我猜我会使用 MADV_REMOVE。但是如果我需要那些空间,我该怎么办? - Lorenzo Pistone
不了解更多细节的情况下,我认为您会更喜欢使用MADV_DONTNEED - Brian Cain

1

创建任意大小的映射,除非您实际使用它,否则它不会“占用RAM”。

如果您担心在使用完RAM后仍然保持其繁忙状态,请调用madvise(MADV_DONTNEED) - 这将清除页面并在再次访问时从零池中返回新页面。


在写入已标记为“MADV_DONTNEED”的范围之前,我不需要执行任何操作,对吗? - Lorenzo Pistone
请注意,零填充行为仅在后备存储不再具有这些页面的情况下发生(例如,在某些数据可能已被“ftruncate”丢弃的情况下)。 - Brian Cain
@LorenzoPistone:你不需要做任何事情。MADV_DONTNEED将丢弃页面并在需要时透明地从磁盘重新加载页面(如果由文件支持),或者(匿名映射)只是向您抛出一个零页面。请注意,后者实际上是“不正确”的行为,因为POSIX规定语义不会改变(这是相当明确的!)。但这正是你想要的。 - Damon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接