Answers:
简而言之,fork
是在Unix中,因为当时它很容易安装到现有系统中,并且伯克利的前身系统使用了fork的概念。
摘自Unix分时系统的演进(相关文本已突出显示):
几天之内就设计并实施了现代形式的过程控制。令人惊讶的是它很容易安装到现有系统中;同时,很容易看到设计中某些稍微不寻常的功能是如何呈现的,因为它们代表了对存在的内容的细微,易于编码的更改。一个很好的例子是fork和exec函数的分离。创建新流程的最常见模型涉及为该流程执行指定程序。在Unix中,派生进程将继续与其父进程运行相同的程序,直到执行显式exec。这些功能的分离当然不是Unix独有的,事实上,它存在于汤普森众所周知的伯克利分时系统中。。仍然可以合理地假设它存在于Unix中,主要是因为可以很容易地实现fork而不改变其他内容。系统已经处理了多个(即两个)过程;有一个进程表,并且进程在主内存和磁盘之间交换。fork的初始实现只需要
1)扩展过程表
2)添加一个fork调用,该调用使用现有的swap IO原语将当前进程复制到磁盘交换区域,并对进程表进行一些调整。
实际上,PDP-7的fork调用恰好需要27行汇编代码。当然,还需要对操作系统和用户程序进行其他更改,其中一些更改非常有趣且出乎意料。但是,仅仅因为不存在这样的exec,合并的fork-exec就会复杂得多。它的功能已经由外壳使用显式IO执行。
从那篇论文开始,Unix就发展了。fork
其次exec
不再是运行程序的唯一方法。
vfork的创建是为了在新进程打算在该分支之后立即执行exec的情况下提高效率。执行vfork之后,父进程和子进程共享相同的数据空间,并且父进程被挂起,直到子进程执行程序或退出。
posix_spawn在一个系统调用中创建一个新进程并执行一个文件。它带有许多参数,可让您有选择地共享调用者的打开文件,并将其信号处置和其他属性复制到新进程。
posix_spawn()
执行相同的叉后重排作业所需的机械设计(数据结构设置),可以轻松使用fork()
内联代码完成,并且内联代码令人信服,理由fork()
是其易于使用。
[我将从这里重复部分答案。]
为什么不仅仅拥有一个从头开始创建新进程的命令? 复制仅将立即替换的内容是荒谬且效率低下的吗?
实际上,由于以下几个原因,效率可能不那么高:
fork()
因为内核使用写时复制系统,所以产生的“副本” 有点抽象。真正需要创建的只是一个虚拟内存映射。如果该副本随后立即调用exec()
,则实际上如果已被流程的活动修改了该副本的大部分数据,则实际上不必复制/创建该副本,因为流程不需要执行任何需要使用的操作。
子进程的各个重要方面(例如,其环境)不必根据上下文的复杂分析等单独进行复制或设置。仅假设它们与调用过程的相同,并且这是我们熟悉的相当直观的系统。
为了进一步解释#1,至少在大多数情况下,“复制”但从未随后访问的内存永远不会真正被复制。在这种情况下,例外情况可能是,如果您分叉了一个流程,然后在子级将自己替换为子级之前退出了父级进程exec()
。我之所以说是可能的,是因为如果有足够的可用内存,那么很多父对象都可以被缓存,而且我不确定这将被利用到什么程度(取决于操作系统的实现)。
当然,从表面上看,这并不比使用空白板更有效地使用副本-除了“空白板”从字面上看不是什么,而必须涉及分配。该系统可以具有通用的空白/新流程模板,该模板以相同的方式复制1,但是与写时复制fork相比并不会真正节省任何东西。因此,#1仅演示了使用“新”空进程不会更有效。
第二点确实解释了为什么使用分叉可能更有效。子级环境是从其父级继承的,即使它是完全不同的可执行文件。例如,如果父进程是一个外壳程序,子进程是一个Web浏览器,$HOME
则两者都相同,但是由于随后任何一个都可以更改它,因此它们必须是两个单独的副本。孩子中的一个是原始的fork()
。
1.一种策略可能并没有多大的意义,但我的观点是,创建进程所涉及的不仅仅是将其映像从磁盘复制到内存中。
fork()
可以非常快地完成它(如GL所述,大约27条装配线)。从另一个方向看,如果您想“从头开始创建流程”,则fork()
花费仅比从空白创建的流程开始花费的成本高(27行汇编+关闭文件句柄的成本)。因此,fork
既处理好分叉又创造得很好,而create
只能处理好创造。
fork
实际上复制了所有进程内存,这非常昂贵。
我认为Unix仅fork
具有创建新进程的功能的原因是Unix哲学的结果
他们构建了一个功能出色的功能。它创建一个子进程。
然后,由新程序员决定如何处理新过程。他可以使用exec*
功能之一并启动其他程序,或者他不能使用exec并使用同一程序的两个实例,这很有用。
因此,您可以使用,因此拥有更大的自由度
此外,您只需要记住fork
和exec*
函数调用,就可以在1970年代完成。
流程创建有两种哲学:带有继承的派生和带有参数的创建。显然,Unix使用fork。(例如,OSE和VMS使用create方法。)Unix具有许多可继承的特性,并且会定期添加更多特性。通过继承,可以添加这些新特性而无需更改现有程序!使用带参数创建模型,添加新特征将意味着向create调用添加新参数。Unix模型更简单。
它还提供了非常有用的fork-without-exec模型,其中一个进程可以将自身分成多个部分。当没有异步I / O形式时,这是至关重要的,并且在系统中利用多个CPU时很有用。(预线程。)这些年来,甚至最近,我已经做了很多事情。本质上,它允许将多个“程序”容器化为单个程序,因此绝对没有损坏或版本不匹配等的空间。
fork / exec模型还为特定的孩子提供了继承在fork和exec之间建立的根本怪异环境的能力。诸如继承文件描述符之类的事情尤其如此。(stdio fd的扩展。)create模型不提供继承create调用的创建者未想到的任何内容的功能。
一些系统还可以支持本地代码的动态编译,该过程实际上是在编写自己的本地代码程序。换句话说,它需要一个新的程序,它可以即时编写自己的程序,而不必经历源代码/编译器/链接器的周期,而无需占用磁盘空间。(我相信有一个Verilog语言系统可以做到这一点。)fork模型支持这一点,而create模型通常不支持。
fork()函数不仅复制父进程,还返回一个值,该值表示该进程是父进程还是子进程,下图说明了如何使用fork()作为父进程和父进程。儿子:
如进程是父进程时所示,fork()返回子进程ID,PID
否则返回0
例如,如果您有一个接收请求的进程(Web服务器),并且可以在每个请求上创建一个son process
来处理该请求,则可以使用它,此处父级和子级都有不同的工作。
因此,没有运行进程副本不是fork()的确切含义。
fork
,如果你认为你要fork
摆在首位
在fork之后和exec之前,最容易实现I / O重定向。孩子知道自己是孩子,可以关闭文件描述符,打开新的文件描述符,使用dup()或dup2()将它们添加到正确的fd编号等,而所有这些都不影响父对象。完成此操作之后,也许任何所需的环境变量都会发生变化(也不会影响父级),它可以在定制的环境中执行新程序。
我想这里的每个人都知道fork是如何工作的,但问题是为什么我们需要使用fork创建父级的精确副本? 答案 ==>以服务器为例(不带fork),当client-1正在访问服务器时,如果同时有第二个client-2到达并想要访问服务器,但服务器未向新到达的服务器授予权限client-2,因为服务器忙于为client-1提供服务,因此client-2必须等待。到client-1的所有服务完成后,client-2现在可以访问服务器了。现在考虑是否同时客户端3到达,因此客户端3必须等到对客户端2的所有服务完成。以这种情况为例,成千上万的客户端需要同时访问服务器...然后所有客户端必须等待(服务器正忙!)。
通过创建(使用fork)服务器的精确副本(即子节点)来避免这种情况,其中每个子节点(即其父节点(即服务器)的精确副本)专用于新到达的客户端,因此所有客户端同时访问同一客户端服务器。
fork
inetd