操作系统修炼秘籍(29):Linux进程退出和wait/waitpid
进程退出和wait/waitpid
当进程执行完成后,进程会退出,但是进程退出并不代表着进程真的消失了,内核的进程表中仍然记录着该进程的进程表项。之所以不直接退出子进程,是为了让父进程可以去获取该子进程的退出信息,从而更好地进行多进程的管理和控制。
其实,当某进程执行完所有流程后,它会设置一个2字节的退出状态信息,其中就包含了我们在shell下经常查看的退出状态码$?
的值。这个退出状态信息非常重要,它只有父进程通过wait()或waitpid()系统调用才能够读取。只有读取了这个退出状态信息后,才意味着父进程已经获取到了子进程的信息,也就是已经完成了子进程的控制,于是内核才会清理掉进程表中的该进程表项,此时子进程才完全退出消失。
当调度到父进程时,父进程接收这个SIGCHLD信号,虽然这个信号表明了有子进程退出,但是父进程并不一定会根据这个信号做出处理,因为默认情况下这个信号是被忽略的,除非手动定义了SIGCHLD信号的信号处理程序,否则父进程还是不知道子进程退出。那么父进程什么时候才知道要去读取子进程的退出状态信息呢?
其实非常简单。前面说过,wait()和waitpid()是用来读取子进程状态信息的,而这两个系统调用默认是阻塞的(当然,waitpid可以设置为非阻塞方式),当父进程的代码中编写了这两个函数之一时,父进程会在调用到它们的时候一直阻塞等待,直到有子进程退出,wait/waitpid直接就去读取子进程的退出状态信息,相当于wait/waitpid一直在监视着是否有子进程退出。
仍然可以用一段shell的伪代码来描述:
1 | pid=$(fork) # 从此开始,有两个进程分支 |
上面的描述虽然稍显复杂,但整个过程其实非常简单,如图:
另外,既然子进程退出时,内核会发送SIGCHLD信号给父进程,更好的方式自然是在父进程中定义SIGCHLD的信号处理程序,并将wait或waitpid放入这个信号处理程序中去,这样的话,父进程就不需要一开始就阻塞在wait或waitpid上,它可以继续执行其它任务,但只要父进程接收到SIGCHLD信号,就立即触发SIGCHLD处理程序,于是调用该处理程序内部的wait/waitpid去读取子进程的退出状态信息。