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

时间:2015-07-24 07:10:14

标签: c linux fork enomem

由于父进程使用大量内存,fork可能会因内核过量使用策略的某些配置errno ENOMEM而失败。即使子进程可能只有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并且不使用父进程中的任何全局或静态变量。例如,子进程只执行一些日志记录然后退出。

5 个答案:

答案 0 :(得分:8)

不,这是不可能的。您可能对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)并将其删除({ {1}}和swapoff /var/tmp/swapfile)当你不再需要它时。

您可能不希望交换rm /var/tmp/swapfile这样的/tmp/文件系统,因为 tmpfs 文件系统由交换空间备份!

我不喜欢tmpfs并禁用它(通过memory overcommitment)。 YMMV。

答案 1 :(得分:6)

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

答案 2 :(得分:4)

Asile 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中),那么设计具有需要仔细考虑,但在正常情况下,实施非常简单。

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

答案 3 :(得分:2)

这在Linux上是可能的。使用不带标志clone和带标志CLONE_THREAD的{​​{1}}系统调用。父进程和子进程将使用相同的映射,就像线程一样。没有COW或页面表复制。

答案 4 :(得分:0)

madvise(addr, size, MADV_DONTFORK)

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