为什么“fork 后通常跟着 exec”?在 UNIX 中不能直接创建一个新进程吗?
答案1
与大多数现有“羞辱分叉”的答案略有不同的观点...;)
最初,正如 @davidbak 提到的那样,这样做可能非常容易。但是,在多次使用fork
/之后(也经常只使用,进行多处理),肯定有理由认为这种工作方式仍然有效,并且不会被历史迷雾所笼罩:exec
fork
- 从程序员的角度来看,它仍然非常简单,几乎任何编程语言都可以。无论我在哪里编码,任何语言都可以信任语义的极其简单的含义
fork
并将其作为语言的一部分提供。因此,每种语言都有一种相对简单的方法(与进程内多线程相比)为其用户提供至少多处理。(注:一个例外是如果您在程序中使用多线程 -fork 之后,只有一个线程在运行;这可能会导致除最简单的多线程应用程序之外的所有应用程序出现明显的问题。) - 作为用户(程序员),我可以用几行代码编写多处理程序,无需担心互斥锁、信号量、非法覆盖任何程序变量的状态等等。同时,父子之间的“初始通信”也很容易处理 - 子进程可以完全访问父进程拥有的任何变量或 RAM,并且可以继续使用它。实际上,这意味着,如果我需要与主程序并行执行一些短 I/O 或网络进程,我可以用几行代码来完成;所有内容都在一个地方,很容易看到。之后我可以收集子进程,然后高高兴兴地离开。没有“工作线程”,我不需要小心只使用线程安全的方法或数据结构。
- 同样,由于内存内容一开始相同但实际上是分开的,因此父/子进程之间不存在覆盖任何内容的风险。是的,我确实必须找到其他方式在父子进程之间进行 IPC,但这些方法并不适用。那也很难;通常,语言提供像“open3”这样的标准函数或类似的函数,它自动提供双向管道文件句柄进行通信,以避免死锁等。
- 具体来说,在编程语言之间切换时,一旦理解了语义
fork
,就不再需要学习有关新环境的任何知识 - 它总是像任何其他语言一样简单。 - 无论如何,有它总是好的
exec
。它允许我们用不同的东西替换当前的进程映像(即正在执行的可执行文件)。这使得它变得清晰,比如说,有一些脚本或程序准备某种环境,然后执行其他东西,而它本身却从场景中消失。它不仅释放资源(RAM,还有进程表中的空间,等等),而且让任何参与或查看它的人都很清楚,以前的父进程将来不会再扮演任何角色。你经常会在编写良好的bash
脚本中发现这一点,这些脚本在启动它们的“有效负载”时释放解释器的资源bash
。
此外:
- 它完全符合 Unix 的哲学,即拥有许多可以相互作用的小工具,而不是功能非常有限或需要一大组参数或 API 才能真正使用的肥胖黑盒子。
- 如上所示,在某些只有单一函数会受到限制的场景中,它非常强大;但它也很容易让
fork
+exec
接踵而至。这并不像你有除非你需要,否则要在其间做很多事情(或者实际上什么都不做)。 - 根据手册页,在某些现代 Unix 系统(即 Linux)中,叉本身只是一个包装更现代、更强大克隆调用,这确实有点像
fork+exec
。请注意,这里我们已经看到复杂性已经抬头;Linux 也有一个克隆3它取代了之前的函数clone
,并使界面变得更容易或更方便(使用structs
而不是那么多标志)。
答案2
fork()
创建一个新的进程,这是一个复制父进程。因此,如果您只执行fork()
,则将有两个相同的进程正在运行。因此,为了用另一个代码替换分叉进程,您需要执行 ,exec()
用指定的可执行文件替换当前正在运行的进程。
Linux 内核就是这样组织的。你没有一个系统调用可以同时创建新进程和加载新的可执行文件。你必须分两步完成 - 首先创建新进程,然后将新的可执行文件加载到这个新进程中。(虽然你可能有一个库函数在将这两者结合在一起的编程语言中 - 例如spawn()
在许多 C 变体中)。
有时exec()
不需要,如果您只想创建当前进程的另一个副本。例如,许多守护进程都会这样做。
答案3
这是由于历史原因:在时间的开始,只有fork
和exec
。因为易于实施(根据 DMR:只有 27 行 PDP-7 汇编代码fork
! - 参见例如在fork()
路上(Baumann、Appavoo、Krieger、Roscoe,2019 年)- 尽管引用了主要来源,但仍是次要来源Unix 分时系统的演变(Ritchie,1979)。无论如何,从头计算直接进程创建出现得晚得多。(可能不在 POSIX 中?)
真正的直接进程创建 API 出现得晚得多,这一事实至今仍影响着 Unix 编程。因为数百本书籍、手册、教程、幻灯片和课程都解释了这些fork
,exec
并且几十年来一直向学生和程序员传授这些知识,这在 Unix 中创建/控制进程的方法广泛的传统在代码编写方式中得以延续到今天。
哦,这是Unix 分时系统的演变(里奇,1979 年)。向下滚动到第 6 页查看:“现代形式的过程控制是在几天内设计和实施的。......事实上,PDP-7 的叉调用需要恰好 27 行汇编代码。”
答案4
不可以,您无法在 UNIX 中创建新进程,您只能复制当前进程(使用fork
)。如果您希望新进程执行与当前进程不同的操作,则需要替换它(使用exec
)。
fork
在调用 之前您不必这样做exec
。在启动登录会话(.xinitrc
以及类似会话)的脚本中,有一种常见用法,即设置环境变量并启动后台任务(例如ssh-agent
),然后运行会话管理器。启动会话管理器后,启动脚本无需再执行任何操作,因此您exec
可以释放分配给运行启动脚本的资源。启动脚本的父级不知道此替换 - PID 保持不变 - 因此它们会继续等待此子级死亡,然后再执行清理操作。