【问题标题】:How safe are memory-mapped files for reading input files?读取输入文件的内存映射文件有多安全?
【发布时间】:2014-02-12 17:50:45
【问题描述】:

将输入文件映射到内存中,然后直接从映射的内存页中解析数据,是一种从文件中读取数据的便捷高效的方式。

但是,除非您可以确保没有其他进程写入映射文件,否则这种做法似乎从根本上也不安全,因为如果底层文件被另一个进程写入,即使私有只读映射中的数据也可能发生变化。 (POSIX 例如doesn't specify“在建立 MAP_PRIVATE 映射后对底层对象所做的修改是否通过 MAP_PRIVATE 映射可见”。)

如果您想在映射文件存在外部更改的情况下确保代码安全,则必须仅通过易失性指针访问映射内存,然后非常小心读取和验证输入的方式,这对于许多用例来说似乎不切实际。

这个分析正确吗?内存映射 API 的文档通常只是顺便提及这个问题,如果有的话,所以我想知道我是否遗漏了什么。

【问题讨论】:

  • 在您使用文件时,其他进程可能会修改该文件。内存映射是使用文件的众多方式之一 - 它并不比其他任何方式更不安全。
  • 使用普通读取文件 API 将文件内容复制到内存缓冲区中始终是安全的,即使文件被同时修改。相反,如果您通过普通指针访问内存映射文件并且外部进程同时修改它,您会得到未定义的行为,因为这超出了 C(++) 内存模型的约束。通常,如果您或您的编译器期望在您的脚下不断变化的内存,就会发生不好的事情。您在某一时刻验证的输入可能在下一时刻变得无效。
  • 您误读了MAP_PRIVATE 的目的。这并不意味着给我一份私人副本,而是意味着我所做的修改对我来说是私人的。它与访问文件的任何其他方法具有所有相同的并发问题
  • @Petesh 我在哪里写或暗示MAP_PRIVATE 的意思是“给我一份私人副本”?我实际上引用了规范中相反的部分。如果有一个选项确实可以确保映射页面一旦被访问就不会被其他进程更改,那就太好了。
  • @StephanTolksdorf 这似乎是你问题的本质。而“确保映射的页面一旦被访问就不会被其他进程更改的选项”是通过写入到页面来完成的。 mmap 只不过是一种方便的读/写

标签: c++ c windows posix memory-mapped-files


【解决方案1】:

这不是一个真正的问题。

是的,当您映射文件时,另一个进程可能会修改该文件,是的,您可能会看到这些修改。甚至有可能,因为几乎所有操作系统都有统一的虚拟内存系统,所以除非有人请求无缓冲写入,否则不经过缓冲区缓存就无法写入,如果没有人持有映射就无法写入看到变化。
这甚至不是一件坏事。实际上,如果您看不到更改会更令人不安。由于文件在映射时准成为地址空间的一部分,因此您可以看到文件的更改是完全合理的。

如果您使用传统的 I/O(例如 read),在您阅读文件时仍然有人可以修改它。换句话说,将文件内容复制到内存缓冲区并不在存在修改时总是安全的。它是“安全的”,因为 read 不会崩溃,但它确实保证您的数据是一致的。
除非您使用readv,否则您无法保证任何原子性(即使使用readv,您也无法保证内存中的内容与磁盘上的内容一致,或者在两次调用@ 之间不会改变987654325@)。有人可能会在两次 read 操作之间修改文件,甚至在您进行操作时修改文件。
只是没有正式保证但“可能仍然有效”的东西 - 相反,例如在 Linux 下写入明显不是原子的。甚至不是偶然的。

好消息:
通常,进程只是打开一个任意随机文件并开始写入。当这样的事情发生时,它通常要么是属于进程的众所周知的文件(例如日志文件),要么是您明确告诉进程写入的文件(例如保存在文本编辑器中),或者是进程创建一个新文件(例如,编译器创建一个目标文件),或者该过程只是追加到现有文件(例如,db journals,当然还有日志文件)。或者,一个进程可能会自动将一个文件替换为另一个文件(或取消链接)。

在任何情况下,整个可怕的问题都归结为“没有问题”,因为要么您很清楚将会发生什么(因此这是您的责任),要么它可以无缝运行而不会受到干扰。

如果您真的不喜欢另一个进程可能在您映射文件时写入文件的可能性,您可以在 Windows 下创建文件句柄时简单地省略 FILE_SHARE_WRITE。 POSIX 使它有点复杂,因为您需要fcntl 强制锁的描述符,这不是每个系统都必须支持或 100% 可靠的(例如,在 Linux 下)。

【讨论】:

  • 我不关心读取或写入的原子性。当您使用read 将文件内容复制到内存缓冲区时,您可以在之后验证读取的输入,然后确保 缓冲区中的内容 保持有效。当一个输入文件被映射到内存时,它通常被视为一个常量缓冲区,并通过普通指针直接访问和解析。如果这个可能是常量的内存被外部进程更改,它会破坏编译器和开发人员的预期,从而导致未定义的行为。
  • 没错,如果这真的有问题,您仍然可以对匿名映射(或堆分配块)执行memcpy。当然,您将失去内存映射的主要优势之一:数据不再“神奇”和“免费”可用。不过,我不会为复制而烦恼。问题是,您通常知道当您修改文件(或代表您的程序)时,这不仅仅随机发生在随机文件上。
  • @Damon 这是一个源文件。人们确实会在意想不到的时刻打开它们,而人们确实会在您最意想不到的时候将它们写回去。
  • @JamesKanze:你怎么知道问题是关于源文件的?我错过了什么吗?
  • @Damon 他谈到了解析。所以这是他的程序的来源。 (但确实一个人解析了很多东西……我之前解析过日志文件。)
【解决方案2】:

理论上,如果有人这样做,您可能会遇到真正的麻烦 在阅读时修改文件。在实践中:你是 读取字符,仅此而已:没有指针或任何东西 这可能会给你带来麻烦。在实践中......正式地, 我认为它仍然是未定义的行为,但它是一种 我认为你不必担心。除非修改 非常小,你会得到很多编译器错误,但那是 快结束了。

可能导致问题的一种情况是,如果文件是 缩短。我不确定当你阅读时会发生什么 结束之后。

最后:系统不会随意打开和 修改文件。这是一个源文件;这将是一些白痴 做这件事的程序员,他应得的。在没有 情况下您未定义的行为是否会破坏系统或其他 人民档案。

还要注意,大多数编辑都在私人副本上工作;当。。。的时候 回写,他们通过重命名原件来做到这一点,并创建 一个新文件。在 Unix 下,一旦你打开文件到mmap 它,所有重要的是inode编号。而当编辑 重命名或删除文件,您仍然保留您的副本。这 修改后的文件将获得一个新的 inode。你唯一需要做的 担心的是如果有人打开文件进行更新,然后 到处修改它。没有多少程序在文本上这样做 文件,除了在末尾附加额外的数据。

所以虽然正式,但有一些风险,我认为你不必这样做 担心它。 (如果你真的很偏执,你可以关掉 在mmaped 时写授权。如果有 真的是敌方特工来抓你的,他可以马上把它转回来 开。)

【讨论】:

  • 我认为 OP 并不是在谈论读取源文件本身,而是程序在运行时可能读取的任何文件。因此,您关于编译器错误的观点似乎不合时宜。
  • @MarcClaesen 他提到了解析。编译器错误可能有点严重,但无论何时解析文本,都需要能够以某种方式处理输入中的错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-25
  • 2016-02-23
  • 2020-11-06
  • 2011-12-23
  • 1970-01-01
相关资源
最近更新 更多