Unix:在进程之间共享已映射的内存

15

我有一个预先构建好的用户空间库,它具有类似于以下的API:

void getBuffer (void **ppBuf, unsigned long *pSize);
void bufferFilled (void *pBuf, unsigned long size);

我的想法是,我的代码从库中请求一个缓冲区,填充一些内容,然后将其还给库。

我希望另一个进程能够填充这个缓冲区。我可以通过使用shm*/shm_* API创建一些新的共享缓冲区,让另一个进程填充它,然后将其复制到库的缓冲区中,但这会产生额外的(可能很大的)复制开销。

是否有一种方法可以共享已经为进程映射的内存?例如:

[local lib process]
getBuffer (&myLocalBuf, &mySize);
shmName = shareThisMemory (myLocalBuf, mySize);

[other process]
myLocalBuf = openTheSharedMemory (shmName);
那样,其他进程可以直接写入库的缓冲区。 (进程之间的同步已经得到处理,因此没有问题)。
3个回答

11

有很好的理由允许此功能,特别是从安全方面考虑。"分享这个内存"的API将破坏访问权限系统。

假设一个应用程序在内存中保存了某种关键/敏感信息; 应用程序链接(例如使用共享库、预加载、修改后的链接器/加载程序)到外部组件,这些组件仅仅为了好玩而决定“分享地址空间”。这将是一场自由竞技,一种绕过任何数据访问权限/限制的方法。你会挤入应用程序。

对于您的用例来说并不好,但从系统/应用程序完整性的角度来看是合理的。尝试搜索网页以了解为什么通常不希望进行这种访问(例如:/proc/pid/mem mmap漏洞)。

如果您使用的库旨在允许此类共享访问,则必须自行提供钩子以分配此类共享缓冲区或使用其他地方预分配的(可能共享的)缓冲区。

编辑:要明确这一点,进程边界明确是关于不共享地址空间(以及其他事情)。
如果您需要共享地址空间,请使用线程(那么整个地址空间都是共享的,永远不需要“导出”任何东西),或者显式设置共享内存区域,就像您设置共享文件一样。

从后者的角度来看,两个O_EXCL打开的进程会共享文件的访问权限。但是,如果一个进程已经以O_EXCL方式打开了它,则“使其共享”的唯一方法(可供另一个进程打开)是首先将其close(),然后再次open()它,而不使用O_EXCL。如果您已经以这种方式打开了一个文件,没有其他方法可以从中删除独占访问权,而只能先关闭它。
正如没有办法除非先取消映射它,否则无法删除被映射为独占访问的内存区域 - 对于进程的内存来说,默认情况下是使用MAP_PRIVATE,原因充分。

更多信息:一个进程共享的内存缓冲区与进程共享的文件其实并没有太大不同;使用SysV-IPC风格的语义,你可以有:

              | SysV IPC共享内存            文件
==============+===================================================================
创建          | id = shmget(key,..., IPC_CREAT);  fd = open("name",...,O_CREAT);
查找          | id = shmget(key,...);             fd = open("name",...);
访问          | addr = shmat(id,...);             addr = mmap(...,fd,...);
              |
全局句柄      | IPC key                           文件名
本地句柄      | SHM ID号                         文件描述符号码
内存位置      | 由shmat()创建                    由mmap()创建

也就是说,密钥是您要寻找的“句柄”,以与传递文件名相同的方式传递它,然后IPC连接的两端都可以使用该密钥检查共享资源是否存在,并通过它访问(附加到句柄)内容。


谢谢回复。我正在开发一个嵌入式系统,其中有多个进程通过IPC消息进行通信。这对于大多数需要传递小消息的操作非常有效,但是进程还需要相互传递大缓冲区的数据。因此出于性能原因,我希望有一种特殊的“快捷方式”,可以在消息中传递一些共享内存句柄,而不必复制整个缓冲区(通过管道或另一个中间共享内存段)。 - gimmeamilk
请传递SHM IPC密钥句柄。所有用户/共享者都使用shmget/shmat获取缓冲区地址(在其地址空间内)。 - FrankH.
谢谢FrankH。不幸的是,这两种方法都要求我成为内存的创建者,但我需要使由不透明库创建的内存可共享。 - gimmeamilk
1
如果您可以控制平台上的内核,则编写一个设备驱动程序,通过例如ioctl()调用来实现此功能,将PID和[start,end]范围作为参数。您本质上将在/proc/PID/mem上重新实现mmap() - 这个函数因安全原因在近十年前从内核中删除,详情请参见http://www.securiteam.com/unixfocus/6V00F206AA.html。虽然这不是不可能实现的,但并不是一个好主意... - FrankH.
@FrankH。以下链接已失效:www.securiteam.com/unixfocus/6V00F206AA.html - Semnodime
web.archive.org在这种情况下非常有帮助;我提供的链接报告也仍然可以在https://seclists.org/vulnwatch/2002/q4/93上找到。 - FrankH.

5
一种更现代的进程间共享内存的方式是使用POSIX shm_open() API。实质上,它是在ramdisk(tmpfs)上放置文件的可移植方式。因此,一个进程使用shm_openftruncatemmap,另一个进程使用相同名称的shm_openmmapshm_unlink。(有多个进程时,最后一个使用mmap的进程可以unlink它。)
这样,当最后一个进程退出时,共享内存将自动回收;无需显式删除共享段(与SysV共享内存不同)。
但仍需要修改应用程序以以这种方式分配共享内存。

POSIX的shm_*()和旧式SysV-IPC-SHM之间的区别更多地在于管理方面,而不是实现/使用方面(无论您使用序列shmctl(); shmget(); shmat(); shmdt()还是shm_open(); ftruncate(); mmap(); shm_unlink()来创建/分配/映射/取消映射似乎取决于个人偏好)。虽然POSIX风格更符合通用UNIX“如果可能则制作<stuff>文件”的方法。 - FrankH.
@FrankH:实际上有很大的区别...使用SysV风格的shm,您必须在使用完后手动删除(IPC_RMID)段。而使用POSIX shm_*,您可以在仍在使用内存时shm_unlink文件。就像普通文件一样,未链接的段将继续存在,直到最后一个进程取消映射它,即使(例如)进程崩溃也会发生这种情况。使用SysV SHM,崩溃可能会留下共享内存"残留物"。如果尽早取消链接文件,则使用POSIX shm_*永远不会发生这种情况。 - Nemo
这就是我所说的“管理方面”。除此之外(使用POSIX文件系统语义进行清理),几乎没有什么区别。无论您是选择保留该段(因为,比如对于一个32GB数据库SGA来说,重新创建它需要一段时间,比零填充更长)还是不保留,都是一种“管理”选择。 - FrankH.
@FrankH:啊,明白了。好的。(当然,POSIX shm_让你选择如何管理共享内存;你可以通过不取消链接文件来使其持久化。)同意底层实现基本相同。 - Nemo

-1
理论上,您可以记录从lib获取的缓冲区的内存地址,并使另一个进程使用该地址作为偏移量映射/proc/$PID_OF_FIRST_PROCCESS/mem文件。
我没有测试过它,也不确定/proc/PID/mem实际上是否有mmap文件操作实现,而且还有很多安全考虑因素,但这可能有效。祝好运 :-)

/proc/$pid/mem 不支持 mmap。https://dev59.com/TW435IYBdhLWcg3wxDP_ - Nicholas Knight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接