我正在学习fork()
和exec()
命令。看起来fork()
和exec()
通常是一起称呼的。 (fork()
创建一个新的子进程,并exec()
用新的进程映像替换当前的进程映像。)但是,在什么情况下您可以单独调用每个函数?有这样的场景吗?
答案1
当然! “包装”程序中的常见模式是执行各种操作,然后仅通过exec
调用(无分叉)将其自身替换为其他程序
#!/bin/sh
export BLAH_API_KEY=blub
...
exec /the/thus/wrapped/program "$@"
一个现实生活中的例子是GIT_SSH
(尽管如果您不想执行上述包装程序方法,git(1)
也可以提供)。GIT_SSH_COMMAND
Fork-only 用于生成一堆典型的工作进程(例如,httpd
fork 模式下的 Apache(尽管 fork-only 更适合需要消耗 CPU 的进程,而不是那些等待网络 I/O 发生的进程) )或对于特权分离sshd
由OpenBSD 上的程序和其他程序使用(无 exec)
$ doas pkg_add pstree
...
$ pstree | grep sshd
|-+= 70995 root /usr/sbin/sshd
| \-+= 28571 root sshd: jhqdoe [priv] (sshd)
| \-+- 14625 jhqdoe sshd: jhqdoe@ttyp6 (sshd)
sshdroot
在客户端连接上分叉出自身的副本 (28571),然后分叉出另一个副本 (14625) 以进行权限分离。
答案2
有很多。
fork()
不调用的程序exec()
通常遵循生成子进程的模式工作进程用于在与主进程不同的进程中执行各种任务。您会在dhclient
、php-fpm
、 和 等各种程序中发现这一点urxvtd
。
exec()
一个没有调用的程序fork()
是链式装载,用不同的程序映像覆盖其进程。链加载实用程序有一个完整的亚文化,它们对处理状态执行特定的操作,然后执行另一个程序以使用修改后的进程状态运行。此类实用程序常见于守护进程工具家族服务和系统管理工具集,但不限于这些。举几个例子:
chpst
来自格里特·佩普的运行s6-softlimit
和s6-envdir
来自洛朗·贝尔科特s6local-reaper
和move-to-control-group
从我的小吃工具集rdprio
在idprio
FreeBSD 上numactl
在 FreeBSD 和 Linux 上
daemontools 系列工具集有很多此类工具,来自machineenv
通过find-matching-jvm
到runtool
。
答案3
除了其他答案之外,调试器ptrace
通常会利用fork
和之间的间隙exec
。调试对象应该用 来标记自己,以PTRACE_TRACEME
表明它正在被其父进程(调试器)跟踪。这是为了向调试器授予所需的权限。
所以调试器首先会分叉自己。孩子会ptrace
先打电话PTRACE_TRACEME
,然后再打电话exec
。无论子执行程序的哪个程序现在都可以由父执行程序跟踪。
答案4
不使用 fork 执行
您想要做这样的事情至少有两个原因:
- 链条装载。当前的过程映像被替换为不同的东西。
- 重新启动当前正在运行的程序(例如,当您 SIGHUP 或此类服务器进程时可能会发生,重新加载所有内容并进行全新启动)。在某种程度上,人们可能会认为这是链式加载,只是与同一个程序巧合。
没有 exec 的 fork
这就是每个守护进程每次启动时都会执行的操作(实际上是两次)。这会做几件事,其中 shell 不会挂起(因为 shell 等待的原始进程终止)并且守护进程不再受终端控制,因此关闭 shell 窗口不会杀死守护进程。
另一个常见的用途是分叉工人子代,它在大约 25 年前因 apache Web 服务器而闻名(现在,由于很容易出现雷鸣群问题,这不再被认为是最先进的,但它确实提供了该死的最简单、最强大的服务器可能)。
另一个常见用途是创建一致的快照。fork
不仅创建一个进程,它还复制(理论上,实际上它只标记页面写时复制)地址空间。这(以原子方式)创建完整程序数据的快照,父级无法再修改该快照。
有些程序利用了这一点。例如redis将数据保存到磁盘(处于一致状态),同时修改数据集同时进行。这只有效,因为fork
创建了一个一致的快照,看不到父进程所做的修改。