如何为共享内存映射选择固定地址

How to choose a fixed address for shared memory mapping

本文关键字:选择 地址 映射 内存 共享      更新时间:2023-10-16

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

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

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

选择这个固定地址的好策略是什么? 例如,我是否应该只使用一个相当高的数字来减少堆空间不足的可能性?

这是一个

难题。如果要分叉单个程序以创建子程序,并且只有父程序和子程序将使用内存段,请确保在分叉之前映射它。子项将自动从其父级继承映射,无需使用固定地址。

如果不是,那么首先要考虑的是是否真的需要使用原始 STL 容器而不是 boost 进程间容器。您已经在使用 boost interprocess 来分配共享内存段,这表明您在使用 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。但强烈建议您现在引入的复杂性可能比您只使用提升进程间容器时必须处理的复杂程度更高;)

从配置文件中读取地址。 这将允许轻松进行实验,并随着情况的变化轻松更改地址。

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

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

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

我的解决方案:

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

警告:我将 64 位 Fedora 21 与 Kdevelopment 4.7 一起使用,发现"void*"有 64 位长。写入段头地址的光盘涉及sprintf(bu, "%p", pointer);并写入文本文件:

恢复读取此文件并将十六进制数解码为"长长"值。这将返回到调用方,在那里它被强制转换为 (void*)

我还发现将所有访问例程分组到一个文件夹中在各个进程的级别之上(每个进程本身都是一个项目)以帮助以牺牲进程文件中单个异常的"#include"为代价来挽救我的理智

大卫·莱恩