实际上,与许多基本的 UNIX 功能不同,fork 是一个相对较晚的人(a)。
UNIX 中最早存在的多进程由几个(固定数量)进程组成,每个终端一个连接到 PDP-7 机器(b)。
基本思想是给定终端的 shell 进程将接受来自用户的命令,定位程序文件,将一个小的引导程序加载到高内存并跳转到它,传递足够的详细信息以加载引导代码程序文件。
引导代码在将程序加载到低内存(覆盖 shell)后,会跳转到它。
程序完成后,它会调用exit,但它不像我们今天所知道和喜爱的exit。 这exit 将简单地重新加载 shell 并使用与最初加载程序几乎相同的方法运行它。
所以它实际上更像是一个基本的exec 命令,在同一个进程空间中用另一个替换你当前的程序。
shell 会exec 你的程序然后,当你的程序完成后,它会再次通过调用exit exec shell。
这种方法与当时在许多其他交互式系统中发现的方法相似,包括 UNIX 得名的 Multics。
从双向exec 开始,将fork 添加为协同工作的流程复制器实际上并没有那么大的飞跃。虽然许多系统直接运行另一个程序,但正是这种“只添加需要的”方法负责在 UNIX 中分离 fork 和 exec 之间的职责。它还产生了一个非常简单的fork 函数。
如果您对 Unix 的各种功能(c) 的早期历史感兴趣,您不能阅读 Dennis Ritchie 的文章 The Evolution of the Unix Time-Sharing System,发表于1979 年在澳大利亚举行的一次会议,随后由 AT&T 出版。
(a) 虽然我的意思是后来者,因为宇宙中四种基本力的分离是“迟到的”,发生在大爆炸后约 0.00000000001 秒。幽默>。
(b) 由于在评论中提出了有关 shell 最初是如何启动的问题,因此有一个 great 资源包含非常早期的 Unix 源代码在The Unix Heritage Society,特别是the source code archives,特别是first edition。
第一版中的init.s 文件显示了如何创建固定数量的 shell 进程(稍微重新格式化):
...
mov $itab, r1 / address of table to r1
1:
mov (r1)+, r0 / 'x, x=0, 1... to r0
beq 1f / branch if table end
movb r0, ttyx+8 / put symbol in ttyx
jsr pc, dfork / go to make new init for this ttyx
mov r0, (r1)+ / save child id in word offer '0, '1, etc
br 1b / set up next child
1:
...
itab:
'0; ..
'1; ..
'2; ..
'3; ..
'4; ..
'5; ..
'6; ..
'7; ..
0
在这里您可以看到为每个连接的终端创建进程的 sn-p。这些是硬编码值的日子,不涉及终端数量的自动检测。 itab 处的零终止表用于创建多个进程,希望代码中的 cmets 解释如何(唯一可能棘手的是标签 - 尽管有多个 1 标签,但您可以分支到最近的一个在给定方向,因此1b 表示向后最近的1 标签)。
显示的代码只是处理表,调用dfork 为每个终端创建一个进程并启动getty,登录提示。反过来,getty 程序最终启动了 shell。从那时起,就像我在这个答案的主要部分中描述的那样。
(c) 没有路径(以及使用临时链接来绕过这个限制),有限的进程,为什么密码文件中有一个 GECOS 字段,以及各种其他琐事,通常很有趣当然,仅限于超级极客。