< 返回博客

进程终止(exit)笔记


wait

监控进程状态的更改事件(比如常见的:监控子进程的退出)有多种方式,常用的两种为:

int wait(int *status);
int waitpid(pid_t wpid, int &status, int options);

两者均返回子进程pid,并在status中保存子进程的退出信息。成功返回子进程的pid,发生错误则返回-1。
另外,waitpid调用中,如果第三个参数含有WNOHANG标志,在没有子进程状态改变的情况下会返回0而不是阻塞等待。利用这个特性可以非阻塞地检查当前的子进程的状态。

status的值表示子进程的退出信息(原因、退出码等)。虽然是一个int型变量,但实际只用到了低两个字节。可以使用如下宏来检查其中的内容(定义在sys/wait.h):

  • WIFEXITED(status)
    进程正常结束返回1,此时WEXITSTATUS(status)宏返回进程的退出码;

  • WIFSIGNALED(status)
    进程被信号结束返回1,此时WTERMSIG(status)宏返回导致子进程结束的信号值,如果产生了内核转储文件,则WCOREDUMP(status)宏返回1;

  • WIFSTOPPED(status)
    进程被信号停止返回1,此时WSTOPSIG(status)宏返回导致子进程停止的信号值;

  • WIFCONTINUED(status)
    进程被信号继续返回1。

二者对比

  1. 二者都可以监控子进程状态的更改,wait调用只能监控子进程的终止,而无法监控进程因信号而停止(SIGSTOP等)或继续(SIGCONT)的事件;waitpid则可以通过指定第三个参数options来选择等待子进程的不同的事件;
  2. waitpid调用可以指定唯一的子进程进行等待,wait则不能;
  3. waitpid支持非阻塞地监控;
  4. wait(&status)相当于waitpid(-1, &status, 0)

waitpid调用的参数解释:

waitpid的第一个参数指定要等待的子进程,如果:

  • pid > 0,表示要等待唯一一个进程,进程id为所指定的pid
  • pid = 0,表示要等待当前进程组的所有子进程
  • pid < -1,表示要等待进程组标识符与pid绝对值相等的所有子进程;
  • pid = -1,等待任意子进程。

waitpidoptions参数指定等待的事件:为0时,监控子进程的正常终止(exit/_exit);同时可以包含0个或多个下列标志(按位或):

  • WUNTRACED
    同时监控子进程因信号而停止的事件(SIGSTOP等);
  • WCONTINUED
    同时监控子进程因信号而继续的事件(SIGCONT);
  • WNOHANG
    非阻塞地检查是否有未被等待的子进程事件。

子进程从信号处理器中结束进程:

如果进程收到某个信号需要结束,在此之前想要进行一些清理工作,简单地在信号处理器函数中调用_exit()会导致父进程认为其正常退出。一般做法是:在信号处理器中先清理掉目前的信号处理器(设置为默认),然后重新向自己发送信号(raise(sig))。

SIGCHLD信号

  • 子进程被信号停止(STOP);
  • 子进程终止,但尚未被父进程wait,沦为僵尸进程;

这两种情况下系统会为其父进程发送SIGCHLD信号。

在为该信号设置信号处理器的时候,如果指定SA_NOCLDSTOP标志,子进程因信号被停止(STOP)的时候,不会触发该信号;如果指定SA_NOCLDWAIT标志,子进程结束后会被系统直接清理,不会产生僵尸进程,但仍会触发父进程的信号处理函数。

这个信号默认被忽略,可以为其安装信号处理器,在其中调用wait来处理僵尸进程。

SIGCHLD处理器中的wait

简单的在信号处理器中调用一次wait处理子进程的问题在于:一般在信号处理器调用时会阻塞信号,而且SIGCHLD信号为标准信号,不做排队处理,因此,如果有多个子进程“同时”终止,父进程信号处理器调用的次数会比预期的少。

解决方案是,在信号处理器中循环调用带有WNOHANG标志的waitpid,直至其返回0,表示暂无停止的子进程。

while (waitpid(-1, NULL, WNOHANG) > 0);

这条循环结束后,所有的僵尸子进程都已处理完毕;如果在执行循环所在的信号处理器期间有大于一个的子进程结束,那么在下一次SIGCHLD信号触发时,这些子进程全都会被处理。这样,即使信号处理器的调用次数少于实际子进程的结束次数,也可以保证所有的僵尸进程都会被处理。

不产生僵尸进程

有两种方式可以使得子进程结束时不产生僵尸进程,不论父进程是否等待。

  • 一种方式是显式地将SIGCHLD的处理器设置为SIG_IGN,虽然SIGCHLD信号的默认处理即为忽略,但是经过显式设置之后,子进程结束后直接会被清理,不会产生僵尸进程;
  • 另一种效果类似的方法是在安装SIGCHLD的信号处理函数时,带上SA_NOCLDWAIT标志,这样,子进程结束之后也不会产生僵尸进程。
    这种情况下,由于子进程不产生僵尸进程,内核不会为退出的子进程保留信息,因此父进程的wait/waitpid表现会有所不同。wait/waitpid会阻塞直到父进程的所有子进程退出,然后返回-1,errno设置为ECHLD,表示没有更多的子进程。