首页 文章

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

提问于
浏览
4

由于父进程使用大量内存, fork 可能会在内核过量使用策略的某些配置下使用 errno ENOMEM 失败 . 尽管子进程可能只有ls等低内存消耗程序 .

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

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

  • 如果子进程在 fork 之后调用 exec

  • 如果子进程未调用 exec ,并且不会使用父进程中的任何全局或静态变量 . 例如,子进程只执行一些日志记录然后退出 .

3 回答

  • 3

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

  • 6

    不,这是不可能的 . 您可能对vfork(2)感兴趣,我不推荐 . 另请参阅mmap(2)及其 MAP_NORESERVE 标志 . 但是内核使用了copy-on-write技术,因此实际上不会使RAM消耗增加一倍 .

    我的建议是 have enough swap space 不要担心这个问题 . 因此,将计算机设置为具有比最大运行进程更多的可用交换空间 . 您可以随时 create some temporary swap file (例如 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)) . 因人而异 .

  • 6

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

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

    让大型父进程创建一个unix域套接字并尽可能早地分叉一个从进程,关闭从属中的所有其他描述符(重新打开 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也可以可靠地检测到故障和确切的原因 . 如果成功,奴隶父母关闭不必要的管道结束,回复原始过程关于成功,其他管道结束为SCM_RIGHTS辅助数据 . 发送消息后,它会关闭其余的管道端,并等待新消息 .

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

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

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

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

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

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

相关问题