为什么默认的进程创建机制是fork?

Ell*_*tus 51 process process-management fork

用于进程创建的 UNIX 系统调用 fork() 通过复制父进程来创建子进程。我的理解是,这之后几乎总是调用 exec() 来替换子进程的内存空间(包括文本段)。在 fork() 中复制父级的内存空间对我来说总是很浪费(尽管我意识到可以通过使内存段在写时复制从而只复制指针来最小化浪费)。无论如何,有谁知道为什么流程创建需要这种复制方法?

cjm*_*cjm 66

这是为了简化界面。替代forkexec将类似于 Windows 的CreateProcess函数。请注意有多少参数CreateProcess,其中许多是具有更多参数的结构。这是因为一切你可能想控制对新工艺将被传递到CreateProcess。事实上,CreateProcess没有足够的参数,所以微软不得不添加CreateProcessAsUserCreateProcessWithLogonW

使用该fork/exec模型,您不需要所有这些参数。相反,过程的某些属性会在exec. 这允许您fork,然后换你想要的任何进程属性(使用相同的功能,你会正常使用),并随后 exec。在 Linux 中,fork没有参数,execve只有 3 个:要运行的程序,要给它的命令行,以及它的环境。(还有其他exec函数,但它们只是execveC 库提供的用于简化常见用例的包装器。)

如果要使用不同的当前目录启动进程: fork, chdir, exec.

如果要重定向 stdin/stdout: fork、关闭/打开文件、exec.

如果要切换用户:fork, setuid, exec.

所有这些都可以根据需要进行组合。如果有人想出了一种新的流程属性,您不必更改forkexec

正如 larsks 提到的,大多数现代 Unix 使用写时复制,因此fork不涉及大量开销。

  • 很好的解释。“那些不了解 UNIX 的人注定要重新发明它,很糟糕。” ——亨利·斯宾塞 (18认同)
  • @StevenMonday,是的,但它在内核的初始化代码中,不能从外部访问。它不需要所有这些参数,因为几乎所有东西都是硬编码的。它只能创建进程 ID 1,也就是 init 进程。之后,进程只能通过分叉来创建。 (5认同)
  • 但是一定不能在 Unix 的某个地方有一些等同于 CreateProcess() 的东西吗?否则第一个进程是如何创建的?与神话中的创世神不同,第一个进程不能从虚无中 fork() 本身。;-) (3认同)
  • @StevenMonday,第一个过程是上帝手工制作的(内核) (3认同)
  • @Aki,不,CreateProcess() 实际上创建了一个新进程并从头开始构建它,没有分叉。 (2认同)
  • 我相信“写时复制”功能是使用 fork 的一个很好的理由:通过 fork 一个现有进程,如果它在很大程度上相似,您可以立即启动 fork 进程。如果有相似之处,则只需要将不同的部分写入不同的内存位置,而不是整个内容。(例如:一个 bash shell 分叉另一个来处理管道:两个 bash 进程中的大部分内容都相似,因此几乎不需要将任何内容复制到第二个进程的新内存位置。只需复制内存指针,无需复制程序二进制结束。因此它非常快)。 (2认同)

Kev*_*art 7

除了 cjm 的回答之外,Single Unix Specification 还定义了一个名为vfork(). 该函数的工作方式与 fork 类似,只是如果 fork 进程除了尝试调用 exec 系列函数或调用_exit().

因此,定义行为的唯一用途几乎是:

pid_t ret = vfork();
if(ret == 0)
{
    exec(...);
    _exit(EXIT_FAILURE); //in case exec failed for any reason.
}
Run Code Online (Sandbox Code Playgroud)

那么有什么作用vfork呢?它是一种物美价廉的fork。在没有写时复制的实现中,生成的进程将与原始进程共享内存空间(因此出现未定义的行为)。在带有 copy-on-write 的实现中,vfork允许与 相同fork(),因为 copy-on-write 实现速度很快。

还有一个可选posix_spawn函数(和一个posix_spawnp函数)可以直接创建一个新进程。(也可以通过使用forkand的库调用来实现它们exec,并提供了一个示例实现。)