POSIX C 中 fork() 的更轻量级替代品?

Lighter weight alternatives to fork() in POSIX C?

在我阅读的手册页中,popen、system 等似乎倾向于调用 fork()。反过来,fork() 复制进程的整个内存状态。这看起来真的很重,尤其是在许多情况下,如果调用 fork() 的子进程使用了​​很少的分配给父进程的内存。

所以,我的问题是,我能否在不复制父进程的整个内存状态的情况下获得类似 fork() 的行为?还是我遗漏了什么,例如 fork() 并不像看起来那么重(比如,可能会优化调用以避免不必要的内存重复)?

fork(2) is, as all syscalls, a primitive operation (but some C libraries use clone(2) 为它),从用户的角度来看-space 应用程序。它主要是一条机器指令 SYSCALLSYSENTER 从用户模式切换到内核模式,然后(最新版本的)Linux 内核正在做相当重要的处理。

它在实践中非常有效(例如不到一毫秒,有时甚至不到十分之一)因为内核在覆盖共享页面时广泛使用惰性copy-on-write techniques to share pages between parent & child processes. The actual copying would happen later, on page faults

forking has a huge advantage, since the starting of some other program is delegated to execve(2)概念上简单:父进程和子进程之间的唯一区别是fork

的结果

POSIX 系统上的顺便说一句,例如 Linux、fork(2) or the suitable clone(2) equivalent is the only way to create a process (there are some few weird exceptions that you should generally ignore: the kernel is making some processes like /sbin/init etc...), since vfork(2) 已过时。

问题是运行标准链接可执行文件的main函数,你需要调用execve,exec会替换整个进程映像,所以你需要一个新地址space,这就是 fork 的用途。

您可以通过让您的 calee 在共享库中公开它的 main 功能来解决这个问题(但它不能被称为 main),然后您可以使用 main 功能而无需分叉(前提是没有符号冲突)。

这将是比 system 更有效的替代方法(基本上是函数调用的效率)。 现在 popen 涉及管道,要使用管道,您需要将管道末端置于不同的可调度单元中。使用相同地址 space 的线程可在此处用作单独进程的更轻量级替代方案。

正如您提到的,fork() 是一个有点疯狂的系统调用,由于历史原因一直存在。有一篇关于它的缺陷的好文章 here, and also this post 介绍了一些细节和潜在的解决方法。

虽然在 Linux fork() 上优化为内存使用写时复制,但它仍然不是“免费”的,因为:

  1. 它仍然需要做一些与内存相关的管理(新页表等)
  2. 如果您使用的是 RAII(例如在 C++ 或可能是 Rust 中),那么所有复制的对象都将被清理两次。这甚至可能导致逻辑错误(例如两次删除临时文件)。
  3. 父进程很可能会保留 运行ning,可能会修改它的大量内存,然后它 必须被复制。

备选方案似乎是:

  • vfork()
  • clone()
  • posix_spawn()

vfork() 是为执行 fork() 然后 execve() 到 运行 程序的常见用例而创建的。 execve() 将当前进程的 all 内存替换为一组新内存,因此如果您正要删除它,则复制父进程的内存毫无意义。

所以 vfork() 不会那样做。相反,它 运行s 在与父进程相同的内存 space 中并暂停它直到它到达 execve()vfork() 的 Linux 手册页说除了 vfork() 然后 execve() 之外做任何事情都是未定义的行为。

posix_spawn() 基本上是 vfork()execve().

的一个很好的包装

clone() 类似于 fork() 但允许您准确指定复制的内容(文件描述符、内存等)。它有很多选项,包括一个 (CLONE_VM),它让子进程在与父进程相同的地址 space 中处理 运行,这非常疯狂!我想这是创建新进程的最轻量级方法,因为它根本不涉及任何内存复制!

但实际上我认为在大多数情况下你应该:

  • 使用线程,或者
  • 使用posix_spawn().

(注意,我现在正在研究这个;我不是专家,所以我可能有一些错误。)