【问题标题】:Unix: sharing already-mapped memory between processesUnix:在进程之间共享已经映射的内存
【发布时间】:2023-03-19 00:33:01
【问题描述】:

我有一个预先构建的用户空间库,其中有一个 API 类似于

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

这个想法是我的代码从库中请求一个缓冲区,用东西填充它,然后将其交还给库。

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

有没有办法共享已经为进程映射的内存?例如:

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

[other process]
myLocalBuf = openTheSharedMemory (shmName);

这样其他进程可以直接写入库的缓冲区。 (进程之间的同步已经处理好了,所以没有问题)。

【问题讨论】:

    标签: memory ipc shared


    【解决方案1】:

    允许此功能有充分的理由,尤其是从安全方面。 “share this mem” 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("名称",...,O_CREAT);
    查找 | id = shmget(key,...); fd = open("名称",...);
    访问 | addr = shmat(id,...); addr = mmap(...,fd,...);
                  |
    全球手柄 | IPC 密钥文件名
    本地句柄 | SHM ID 号文件描述符号
    内存位置 |由 shmat() 创建由 mmap() 创建

    即关键是您正在寻找的“句柄”,以与传递文件名相同的方式传递,然后IPC连接的双方都可以使用该密钥来检查共享资源是否存在,以及在访问时(附加到句柄)的内容。

    【讨论】:

    • 感谢您的回复。我正在开发一个嵌入式系统,该系统具有多个通过 IPC 消息进行通信的进程。这对于大多数需要来回传递小消息的操作非常有效,但进程也需要相互传递大量数据缓冲区。因此,出于性能原因,我希望有一个特殊的“快捷方式”,我可以在消息中传递一些共享内存句柄,而不必复制整个缓冲区(通过管道或另一个中间共享内存段)。
    • 查看编辑;要传递的句柄是 SHM IPC 密钥。然后所有用户/共享者使用 shmget/shmat 来获取缓冲区地址(在他们的地址空间内)。
    • 感谢 FrankH。不幸的是,这两种方法都要求我成为内存的创建者,但我需要使不透明库创建的内存可共享。
    • 如果您可以在您的平台上控制内核,那么编写一个设备驱动程序来实现这一点,例如ioctl() 调用,将 PID 和 [start, end] 范围作为参数。您实际上是在 /proc/PID/mem 上重新实现 mmap() - 正如出于安全原因所述,它在大约十年前已从内核中删除;有关详细信息,请参阅securiteam.com/unixfocus/6V00F206AA.html。这不是不可能的,只是不是一个好主意......
    【解决方案2】:

    在进程之间共享内存的一种更现代的方法是使用POSIX shm_open() API

    本质上,它是一种将文件放在 ramdisk (tmpfs) 上的可移植方式。所以一个进程使用shm_open加上ftruncate加上mmap。其他使用shm_open(同名)加上mmap加上shm_unlink。 (如果有两个以上的进程,最后一个mmap它可以取消链接。)

    这样共享内存会在最后一个进程退出时自动回收;无需显式删除共享段(与 SysV 共享内存一样)。

    不过,您仍然需要修改您的应用程序以通过这种方式分配共享内存。

    【讨论】:

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

    至少理论上,你可以记录你从你的lib中获得的缓冲区的内存地址,并让其他进程mmap /proc/$PID_OF_FIRST_PROCCESS/mem 文件以该地址作为偏移量。

    我还没有测试过它,我不确定 /proc/PID/mem 是否真的实现了 mmap 文件操作,并且有很多安全考虑,但它可能会起作用。祝你好运:-)

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    • 2012-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    相关资源
    最近更新 更多