【问题标题】:Is it possible to fork a process without inherit virtual memory space of parent process?是否可以在不继承父进程的虚拟内存空间的情况下分叉一个进程?
【发布时间】:2015-10-14 18:14:20
【问题描述】:

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

为了澄清问题,当 /proc/sys/vm/overcommit_memory 配置为 2 时,(虚拟)内存的分配限制为 SWAP + MEMORY * ration(default to 50%)。 当一个进程分叉时,由于COW,虚拟内存不会被复制。但是内核仍然需要分配虚拟内存空间。打个比方,fork 就像 malloc(virtual memory space size),不会分配物理内存,写入共享内存会导致复制虚拟内存,分配物理内存。当 overcommit_memory 配置为 2 时,fork 可能会因为虚拟内存空间分配而失败。

在下列情况下是否可以fork一个进程不继承父进程的虚拟内存空间?

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

  2. 如果子进程不调用exec 并且不会使用来自父进程的任何全局或静态变量。例如,子进程只是做一些记录然后退出。

【问题讨论】:

  • 我不太明白;这不是共享虚拟内存写时复制吗?因此,任何额外的内存实际上都是子进程私有的。不共享虚拟内存不会加剧问题吗?
  • @trojanfoe 当进程分叉时,由于 COW,虚拟内存不会被复制。但是内核仍然需要分配虚拟内存空间。打个比方,fork 就像 malloc(virtual memory space size),不会分配物理内存,写入共享内存会导致复制虚拟内存,分配物理内存。当 /proc/sys/vm/overcommit_memory 为 2 时,内存分配限制为 SWAP+MEMORY*ratio。因此,fork 可能会因 ENOMEM 失败。

标签: c linux fork enomem


【解决方案1】:

不,这是不可能的。你可能对vfork(2) 感兴趣,我不推荐。还要查看mmap(2) 及其MAP_NORESERVE 标志。但是内核使用了copy-on-write 技术,因此您实际上不会使内存消耗翻倍。

我的建议是有足够的交换空间,不要担心这样的问题。因此,将您的计算机设置为比最大的运行进程拥有更多的可用交换空间。您总是可以创建一些临时交换文件(例如,使用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))。 YMMV。

【讨论】:

  • 不要使用vfork()。它有一些严重的问题——严重到它从根本上被破坏了。在Linux上,它只阻塞调用thread,所以在一个多线程进程中,会有两个不同的process运行在同一个地址空间。如果子进程在运行时收到信号,它可能会破坏父进程。 ewontfix.com/7 中列出的一些 Linux 实现问题已得到解决,例如 setuid 进程的竞争条件,但由另一个进程在同一地址空间中运行所产生的基本问题仍然存在。
  • 假设堆是连续的,页表开销是每 2mb 4kb,并且 vfork() 没有任何问题,这使得 /bin/sh 和许多重要的东西运行得非常快。我知道并不是每个人都同意 Linux 的设计方式。他们可能想考虑使用 Windows NT,因为 Microsoft 和他们一样。
  • 交换文件和更多可用 RAM 无济于事,例如当一个 32 位应用程序 crashes with VSS=4G.
  • 在这种情况下,32 位 Linux 应用程序(因 VSS=4G 崩溃)需要从源代码重新编译,然后作为 64 位应用程序进行调试。阅读GCCGDB 的文档
【解决方案2】:

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

【讨论】:

  • 它会导致父进程阻塞,直到子进程执行 execve 或终止。 这并不总是正确的 - 在 Linux 上只有调用 线程 被阻塞。这可能更糟,因为这意味着两个不同的进程同时在同一个地址空间中运行。
  • 你是对的@AndrewHenle。请注意,将fork 与线程结合使用是危险的,最好避免。
【解决方案3】:

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

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

让大型父进程创建一个 unix 域套接字并尽早派生一个从属进程,关闭从属进程中的所有其他描述符(重新打开 STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO/dev/null)。我更喜欢数据报套接字,因为它简单且有保证,尽管流套接字也可以工作。

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

在任何情况下,从属进程都会阻塞从套接字读取。当另一端关闭socket时,read/receive返回0,slave进程退出。

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

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

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

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

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

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

总而言之,可以解决最初的问题,尽管对提出的问题的回答是否定的。如果执行是安全敏感的,例如更改权限(用户帐户)或功能(在 Linux 中),那么设计具有需要仔细考虑,但在正常情况下,实施非常简单。

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:
      madvise(addr, size, MADV_DONTFORK)
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-21
        • 2014-06-29
        • 2015-05-07
        • 2010-11-05
        • 2019-10-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多