fork
和exec
的使用体现了UNIX的精神,因为它提供了启动新进程的非常简单的方法.
fork
调用基本上复制了当前进程,在每种情况下almost都是相同的.并非所有内容都会被复制(例如,某些实现中的资源限制),但我们的 idea 是创建尽可能接近的副本.
新进程(子进程)获得不同的进程ID(PID),并将旧进程(父进程)的PID作为其父进程PID(PPID).因为这两个进程现在正在运行完全相同的代码,所以它们可以通过返回代码fork
来区分哪个是哪个-子进程得到0,父进程得到子进程的PID.当然,假设fork
调用正常工作,这就是全部内容-如果不工作,则不会创建子进程,父进程将获得错误代码.
exec
调用是一种基本上用新程序取代当前整个流程的方法.它将程序加载到当前进程空间,并从入口点运行.
因此,fork
和exec
通常按顺序使用,以使新程序作为当前进程的子进程运行.每当你试图运行一个像find
这样的程序时,shell通常会这样做——shellForking ,然后子程序将find
程序加载到内存中,设置所有命令行参数、标准I/O等等.
但它们不需要一起使用.例如,如果程序同时包含父代码和子代码(您需要小心操作,每个实现可能都有限制),则完全可以接受程序本身为fork
而不使用exec
ing.对于只在TCP端口上侦听的守护进程来说,这已经被大量使用了(现在仍然是),而当父进程返回侦听时,守护进程将自己的一个副本用于处理特定的请求.
类似地,那些知道已经完成并且只想运行另一个程序的程序不需要为子元素设置fork
、exec
和wait
.他们可以直接将子进程加载到进程空间中.
一些UNIX实现有一个优化的fork
,它使用了他们所说的写时拷贝.这是一个将复制进程空间延迟到fork
的技巧,直到程序试图更改该空间中的某些内容.这对于那些只使用fork
而不是exec
的程序很有用,因为它们不必复制整个进程空间.
如果exec
is调用following fork
(这是大多数情况下发生的情况),则会导致对进程空间进行写入,然后为子进程复制.
请注意,有exec
个电话(execl
、execle
、execve
等等)组成了一个完整的家庭,但这里的exec
指的是其中的任何一个.
下图显示了典型的fork/exec
操作,其中bash
shell用于使用ls
命令列出目录:
+--------+
| pid=7 |
| ppid=4 |
| bash |
+--------+
|
| calls fork
V
+--------+ +--------+
| pid=7 | forks | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash | | bash |
+--------+ +--------+
| |
| waits for pid 22 | calls exec to run ls
| V
| +--------+
| | pid=22 |
| | ppid=7 |
| | ls |
V +--------+
+--------+ |
| pid=7 | | exits
| ppid=4 | <---------------+
| bash |
+--------+
|
| continues
V