mmap
实现。我的原始问题是:在多个用户使用同一块共享内存时,
mmap()
是否可以返回相同的指针?
最终,每个进程地址空间中的地址映射到相同的物理内存。
问题是是否可以以某种方式使每个进程提供的地址相同。
这为什么很重要?
考虑共享包含指针的数据结构的情况。 如果地址映射是共享的,那么这些指针对两个进程都有效。 如果地址映射不共享,则指针可能指向写入它的进程的地址空间。 如果另一个进程试图读取它,就会触发访问冲突。
对于共享库,我们依赖于位置无关代码,但仍然可能存在需要加载器在加载时调整的指针。 (我不清楚这是否只做一次,还是每个进程都得到自己的私有副本,其中包含这样的指针,以适应其自己的地址空间)。
如果地址映射不共享,则对于数据,我们需要创建使用偏移而不是绝对指针的位置无关结构(不幸的首字母缩写) 或限制自己只使用不包含任何指针的数据结构。
在我的应用程序中,我有几个我希望共享的数据结构,它们实质上是向量。 我不希望将它们持久化或序列化/反序列化。我希望每个进程使用相同的物理内存。 基本上这是一个搜索问题 - 共享数据是干草堆,每个进程搜索自己的针。 由于各种原因,我们不希望它们成为线程,尽管这将完全消除此问题,因为线程确实共享地址空间。
我可以使用自定义分配器在共享内存中创建向量。例如:
SharedMemoryAllocator sharedAlloc(somePointerFromMmap);
class Foo
{
std::vector<Bar, SharedMemoryAllocator> A;
std::vector<Snafu, SharedMemoryAllocator> B;
Foo(SharedMemoryAllocator& sharedAlloc):
A(sharedAlloc)
B(sharedAlloc)
{
}
};
如果地址映射是共享的,则可以安全地使用这样的分配器来为整个结构分配空间:
unique_ptr<Foo> fubar = sharedAlloc.new(Foo(sharedAlloc));
因为使用*fubar
指向A和B的存储空间时,它们在相同的地址空间中。
另一方面,如果地址映射不共享,则只能安全地共享A和B所指向的基础内存块。
也就是说,每个进程必须拥有自己的本地Foo实例,其中每个向量都是通过连接到由分配器提供的共享内存块来构建的。这更具可移植性,但更加丑陋。
从技术上讲,在没有添加std::bless()和其他巫术的情况下,两者都不符合C ++规范。因此,我们无论如何都处于未定义(或平台定义)的行为状态。但是,'safe'使用malloc
也是如此(请参见上面的链接)。
据我所知,除了在调用时请求特定地址且调用不失败的情况外,Posix或Linux对
mmap()
返回的地址没有保证。因此,如果它偶尔能够工作,那么最好也只是未定义行为。通常,依赖未定义行为都是不好的选择,出于各种原因。
但也许实际上这是平台定义的,而不是未定义的?
正如提到的例外情况固定地址映射,但你如何让两个独立的进程事先达成一致,使用相同的安全地址呢?这里给出了一个可能的解决方案。
这表明您需要第二个共享内存段或其他IPC机制来共享分配给要使用的共享内存块的创建者的地址。
这是正确的方法吗?它是唯一的方法吗?
观看何时使用mmap MAP_FIXED?- MAP_FIXED 的主要合法用途是重新映射需要在加载库时相对于彼此具有相同的相对地址的不同类型的内存段。
然而,其他人正在使用我建议的方式。那个问题的另一个答案提到:
我发现其他一些人使用MAP_FIXED来做这件事,但我还没有找到可行的示例。
- 共享内存可能包含指针。
这种使用MAP_FIXED的方式是否被ASLR使其无法正常工作?
实际上,只有在非常特定的情况下才能共享地址空间:
例如,仅当:
您使用MAP_ANONYMOUS,并且
通过sendmsg/recvmsg将FD传递给不相关的进程
在这种情况下,MAP_FIXED_NOREPLACE会起作用吗?
您使用shm_open()还是open()是否有区别?
我查看了boost interprocess的代码,它似乎没有使用MAP_FIXED或sendmsg。看起来它是不支持的。
我目前尝试的方法是简化的:
进程A:
fd = open("foobar", O_RDWR);
void* address = mmap(nullptr, PROT_READ|PROT_WRITE, MAP_SHARED, fd);
(*address) = address;
过程 B:
fd = open("foobar", O_RDONLY);
void* addressRequested = readAddressFromFoobar();
void* address = mmap(addressRequested, PROT_READ, MAP_SHARED|MAP_FIXED, fd);
这个操作失败并返回 E_NOMEM 错误。 如果我将 addressRequested 替换为 nullptr,它就成功了,我可以访问相同的数据,但不能依赖于任何指针。
有人能够演示或链接到一种可行的方法吗? 或者明确解释为什么在当前的 Linux 系统中这永远不可能实现。
我很清楚我们可以使用内部偏移量而不是指针来共享对象,但这会失去很多方便性。STL 类型通常假定它们可以使用指针。如果可能的话,我不希望编写自己想使用的每个容器的版本。
显然,Boost 通过提供使用智能指针而不是原始指针的容器来解决这个问题。我之前没有意识到这一点。这是一个解决通用问题的好方法,但与此问题不同。找到一个权威的解释会对 Boost Interprocess 是否支持在进程之间共享包含指针的对象? 或者说是更好的问题提供更好的答案。
mmap
系统调用的手册页面?它可以回答你的问题。 - Sam Varshavchikmalloc.c
中有一个固定的基指针,可以经验性地适用于每个感兴趣的指针大小/平台。我不知道这是否总是安全的,或者仅在您替换malloc
且没有其他共享库调用mmap
时起作用。无论如何,我的答案只是试图在运行时发现一个合适的基础,而不是硬编码它并希望它永远不会改变。 - Useless