如何为共享内存映射选择固定地址
我想在多个进程之间使用共享内存,并且希望能够继续使用原始指针(和 stl 容器)。
为此,我使用映射到固定地址的共享内存:
segment = new boost::interprocess::managed_shared_memory(
boost::interprocess::open_or_create,
"MySegmentName",
1048576, // alloc size
(void *)0x400000000LL // fixed address
);
选择此固定地址的好策略是什么?例如,我是否应该使用相当高的数字来减少堆空间耗尽的可能性?
I would like to use shared memory between several processes, and would like to be able to keep using raw pointers (and stl containers).
For this purpose, I am using shared memory mapped at a fixed address:
segment = new boost::interprocess::managed_shared_memory(
boost::interprocess::open_or_create,
"MySegmentName",
1048576, // alloc size
(void *)0x400000000LL // fixed address
);
What is a good strategy for choosing this fixed address? For example, should I just use a pretty high number to reduce the chance that I run out of heap space?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是一个难题。如果您要分叉单个程序来创建子程序,并且只有父程序和子程序将使用内存段,请确保在分叉之前映射它。子级将自动继承父级的映射,无需使用固定地址。
如果不是,那么首先要考虑的是您是否真的需要使用原始 STL 容器而不是 boost 进程间容器。您已经在使用 boost 进程间来分配共享内存段,这表明您使用 boost 没有任何问题,因此我能想到使用 STL 容器的唯一优点是您不必移植现有代码。请记住,为了使其能够使用固定地址,容器及其包含的指针(假设您正在使用指针容器)需要保存在共享内存空间中。
如果你确定这就是你想要的,你就必须想出一些方法让他们协商地址。请记住,操作系统可以拒绝您所需的固定内存地址。如果该地址处的页面已映射到内存或已分配,它将拒绝该地址。由于不同的程序在不同的时间分配了不同数量的内存,因此哪些页面可用、哪些页面不可用将因程序而异。
因此,您需要程序就内存地址达成共识。这意味着可能需要尝试并拒绝多个地址。如果启动后的某个时间有可能对新程序感兴趣,寻求共识就必须重新开始。该算法看起来像这样:
要提出 A 应该建议的地址,您可以让 A 映射一个非固定内存段,查看它映射到的地址,然后建议该地址。如果不满意,请绘制另一个部分并提出建议。您需要在某个时候取消映射这些段,但您无法立即取消映射它们,因为如果取消映射然后重新映射相同大小的段,操作系统可能会一遍又一遍地为您提供相同的地址。 请记住,您可能永远无法达成共识;无法保证所有进程的公共位置都有足够大的段。如果您的程序全部独立地使用几乎所有内存,例如它们由大量交换进行备份(尽管如果您足够关心性能以使用共享内存,希望您避免交换),则可能会发生这种情况。
上述所有内容都假设您处于相对受限的地址空间中。 如果您使用的是 64 位,则此方法可行。大多数计算机的 RAM + 交换空间将远远小于 64 位允许的大小,因此您可以将内存映射到一个非常远的固定地址,所有进程不太可能已经映射该地址。我建议至少 2^48,因为当前的 64 位 x86 处理器不会超出该范围(尽管指针是 64 位,但您只能插入 48 位允许的 RAM,仍然有大量内存)撰写本文时)。尽管智能堆分配器没有理由不能利用广阔的地址空间来减少其簿记工作,但要真正强大,您仍然需要建立共识。请记住,您至少希望地址是可配置的 - 即使我们很快没有那么多内存,从现在到那时其他人可能有相同的想法并选择您的地址。
要进行双向通信,您可以使用任何套接字、管道或其他共享内存段。您的操作系统可能提供其他形式的 IPC。但请认真考虑一下,您现在可能引入的复杂性比您仅使用 boost 进程间容器时必须处理的复杂性要高;)
This is a hard problem. If you are forking a single program to create children, and only the parent and the children will use the memory segment, just be sure to map it before you fork. The children will automatically inherit the mapping from their parent and there's no need to use a fixed address.
If you aren't, then the first thing to consider is whether you really need to use raw STL containers instead of the boost interprocess containers. That you're already using boost interprocess to allocate the shared memory segment suggests you don't have any problem using boost, so the only advantage I can think of to using STL containers would be so you don't have to port existing code. Keep in mind that for it to work with fixed addresses, the containers and what they contain pointers to (assuming you're working with containers of pointers) will need to be kept in the shared memory space.
If you're certain that it's what you want, you'll have to figure out some method for them to negotiate an address. Keep in mind that the OS is allowed to reject your desired fixed memory address. It will reject an address if the page at that address has already been mapped into memory or allocated. Because different programs will have allocated different amounts of memory at different times, which pages are available and which are unavailable will vary across your programs.
So you need for the programs to gain consensus on a memory address. This means that several addresses might have to be tried and rejected. If it's possible that sometime after startup a new program will become interested, the search for consensus will have to start over again. The algorithm would look something like this:
To come up with what addresses A should propose, you could have A map a non-fixed memory segment, see what address it's mapped at, and propose that address. If it's unsatisfactory, map another segment and propose it instead. You will need to unmap the segments at some point, but you can't unmap them right away because if you unmap then remap a segment of the same size chances are the OS will give you the same address back over and over. Keep in mind that you may never reach consensus; there's no guarantee that there's a large enough segment at a common location across all the processes. This could happen if your programs all independently use almost all memory, say if they are backed up by a ton of swap (though if you care enough about performance to use shared memory hopefully you are avoiding swap).
All of the above assumes you're in a relatively constrained address space. If you're on 64-bit, this could work. Most computers' RAM + swap will be far less than what's allowed by 64-bits, so you could put map the memory at a very far out fixed address that all processes are unlikely to have mapped already. I suggest at least 2^48, since current 64-bit x86 processors don't each beyond that range (despite pointers being 64-bits, you can only plug in as much RAM as allowed by 48-bits, still a ton at the time of this writing). Although there's no reason a smart heap allocator couldn't take advantage of the vastness of the address space to reduce its bookkeeping work, so to be truly robust you would still need to build consensus. Keep in mind that you will at least want the address to be configurable -- even if we don't have that much memory anytime soon, between now and then someone else might have the same idea and pick your address.
To do the bidirectional communication you could use any of sockets, pipes, or another shared memory segment. Your OS may provide other forms of IPC. But strongly consider that you are probably now introducing more complexity than you would have to deal with if you just used the boost interprocess containers ;)
从配置文件中读取地址。这将允许轻松进行实验,并可以根据情况的变化轻松更改地址。
Read the address from a configuration file. That will allow easy experimentation, and make it easy to change the address as the circumstances change.
出于安全原因,请勿使用硬编码的绝对地址作为共享内存区域,即使您不使用分叉或线程。这会绕过所有 ASLR 保护。它使任何攻击者都可以预测进程地址空间中的位置。在二进制文件中搜索此类硬编码指针非常容易。
http://reversingonwindows.blogspot.sg/2013/12 选择了您/hardcoded-pointers.html 作为如何降低软件安全性、绕过 ASLR 的示例。
第二个坏例子是在
地址空间需要在运行时在通信双方之间进行协商。
Don't use hard-coded absolute addresses as shared memory area for security reasons, even when you don't uses forks or threads. This bypasses all ASLR protections. It enables any attacker predictable locations in the process' address space. It is pretty easy to search for such hard-coded pointers in a binary.
You've been choosen by http://reversingonwindows.blogspot.sg/2013/12/hardcoded-pointers.html as example how to make software less secure, bypassing ASLR.
The 2nd bad example is in the boost library.
The address space needs to be negotiated between the communicating parties at run-time.
我的解决方案:
初始化程序让系统选择合适的段地址。该地址被写入光盘并检索以供后续程序根据需要使用。
注意事项:
我使用 64 位 fedora 21 和 Kdevelop 4.7,发现“void*”的长度是 64 位。将段头地址写入光盘涉及
sprintf(bu, "%p", 指针);并写入文本文件:
恢复读取此文件并将十六进制数字解码为“long long”值。这将返回给调用者,并在其中被转换为 (void*)
我还发现将所有访问例程分组到一个文件夹中
高于各个流程的级别(每个流程本身就是一个项目)帮助我保持了理智,但代价是流程文件中出现了一个异常的“#include”
David N Laine
My solution:
The initialising program allows the system to select an appropriate segment address. This address is written to disc and retrieved for use by subsequent programs as required.
Caveats:
I am using 64 bit fedora 21 with Kdevelop 4.7 and find that 'void*' is 64 bits long. Writing to disc of the segment head address involves
sprintf(bu, "%p", pointer); and writing a text file:
Recovery reads this file and decodes the hex number as a 'long long' value. This is returned to the caller where it is cast as (void*)
I have also found that grouping all the access routines into a single folder
above the level of the individual processes (each as a project in its own right) has helped save my sanity at the expense of a single aberrant '#include' in the process files
David N Laine