wait 和 exit 的原理

一、wait 和 exit 的作用

1. exit 浅析

exit 时进程主动退出,结束运行。main 函数执行结束后程序流程会回到 C 运行时库,C 运行库的结束代码处会调用 exit。结束程序运行始终是通过主动调用 exit 系统调用实现的,因为这是唯一让系统重新拿回处理器控制权的机会。

exit 由子进程调用,表面功能是使子进程结束运行并传递返回值给内核,本质上是内核会将进程除 pcb 以外的所有资源都回收。

2. wait 浅析

wait 一是可以使调用进程阻塞,二是可以获得子进程的返回值。

如果调用进程没有子进程,那么 wait 返回 -1。如果有子进程,调用进程将被阻塞,内核再去遍历其所有子进程,查找那个子进程退出了,并将子进程退出时的返回值传递给父进程,随后将父进程唤醒。

wait 可以用来同步父子进程,协调父子进程的执行次序。

wait 是父进程调用的,表面功能是使父进程阻塞自己,直到子进程调用 exit 结束运行,然后获得子进程的返回值。本质上是内核在幕后将子进程的返回值传递给父进程,并唤醒父进程,然后将子进程的 pcb 回收。

二、孤儿进程和僵尸进程

Linux 系统中为什么会有孤儿进程和僵尸进程,原因是因为有 wait 和 exit 系统调用。

1. 僵尸进程

僵尸进程:子进程运行结束,父进程没有调用 wait 这个子进程,那么这个子进程会变成僵尸进程。

一个进程退出时,不管是主动调用 exit,或 main 函数中 return,还是 C 运行时库调用 exit 退出,都需要指定进程的返回值,如 exit 的原型:void exit(int status),其中 status 就是子进程的返回值。然后子进程的返回值需要借助内核才能交给父进程。父进程通过 wait 获取子进程的返回值,wait 原型:pid_t wait(int* status),内核会把子进程的返回值存储到 status 指向的内存空间。至此,父进程拿到了子进程的退出状态。

子进程的退出状态会保存在 pcb 中,进程在调用 exit 后,表示进程的生命周期结束了,其占用的资源被回收,包括内存、页表等。但是不能将进程的 pcb 所占的内存回收,因为 pcb 中还有进程的退出状态。应该在父进程调用 wait 获取子进程的返回值后,再由内核回收子进程 pcb 所占的一页内存,也就是说,回收 pcb 内存空间的工作是在系统调用 wait 对应的内核实现中。

僵尸进程危害:内核无法回收僵尸进程 pcb 所占空间,会在调度队列中占据一个进程表项。僵尸进程没有进程体,因为其进程体已在调用 exit 时被内核回收了。也没法 kill 掉,因为他不会再被调度了。同时他还会占用 pid,当僵尸进程数量很大时,系统将无可用 pid 分配给新进程。

如何解决:kill 掉僵尸进程的父进程即可。

2. 孤儿进程

孤儿进程:父进程提前退出,子进程会被 init 进程收养,init 进程成为子进程的父进程。原理上不会对系统产出危害。