为什么我们需要 fork 来创建新进程?

sar*_*hak 115 process fork architecture

在Unix中,每当我们想要创建一个新进程时,我们会fork当前进程,创建一个与父进程完全相同的新子进程;然后我们执行 exec 系统调用以将来自父进程的所有数据替换为新进程的所有数据。

为什么我们首先创建父进程的副本而不是直接创建新进程?

Mar*_*ick 74

简短的回答是,fork在 Unix 中是因为它很容易适应当时的现有系统,并且因为伯克利前身系统使用了分叉的概念。

来自Unix分时系统的演变(相关文字已突出显示):

现代形式的过程控制是在几天内设计和实施的。令人惊讶的是,它可以轻松融入现有系统;同时,很容易看出设计中一些稍微不寻常的特征是如何出现的,因为它们代表了对现有内容的微小、易于编码的变化。一个很好的例子是 fork 和 exec 函数的分离。创建新进程的最常见模型包括为进程指定一个程序来执行;在 Unix 中,分叉进程继续运行与其父进程相同的程序,直到它执行显式 exec。功能的分离当然不是Unix独有的,事实上它存在于汤普森所熟知的伯克利分时系统中. 尽管如此,假设它存在于 Unix 中似乎是合理的,主要是因为 fork 可以轻松实现而无需更改太多其他内容。系统已经处理了多个(即两个)进程;有一个进程表,进程在主内存和磁盘之间交换。只需要 fork 的初始实现

1) 工艺表的扩展

2)增加一个fork调用,将当前进程复制到磁盘交换区,使用已经存在的交换IO原语,并对进程表做了一些调整。

事实上,PDP-7 的 fork 调用正好需要 27 行汇编代码。当然,还需要对操作系统和用户程序进行其他更改,其中一些更改相当有趣且出乎意料。但是一个组合的 fork-exec 会复杂得多,如果仅仅因为 exec 不存在的话;它的功能已经由 shell 使用显式 IO 执行了。

自那篇论文以来,Unix 不断发展。fork其次exec不再是运行程序的唯一方法。

  • vfork 的创建是为了在新进程打算在 fork 之后立即执行 exec 的情况下成为更有效的 fork。做一次vfork后,父子进程共享同一个数据空间,父进程被挂起,直到子进程要么执行程序要么退出。

  • posix_spawn创建一个新进程并在单个系统调用中执行一个文件。它需要一堆参数,让您有选择地共享调用者的打开文件并将其信号处置和其他属性复制到新进程。

  • 不错的答案,但我想补充一点,不应再使用 vfork。现在性能差异很小,它的使用可能很危险。请参阅此 SO 问题 stackoverflow.com/questions/4856255/...,该站点 http://ewontfix.com/7/,以及关于 vfork 的“高级 Unix 编程”第 299 页 (10认同)
  • 使用 `posix_spawn()` 执行相同的 post-fork 重新管道工作所需的机制(数据结构设置)可以使用 `fork()` 和内联代码轻松完成,这为 `fork()` 提供了一个令人信服的论点使用更简单。 (4认同)

gol*_*cks 40

[我将从这里重复我的部分答案。]

为什么不直接创建一个从头开始创建新进程的命令呢? 复制一个只会马上被替换的东西,这不是荒谬和低效的吗?

事实上,由于以下几个原因,这可能不会那么有效:

  1. 产生的“复制”fork()有点抽象,因为内核使用写时复制系统;真正需要创建的只是一个虚拟内存映射。如果复制然后立即调用exec(),则大部分数据如果已被流程活动修改而将被复制,则实际上不必复制/创建,因为流程不执行任何需要使用它的事情。

  2. 子进程的各个重要方面(例如,其环境)不必根据对上下文的复杂分析等单独复制或设置。它们只是假设与调用进程的相同,并且这是我们熟悉的相当直观的系统。

为了进一步解释#1,至少在大多数情况下,从未真正复制过“复制”但随后从未访问过的内存。这种情况下的一个例外可能是,如果您分叉了一个进程,然后在子进程将自身替换为exec(). 我说可能是因为如果有足够的可用内存,可以缓存大部分父级,我不确定这会在多大程度上被利用(这取决于操作系统实现)。

当然,从表面上看,这并不会使使用副本比使用空白平板有效——除了“空白平板”实际上并不是什么,并且必须涉及分配。系统可以有一个通用的空白/新流程模板,它以相同的方式复制,1但与写时复制叉相比,这不会真正节省任何东西。所以#1 只是证明使用“新”空进程不会更有效。

第 2 点确实解释了为什么使用 fork 可能更有效。子环境继承自其父环境,即使它是完全不同的可执行文件。例如,如果父进程是一个 shell,而子进程是一个 web 浏览器,$HOME它们对它们来说仍然是相同的,但是由于任何一个随后都可以更改它,因此它们必须是两个单独的副本。子里的那个是原版制作的fork()

1. 一种可能没有多少字面意义的策略,但我的观点是,创建一个进程不仅仅是将它的映像从磁盘复制到内存中。

  • 虽然这两点都是正确的,但都不支持为什么选择分叉方法而不是从给定的可执行文件中读取新进程。 (3认同)
  • 我认为这确实回答了这个问题。使用 fork 是因为,在创建新进程是最有效的方式的情况下,使用 fork 的成本是微不足道的(可能不到进程创建成本的 1%)。另一方面,在很多地方,fork 的 API 效率更高或更简单(例如处理文件句柄)。Unix 做出的决定是只支持一种 API,使规范更简单。 (3认同)
  • 最初的 Unix 实现没有虚拟内存或写时复制。因此,虽然这解释了为什么 `fork()` 现在很便宜,但这并不能说明为什么首先选择该模型。 (2认同)
  • 您的回答涉及硬件改进:虚拟内存、写时复制。在这些之前,`fork` 实际上复制了所有进程内存,而且非常昂贵。 (2认同)

Rap*_*ens 7

我认为 Unix 只有fork创建新进程的功能的原因是Unix 哲学的结果

他们构建了一个功能,可以很好地完成一件事。它创建了一个子进程。

对新流程的处理取决于程序员。他可以使用其中一个exec*函数并启动不同的程序,或者他不能使用 exec 并使用同一程序的两个实例,这可能很有用。

所以你获得了更大的自由度,因为你可以使用

  1. 没有 exec 的 fork*
  2. fork 与 exec* 或
  3. 只是 exec* 没有 fork

此外,您只需记住forkexec*函数调用,而在 1970 年代您必须这样做。

  • 我了解叉子是如何工作的,以及如何使用它们。但是为什么我要创建一个新的流程,当我可以做同样的事情但用更少的努力?例如,我的老师给了我一个作业,我必须为传递给 argv 的每个数字创建一个过程,以检查该数字是否为素数。但这不是最终做同样的事情的绕道而行吗?我可以只使用一个数组并为每个数字使用一个函数......那么为什么我们要创建子进程,而不是在主进程中进行所有处理? (3认同)
  • 我敢说你了解 fork 是如何工作的,以及如何使用它们,***因为***你曾经有一位老师给你一个作业,你必须创建一堆进程(指定数量在运行时),控制它们,协调它们,并在它们之间进行通信。当然,在现实生活中,没有人会做这种琐碎的事情。但是,如果您有一个很容易分解成可以并行处理的部分的大问题(例如,图像中的边缘检测),分叉可以让您同时使用多个 CPU 内核。 (3认同)

Jim*_*hey 6

进程创建有两种哲学:带继承的 fork 和带参数的创建。显然,Unix 使用 fork。(例如,OSE,VMS 使用 create 方法。)Unix 具有许多可继承的特性,并且会定期添加更多特性。通过继承,可以在不更改现有程序的情况下添加这些新特性!使用 create-with-arguments 模型,添加新特征意味着向 create 调用添加新参数。Unix 模型更简单。

它还提供了非常有用的 fork-without-exec 模型,其中一个进程可以将自身分成多个部分。当没有任何形式的异步 I/O 时,这很重要,并且在利用系统中的多个 CPU 时很有用。(预线程。)这些年来,即使是最近,我也做了很多。从本质上讲,它允许将多个“程序”容器化为一个程序,因此绝对没有损坏或版本不匹配等的空间。

fork/exec 模型还为特定子进程提供了继承一个非常奇怪的环境的能力,该环境设置在 fork 和 exec 之间。诸如继承的文件描述符之类的东西,尤其是。(stdio fd 的扩展。)create 模型不提供继承 create 调用创建者未设想的任何内容的能力。

一些系统还可以支持本地代码的动态编译,其中进程实际上是在编写自己的本地代码程序。换句话说,它想要一个新程序,它可以动态地自己编写,而不必经过源代码/编译器/链接器循环,也不需要占用磁盘空间。(我相信有一个 Verilog 语言系统可以做到这一点。) fork 模型支持这一点,而 create 模型通常不会。