1.5 系统调用waitid()
waitid()返回子进程的状态,相对于waitpid()提供了扩展功能。
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Returns 0 on success or if WNOHANG was specified and there were no children to wait for, or –1 on error
参数idtype和id指定需要等待哪些子进程:
如果idtype为P_ALL,则等待任何子进程,同时忽略id值。
如果idtype为P_PID,则等待进程ID为id进程的子进程。
如果idtype为P_PGID,则等待进程组ID为id给进程的所有子进程。
waitid()通过options提供更为精确控制:
WEXITED:等待已终止的子进程,而无论其是否正常返回。
WSTOPPED:等待已通过信号而停止的子进程。
WCONTINUED:等待经由信号SIGCONT而恢复的子进程。
WNOHANG:如果参数pid所指定的子进程并未发生状态改变,则立即返回,而不会阻塞,亦即poll;waitpid()返回0,如果调用进程并无与pid匹配的子进程,则waitpid()报错,将错误号置为ECHILD。
WNOWAIT:如果指定了WNOWAIT,则会返回子进程状态,但子进程依然处于可等待的状态,稍后可在此等待并获取相同信息。
执行成功waitid()返回0,且会更新指针infop所指向的siginfo_t结构,已包含子进程的相关信息。
结构siginfo_t字段如下:
1.6 系统调用wait3()和wait4()
系统调用wait3()和wait4()执行与waitpid()类似的工作,主要差别在于参数rusage所指向的结构中返回终止子进程的资源使用情况。
#define _BSD_SOURCE /* Or #define _XOPEN_SOURCE 500 for wait3() */
#include <sys/resource.h>
#include <sys/wait.h>
pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
Both return process ID of child, or –1 on error
struct rusage结构如下:
struct rusage {
struct timeval ru_utime; /* user time used */
struct timeval ru_stime; /* system time used */
__kernel_long_t ru_maxrss; /* maximum resident set size */
__kernel_long_t ru_ixrss; /* integral shared memory size */
__kernel_long_t ru_idrss; /* integral unshared data size */
__kernel_long_t ru_isrss; /* integral unshared stack size */
__kernel_long_t ru_minflt; /* page reclaims */
__kernel_long_t ru_majflt; /* page faults */
__kernel_long_t ru_nswap; /* swaps */
__kernel_long_t ru_inblock; /* block input operations */
__kernel_long_t ru_oublock; /* block output operations */
__kernel_long_t ru_msgsnd; /* messages sent */
__kernel_long_t ru_msgrcv; /* messages received */
__kernel_long_t ru_nsignals; /* signals received */
__kernel_long_t ru_nvcsw; /* voluntary context switches */
__kernel_long_t ru_nivcsw; /* involuntary " */
wait3()和wait4()的区别在于,wait3()等待任意子进程,wait4()可以用于等待选定的一个或多个子进程。
除了获取rusage信息外,wait3()等同于waitpid(-1, &status, options);wait4()等同于wiatpid(pid, &status, options)。
UNIX中,wait3()和wait4()返回已终止子进程的资源使用情况;对于Linux,如果在options中指定WUNTRACED选项,则还可以获取到停止子进程的资源使用信息。
PS:这两系统调用可移植性较差。
2. 孤儿进程与僵尸进程
谁是孤儿子进程的父进程?
进程ID为1的init进程是众进程之祖,init会接管孤儿进程。
父进程执行wait()之前,其子进程就已经终止,这将会发生什么?
即使子进程已经结束,系统任然允许其父进程在之后的某一时刻执行wait()。该进程所唯一保留的是内核进程表中的一条记录,其中包含子进程ID、终止状态、资源使用情况等。
父进程执行wait()之后,由于不再需要子进程所剩余的最后信息,内核将删除僵尸进程。如果父进程未执行wait()随即退出,那么init进程将接管子进程并自动调用wait(),从系统中移除僵尸进程。
如果父进程创建了某一子进程,但并未执行wait(),那么内核进程表中降为盖子进程永久保留一条记录。如果存在大量僵尸进程,势必将填满内核进程表,从而阻碍新进程的创建。
既然无法用新号杀死僵尸进程,那么从系统中移除的唯一方法就是杀掉它的父进程,此时init进程将接管和等待这些僵尸进程,从而从系统中将他们清理掉。
#include <signal.h>
#include <libgen.h> /* For basename() declaration */
#include "tlpi_hdr.h"
#define CMD_SIZE 200
main(int argc, char *argv[])
char cmd[CMD_SIZE];
pid_t childPid;
setbuf(stdout, NULL); /* Disable buffering of stdout */
printf("Parent PID=%ld\n", (long) getpid());
switch (childPid = fork()) {
case -1:
errExit("fork");
case 0: /* Child: immediately exits to become zombie */
printf("Child (PID=%ld) exiting\n", (long) getpid());
_exit(EXIT_SUCCESS);----------------------------------------子进程打印一条消息后,立即退出。此时变成了僵尸进程。
default: /* Parent */
sleep(3); /* Give child a chance to start and exit */
snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0]));
system(cmd); /* View zombie child */-------------等待子进程变成僵尸进程,ps查看进程情况。
/* Now send the "sure kill" signal to the zombie */
if (kill(childPid, SIGKILL) == -1)--------------------------发送SIGKILL信号杀死僵尸子进程。
errMsg("kill");
sleep(3); /* Give child a chance to react to signal */
printf("After sending SIGKILL to zombie (PID=%ld):\n", (long) childPid);
system(cmd); /* View zombie child again */-------等待SIGKILL信号发送到僵尸子进程,并作出反应。
exit(EXIT_SUCCESS);
使用如下命令执行结果如下:
./make_zombie && ps -a | grep make_zombile
Parent PID=14727
Child (PID=14728) exiting
14727 pts/22 00:00:00 make_zombie
14728 pts/22 00:00:00 make_zombie <defunct>
After sending SIGKILL to zombie (PID=14728):
14727 pts/22 00:00:00 make_zombie
14728 pts/22 00:00:00 make_zombie <defunct>
说明SIGKILL对僵尸进程不起作用,僵尸进程在其父进程退出后也同样被回收。
3. SIGHLD信号
子进程的终止属于异步事件。即使父进程向子进程发送SIGKILL信号,子进程终止的确切时间还依赖与系统的调度:子进程下一次在何时使用CPU。
父进程使用wait()来防止僵尸子进程的累积,以及如下两种方法来避免这一问题:
父进程调用不带WNOHANG标志的wait()或waitpid(),此时如果尚无已经终止的子进程,那么调用将会阻塞。
父进程周期性地调用带有WNOHANG标志的waitpid(),执行针对已终止子进程的非阻塞式检查。
第一种方法会造成父进程阻塞;第二种造成CPU资源浪费,增加应用复杂度。
为了规避这些问题,可以采用这对SIGHLD信号的处理程序。
3.1 为SIGCHLD建立信号处理程序
无论一个子进程何时终止,系统都会向其父进程发送SIGHLD信号。
系统对SIGCHLD信号默认处理是将其忽略,如果通过安装处理程序来捕获它。
会面临如下问题:当调用信号处理程序时,会暂时将引发调用的信号阻塞起来,且不会带SIGCHLD之类的标准信号进行排队处理。当SIGHLD信号处理程序正在为一个终止的子程序运行时,如果相继有两个子进程终止,即使产生了两次SIGHLD信号,父进程也只能捕获到一个。如果父进程的SIGHLD信号处理程序每次只调用一次wait(),那么一些僵尸子进程可能会成为漏网之鱼。
下面的示例程序演示了如何写SIGCHLD处理函数,并且保证不会遗漏。而且说明了SIGCHLD信号不会排队,阻塞期间多次触发,在解除阻塞之后,只会执行一次。
#include <signal.h>
#include <sys/wait.h>
#include "print_wait_status.h"
#include "curr_time.h"
#include "tlpi_hdr.h"
static volatile int numLiveChildren = 0;
/* Number of children started but not yet waited on */
static void
sigchldHandler(int sig)
int status, savedErrno;
pid_t childPid;
/* UNSAFE: This handler uses non-async-signal-safe functions
(printf(), printWaitStatus(), currTime(); see Section 21.1.2) */
savedErrno = errno; /* In case we modify 'errno' */
printf("%s handler: Caught SIGCHLD\n", currTime("%T"));
/* Do nonblocking waits until no more dead children are found */
while ((childPid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("%s handler: Reaped child %ld - ", currTime("%T"),
(long) childPid);
printWaitStatus(NULL, status);
numLiveChildren--;
if (childPid == -1 && errno != ECHILD)
errMsg("waitpid");
sleep(5); /* Artificially lengthen execution of handler */
printf("%s handler: returning\n", currTime("%T"));
errno = savedErrno;
main(int argc, char *argv[])
int j, sigCnt;
sigset_t blockMask, emptyMask;
struct sigaction sa;
if (argc < 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s child-sleep-time...\n", argv[0]);
setbuf(stdout, NULL); /* Disable buffering of stdout */
sigCnt = 0;
numLiveChildren = argc - 1;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sigchldHandler;
if (sigaction(SIGCHLD, &sa, NULL) == -1)-------------------------------------------------设置SIGCHLD信号处理函数。
errExit("sigaction");
/* Block SIGCHLD to prevent its delivery if a child terminates
before the parent commences the sigsuspend() loop below */
sigemptyset(&blockMask);
sigaddset(&blockMask, SIGCHLD);
if (sigprocmask(SIG_SETMASK, &blockMask, NULL) == -1)
errExit("sigprocmask");
/* Create one child process for each command-line argument */
for (j = 1; j < argc; j++) {
switch (fork()) {
case -1:
errExit("fork");
case 0: /* Child - sleeps and then exits */
sleep(getInt(argv[j], GN_NONNEG, "child-sleep-time"));-----------------------------依次创建3个子进程,分别睡眠1、2、 4秒之后退出。
printf("%s Child %d (PID=%ld) exiting\n", currTime("%T"),
j, (long) getpid());
_exit(EXIT_SUCCESS);
default: /* Parent - loops to create next child */
break;
/* Parent comes here: wait for SIGCHLD until all children are dead */
sigemptyset(&emptyMask);
while (numLiveChildren > 0) {
if (sigsuspend(&emptyMask) == -1 && errno != EINTR)------------------------------------在此等待SIGCHLD信号。
errExit("sigsuspend");
sigCnt++;
printf("%s All %d children have terminated; SIGCHLD was caught "
"%d times\n", currTime("%T"), argc - 1, sigCnt);
exit(EXIT_SUCCESS);
分别创建3个子进程,执行结果如下:
./multi_SIGCHLD 1 2 4
10:27:10 Child 1 (PID=14225) exiting------------------------------睡眠1秒的进程退出。
10:27:10 handler: Caught SIGCHLD----------------------------------父进程收到SIGHLD信号,并进入信号处理函数。
10:27:10 handler: Reaped child 14225 - child exited, status=0-----waitpid()回收子进程资源,然后开始睡眠5秒。此时SIGCHLD信号处理是被阻塞的。
10:27:11 Child 2 (PID=14226) exiting
10:27:13 Child 3 (PID=14227) exiting------------------------------2、4秒睡眠的子进程依次退出,但是由于此时SIGCHLD信号处于阻塞状态,所以不会立即被处理。
10:27:15 handler: returning---------------------------------------直到SIGCHLD处理函数退出,对SIGCHLD的阻塞解除。
10:27:15 handler: Caught SIGCHLD----------------------------------再次触发SIGCHLD信号处理函数,但是仅触发一次。
10:27:15 handler: Reaped child 14226 - child exited, status=0
10:27:15 handler: Reaped child 14227 - child exited, status=0
10:27:20 handler: returning---------------------------------------第二次SIGCHLD信号处理函数退出。
10:27:20 All 3 children have terminated; SIGCHLD was caught 2 times---整个流程结束。
3.2 向已停止的子进程发送SIGCHLD信号
当信号导致子进程停止时,父进程也就有可能收到SIGCHLD信号。调用sigaction()设置SIGCHLD信号处理程序时,如传入SA_NOCLDSTOP标志即可控制主义行为。如未使用,系统会在子进程停止时向父进程发送SIGCHLD信号。反之,就不会因子进程的停止而发出SIGCHLD信号。
3.3 忽略终止的子进程
将对SIGCHLD的处置显式置为SIG_IGN,系统从而会将其后终止的子进程立即删除,毋庸转为僵尸进程。将子进程的状态弃之不问,故而后续的wait()调用不会返回子进程的任何信息。
4. 小结
联系方式:arnoldlu@qq.com