【问题标题】:How to choose a fixed address for shared memory mapping如何为共享内存映射选择固定地址
【发布时间】:2011-08-21 19:50:56
【问题描述】:

我想在多个进程之间使用共享内存,并且希望能够继续使用原始指针(和 stl 容器)。

为此,我使用了映射在固定地址的共享内存:

segment = new boost::interprocess::managed_shared_memory(
    boost::interprocess::open_or_create,
    "MySegmentName",
    1048576, // alloc size
    (void *)0x400000000LL // fixed address
);

选择这个固定地址的好策略是什么?例如,我应该只使用一个相当大的数字来减少我用完堆空间的机会吗?

【问题讨论】:

  • 如果您在 Windows 上,VMMap 可以帮助您选择,但您也应该意识到系统上不受您控制的其他应用程序可以将它们的 DLL 注入您的内存空间,从而使您做出的任何决定无效。
  • 您可能需要考虑尝试共享空间的应用程序之间的某种选举协议,它们可以在其中协商要在它们之间使用的公共地址。或者,只需使用正确的 boost::interprocess::shared_ptr<family> 对象。鉴于它们是有效的分段 + 偏移,它们的设计目的是尽可能获得可靠的最佳性能。应用程序是否对性能如此敏感以至于这种额外的间接查找很重要?
  • 另外,堆空间用完是不太可能的。当需要增加堆时,堆管理器会向操作系统请求额外的内存。堆不是(通常)一个大舞台,而是许多连接在一起的。
  • 或者,@Joseph Garvin 在我输入所有 cmets 时所说的话。 :-)
  • 为什么不使用 STL 分配器,它专门在特定的内存块中分配,给定起始地址和长度?然后,您可以在每个进程中使用 named 内存映射(产生任何地址)并将映射的地址+长度交给分配器。对我来说,这似乎比依赖和篡改固定地址更不容易失败。

标签: c++ boost shared-memory interprocess


【解决方案1】:

这是一个难题。如果你正在 fork 单个程序来创建子程序,并且只有父程序和子程序会使用内存段,请确保在 fork 之前对其进行映射。子节点会自动继承父节点的映射,无需使用固定地址。

如果您不是,那么首先要考虑的是您是否真的需要使用原始 STL 容器而不是 boost 进程间容器。您已经在使用 boost 进程间分配共享内存段表明您使用 boost 没有任何问题,所以我能想到的使用 STL 容器的唯一优势是您不必移植现有代码。请记住,要使用固定地址,容器和它们包含的指针(假设您正在使用指针容器)将需要保存在共享内存空间中。

如果您确定这是您想要的,您必须想出一些方法让他们协商地址。请记住,操作系统可以拒绝您想要的固定内存地址。如果该地址的页面已经映射到内存或分配,它将拒绝该地址。因为不同的程序会在不同的时间分配不同数量的内存,所以哪些页面可用,哪些页面不可用会因您的程序而异。

因此,您需要让程序就内存地址达成共识。这意味着可能必须尝试和拒绝多个地址。如果有可能在启动后的某个时间对新程序感兴趣,必须重新开始寻求共识。该算法看起来像这样:

  1. 程序 A 向所有其他程序建议内存地址 X。
  2. 其他程序以 true 或 false 响应以指示地址 X 的内存映射是否成功。
  3. 如果程序 A 收到任何错误响应,请转到 #1。
  4. 程序 A 向其他程序发送一条消息,让他们知道地址已经过验证并且可能已被使用。
  5. 如果新应用对数据感兴趣,它必须通知程序 A 它想要一个地址。
  6. 然后程序 A 必须告诉所有其他程序停止使用数据并转到 #1。

要提出 A 应该提出的地址,您可以让 A 映射一个非固定内存段,查看它映射到的地址,然后提出该地址。如果不满意,请映射另一个细分并提出建议。您将需要在某些时候取消映射这些段,但您不能立即取消映射它们,因为如果您取消映射然后重新映射相同大小的段,操作系统可能会一遍又一遍地给您相同的地址。 请记住,您可能永远无法达成共识;不能保证在所有进程的公共位置都有足够大的段。如果您的程序都独立使用几乎所有内存,例如如果它们由大量交换备份(尽管如果您足够关心性能以使用共享内存,希望您避免交换),则可能会发生这种情况。

以上所有内容都假设您处于相对受限的地址空间中。 如果您使用的是 64 位,这可以工作。大多数计算机的 RAM + 交换将远远小于 64 位允许的内存,因此您可以将内存映射到一个非常遥远的固定地址,所有进程都不太可能已经映射。我建议至少 2^48,因为当前的 64 位 x86 处理器都不会超出该范围(尽管指针是 64 位,但您只能插入 48 位允许的尽可能多的 RAM,在撰写本文的时间)。尽管智能堆分配器没有理由不能利用巨大的地址空间来减少其簿记工作,但要真正强大,您仍然需要建立共识。请记住,您至少希望地址是可配置的——即使我们很快就没有那么多内存,但从现在到那时,其他人可能会有同样的想法并选择您的地址。

要进行双向通信,您可以使用任何套接字、管道或其他共享内存段。您的操作系统可能会提供其他形式的 IPC。但是请强烈考虑,如果您只使用 boost 进程间容器,您现在可能会引入更多的复杂性;)

【讨论】:

  • 感谢这次精彩的讨论!我使用 STL 的主要动机是这将允许我使用“原始”指针而不是 boost::interprocess 使用的偏移指针——这将允许我在不牺牲性能的情况下使用共享内存。有没有办法让地址请求以非常高的概率工作 - 比如说,如果我把它放在 2^40,这应该不会对堆造成任何问题(假设 RAM + 交换空间远小于 1TB ) 并且可能总是成功?
  • @hrr:我不确定声称的性能损失是否值得因重新设计自己的系统而导致失败的可能性,是吗?显然,这是你的决定,但你写的代码越少,你犯的错误就越少。
  • @Matthieu M.:好点子,确实我的目标是减少代码...我希望通过使用固定映射地址会导致一个非常简单的解决方案(我描述了它@987654321 @)。在那里,我只需要将第二个模板参数传递给 std::vector ——因此所需的更改将非常少。
  • @hrr:嗯,极端情况下的性能可能是这样做的另一个原因 :) 我忘了解决 32 位与 64 位的区别。事实上,使用 64 位地址空间,您成功的机会将大大提高。我应该编辑我的帖子,提到不能保证在所有进程中都存在一个足够大小的未映射段——它在 64 位上几乎不可能不存在。我会选择高于 2^40。当前的 64 位处理器实际上只能扩展到 48 位内存。所以如果你使用 2^48,你的代码甚至可以在最大化的系统上工作:)
【解决方案2】:

从配置文件中读取地址。这将允许进行简单的实验,并且可以根据情况的变化轻松更改地址。

【讨论】:

  • 好主意!那么,我的主要问题是如何选择一个数字/地址放入配置文件中?
  • @hrr 有点实验。您可以使用各种工具来获取二进制文件的映射,这将允许您排除某些地址,但对于其余的,您必须稍微猜测一下。
  • 我说话太强了。它或多或少可能在不同的情况下工作:) 为了尽可能强大,你需要比这更复杂的东西。
  • @Joseph 我可能说得太快了。我们没有使用标准容器;我们需要一个散列映射,当时不可用,我们的“数据”和“键”要么是数值(doubleint),要么是非常短的固定长度字符串(char[]);唯一的动态分配是针对节点的,我们手动管理。所有这些都让事情变得容易多了。理论上,您应该能够使用自定义分配器对 STL 做一些事情;我确信它可以工作,但我不能声称实际上已经这样做了。
【解决方案3】:

出于安全原因,请勿使用硬编码的绝对地址作为共享内存区域,即使您不使用分叉或线程也是如此。这绕过了所有 ASLR 保护。它使任何攻击者都可以在进程的地址空间中预测位置。在二进制文件中搜索这种硬编码的指针非常容易。

http://reversingonwindows.blogspot.sg/2013/12/hardcoded-pointers.html 选择了您作为如何绕过 ASLR 来降低软件安全性的示例。 第二个不好的例子是boost library

地址空间需要在通信双方之间在运行时协商。

【讨论】:

    【解决方案4】:

    我的解决方案:

    初始化程序允许系统选择适当的段地址。该地址被写入磁盘并被检索以供后续程序根据需要使用。

    注意事项: 我在 Kdevelop 4.7 中使用 64 位 fedora 21,发现 'void*' 是 64 位长。写入段头地址的磁盘涉及 sprintf(bu,“%p”,指针);并编写一个文本文件:

    Recovery 读取此文件并将十六进制数解码为“long long”值。这被返回给调用者,它被转换为 (void*)

    我还发现将所有访问例程分组到一个文件夹中 高于单个流程的级别(每个流程本身就是一个项目)以牺牲流程文件中的单个异常“#include”为代价帮助挽救了我的理智

    大卫·N·莱恩

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-11
      • 1970-01-01
      • 2023-04-06
      • 2012-09-08
      • 1970-01-01
      • 2011-02-25
      • 1970-01-01
      相关资源
      最近更新 更多