第九章第一节 fork与exec
fork系统调用
Linux下创建新进程的系统调用为fork。
1 | /* |
该函数的每次调用都返回两次,在父进程中返回子进程的PID,则子进程中返回0。
fork函数复制当前进程的进程管理信息给子进程。子进程的进程管理信息大部分属性与原进程相同,如堆地址、栈地址、标志寄存器等;还要一些属性与父进程不同,如进程的PPID(设置为父进程的PID)、信号位图(所有位置0)。
此外,创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数加1。并且父进程的用户根目录、当前工作目录等变量的引用计数也会加1。
exec系列系统调用
exec系统调用将可执行文件替换当前进程映像。
1 | /* |
若exec系列系统调用执行未出错,则原程序中exec调用之后的代码都不会执行!
exec系列系统调用在运行后不会关闭原进程打开的文件描述符,除非该文件描述符被设置了类似SOCK_CLOEXEC属性。
处理僵尸进程
对多进程程序而言,父进程需要跟踪子进程的退出状态;因此,子进程结束运行后,内核不会立即释放该进程的进程管理信息。
在子进程结束运行之后,父进程读取其退出状态之前,子进程处于僵尸状态。
此外,若父进程意外终止,也会使子进程进入僵尸态;此时子进程的PPID被设置为1(父进程为init进程)。之后,init进程接管该子进程并等待它结束。
僵尸进程占用内核资源;为此,Linux系统提供wait/waitpid函数来等待进程结束。
1 | /* |
- wait函数将阻塞进程直到进程的某个子进程结束;
- waitpid只等待pid结束;
同样地,只有在事件发生的情况下使用非阻塞调用才能提高程序的效率。而一个进程结束时,它将给它的父进程发送SIGCHLD信号来表明自身的结束。因此,父进程可捕获SIGCHLD信号,然后在信号处理函数中调用waitpid函数。
一种典型处理如下。
1 | static void handle_child(int sig){ |