是否可以在没有继承父进程的虚拟内存空间的情况下派生进程?

问题描述 投票:8回答:4

由于父进程使用大量内存,fork可能会在内核过量使用策略的某些配置下使用errnoENOMEM失败。即使子进程可能只有exec低内存消耗程序,如ls。

为了澄清这个问题,当/ proc / sys / vm / overcommit_memory配置为2时,(虚拟)内存的分配仅限于SWAP + MEMORY * ration(default to 50%)。当一个进程分叉时,由于COW没有复制虚拟内存。但是内核仍然需要分配虚拟内存空间。作为类比,fork类似于malloc(虚拟内存空间大小),它不会分配物理内存,写入共享内存将导致分配虚拟内存和物理内存的副本。当overcommit_memory配置为2时,由于虚拟内存空间分配,fork可能会失败。

在以下条件下,fork是否可以在没有父进程的继承虚拟内存空间的情况下进行?

  1. 如果孩子进程在exec之后调用fork
  2. 如果子进程没有调用exec并且不会使用父进程中的任何全局或静态变量。例如,子进程只是执行一些日志记录然后退出。
c linux fork enomem
4个回答
7
投票

不,这是不可能的。您可能对vfork(2)感兴趣,我不推荐。再看看mmap(2)及其MAP_NORESERVE旗帜。但是copy-on-write技术被内核使用,因此实际上你不会将RAM消耗加倍。

我的建议是有足够的交换空间,不要担心这样的问题。因此,将计算机设置为具有比最大运行进程更多的可用交换空间。您可以随时创建一些临时交换文件(例如使用dd if=/dev/zero of=/var/tmp/swapfile bs=1M count=32768然后mkswap /var/tmp/swapfile)然后将其添加为临时交换区域(swapon /var/tmp/swapfile)并在您不再需要它时将其删除(swapoff /var/tmp/swapfilerm /var/tmp/swapfile)。

你可能不想交换像tmpfs这样的/tmp/文件系统,因为tmpfs文件系统是由交换空间备份的!

我不喜欢memory overcommitment,我禁用它(通过proc(5))。因人而异。


5
投票

我不知道有任何方法可以做(2),但对于(1)你可以尝试使用vfork,它将分叉一个新进程而不复制父进程的页表。但出于多种原因通常不建议这样做,包括因为它导致父母阻止直到孩子执行execve或终止。


4
投票

作为Basile Starynkevitch answered,这是不可能的。

但是,有一个非常简单和常用的解决方案,它不依赖于Linux特定的行为或内存过量使用控制:使用早期分叉的从属进程执行fork和exec。

让大型父进程创建一个unix域套接字并尽可能早地分叉一个slave进程,关闭slave中的所有其他描述符(重新打开STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO/dev/null)。我更喜欢数据报套接字的简单性和保证,尽管流套接字也可以工作。

在极少数情况下,让从属进程执行单独的专用小帮助程序是很有用的。在大多数情况下,这不是必需的,并且使安全设计更容易。 (在Linux中,您可以在使用Unix域套接字传递数据时包含SCM_CREDENTIALS辅助消息,并使用其中的进程ID来验证对等方使用/proc/PID/exe伪文件的身份/可执行文件。)

在任何情况下,从进程都将阻止从套接字读取。当另一端关闭套接字时,读/接收将返回0,从属进程将退出。

从进程接收的每个数据报描述了要执行的命令。 (使用数据报允许使用C字符串,用NUL字符分隔,没有任何转义等;使用Unix流套接字通常需要您以某种方式分隔“命令”,这反过来意味着转义命令组件字符串中的分隔符。)

从属进程创建一个或多个管道,并分叉子进程。此子进程关闭原始Unix套接字,用相应的管道端口替换标准流(关闭另一端),并执行所需的命令。我个人更喜欢在Linux中使用额外的close-on-exec套接字来检测成功执行;在错误的情况下,errno代码被写入套接字,因此slave-parent也可以可靠地检测到故障和确切的原因。如果成功,则slave-parent关闭不必要的管道末端,回复原始进程关于成功,其他管道结束为SCM_RIGHTS辅助数据。发送消息后,它会关闭其余的管道端,并等待新消息。

在原始过程方面,上述过程是顺序的;只有一个主题可能 执行 一次开始执行外部进程。 (您只需使用互斥锁序列化访问权限。)几个可以同时运行;它只是对序列化的从助手的请求和响应。

如果这是一个问题 - 它不应该是典型的情况 - 你可以通过为每个消息添加一个ID号(由父进程分配,单调增加)来复用连接。在这种情况下,您可能会在父端使用专用线程来管理与从属的通信,因为您当然不能同时从同一个套接字读取多个线程,并期望确定性结果。

对该方案的进一步改进包括为执行的进程使用专用进程组,设置限制(通过设置从进程的限制),以及使用特权从属执行命令作为专用用户和组。

特权从属情况是让父进程为其执行单独的辅助进程最有用的地方。在Linux中,双方都可以通过Unix域套接字使用SCM_CREDENTIALS辅助消息来验证对等体的身份(PID,以及ID,可执行文件),从而使实现强大的安全性变得相当简单。 (但请注意,/proc/PID/exe必须不止一次检查,以捕获恶意程序发送消息的攻击,快速执行相应的程序但使用命令行参数导致它很快退出,使其偶尔看起来像正确的可执行文件发出了请求,而描述符的副本 - 以及整个通信通道 - 控制着一个不愉快的用户。)

总之,原始问题可以解决,虽然提出的问题的答案是否定的。如果执行是安全敏感的,例如更改权限(用户帐户)或功能(在Linux中),那么设计必须谨慎考虑,但在正常情况下,实施是非常直截了当的。

如果有必要,我很乐意详细说明。


1
投票

这在Linux上是可行的。使用没有标志clone和标志CLONE_THREADCLONE_VM系统调用。父进程和子进程将使用相同的映射,就像线程一样;没有COW或页表复制。


0
投票
madvise(addr, size, MADV_DONTFORK)

或者,您可以在fork()之后调用munmap()来删除从父进程继承的虚拟地址。

© www.soinside.com 2019 - 2024. All rights reserved.