如果我只是使用wait()等待1个孩子一次完成,我是否需要对SIGCHLD处理程序执行任何操作?
我有一个程序,要求孩子做一些工作,但我现在一次只做一个孩子。 我正在使用wait()
等待孩子完成,我是否也需要对SIGCHLD
做任何事情(例如禁用处理程序)?
在我的情况下,我在errno
获得了EINTR
的值,这让我认为我需要屏蔽SIGCHLD
。
从广义上讲,这是该计划:
- 读参数
- for(工作清单)
-
fork()
- 如果是child,
execlp()
来工作程序 - 如果是父母,则
wait()
孩子完成 - 当孩子完成时,父循环到下一个工作项
理论
POSIX说关于SIG_IGN
(严格按照XSI扩展说明):
如果SIGCHLD信号的操作设置为SIG_IGN,则调用进程的子进程在终止时不应转换为僵尸进程。 如果调用进程随后等待其子进程,并且进程没有被等待转换为僵尸进程的子进程,它将阻塞直到其所有子进程终止,并且wait(),waitid()和waitpid()将失败并将errno设置为[ECHILD]。
在
的描述中说SIGCHLD
的默认信号处理是 ‘(代码’I’)。 但是, SIG_IGN
SIG_DFL
行为是’忽略’信号; 永远不会产生信号。 这与SIG_IGN
行为不同。
因此,您不必设置信号处理程序,但是您不应该获得有关该过程的信息 – 一旦没有孩子,您就应该收到错误。
示范代码
#include #include #include #include #include #include static siginfo_t sig_info; static volatile sig_atomic_t sig_num; static void *sig_ctxt; static void catcher(int signum, siginfo_t *info, void *vp) { sig_num = signum; sig_info = *info; sig_ctxt = vp; } static void set_handler(int signum) { struct sigaction sa; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = catcher; sigemptyset(&sa.sa_mask); if (sigaction(signum, &sa, 0) != 0) { int errnum = errno; fprintf(stderr, "Failed to set signal handler (%d: %s)\n", errnum, strerror(errnum)); exit(1); } } static void prt_interrupt(FILE *fp) { if (sig_num != 0) { fprintf(fp, "Signal %d from PID %d\n", sig_info.si_signo, (int)sig_info.si_pid); sig_num = 0; } } static void five_kids(void) { for (int i = 0; i < 5; i++) { pid_t pid = fork(); if (pid < 0) break; else if (pid == 0) { printf("PID %d - exiting with status %d\n", (int)getpid(), i); exit(i); } else { int status = 0; pid_t corpse = wait(&status); printf("Child: %d; Corpse: %d; Status = 0x%.4X\n", pid, corpse, (status & 0xFFFF)); prt_interrupt(stdout); fflush(0); } } } int main(void) { printf("SIGCHLD set to SIG_IGN\n"); signal(SIGCHLD, SIG_IGN); five_kids(); printf("SIGCHLD set to catcher()\n"); set_handler(SIGCHLD); five_kids(); return(0); }
fflush(0);
call确保刷新标准输出(特别是),如果将示例程序的输出传送到另一个程序,则这很重要。
示例输出
示例代码的输出与理论一致 - 但需要一点解释。
SIGCHLD set to SIG_IGN PID 4186 - exiting with status 0 SIGCHLD set to SIG_IGN Child: 4186; Corpse: -1; Status = 0x0000 PID 4187 - exiting with status 1 Child: 4187; Corpse: -1; Status = 0x0000 PID 4188 - exiting with status 2 Child: 4188; Corpse: -1; Status = 0x0000 PID 4189 - exiting with status 3 Child: 4189; Corpse: -1; Status = 0x0000 PID 4190 - exiting with status 4 Child: 4190; Corpse: -1; Status = 0x0000 SIGCHLD set to catcher() PID 4191 - exiting with status 0 SIGCHLD set to catcher() Child: 4191; Corpse: -1; Status = 0x0000 Signal 20 from PID 4191 Child: 4192; Corpse: 4191; Status = 0x0000 PID 4192 - exiting with status 1 Child: 4193; Corpse: 4192; Status = 0x0100 Signal 20 from PID 4192 PID 4193 - exiting with status 2 Child: 4194; Corpse: 4193; Status = 0x0200 Signal 20 from PID 4193 PID 4194 - exiting with status 3 Child: 4195; Corpse: 4194; Status = 0x0300 Signal 20 from PID 4194 PID 4195 - exiting with status 4
输出的第一部分与理论完全一致; 调用代码没有关于死孩子的信息,除了没有死孩子(左)等待。
输出的第二部分也与理论一致,但似乎孩子们没有在父母之前执行,所以来自父母的第一个wait()
没有等待的死孩子,并且它返回-1。 随后的呼叫可以获得各种各样的孩子,但是一个人无法逃脱(但尸体将被系统清理)。 如果代码使用waitpid()
例如: pid_t corpse = waitpid(pid, &status, 0);
,它会依次等待每个孩子。
根据上面关于“忽略”的修改后的评论,本评论中的一部分可能需要修改。 TBD - 现在没时间了。
3 GHz Intel Core 2 Duo上的Mac OS X 10.8.4,GCC 4.6.0,64位编译:
gcc -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \ -Wold-style-definition sigchld.c -o sigchld
这是对一些现有代码的快速改编,以测试SIGINT
和pause()
的行为。 它可能含有一些多余的材料。 您不必在信号处理程序中调用wait()
或其中一个亲戚; 但是,如果你愿意,你可以这样做。
如果您不关心分叉子项的退出状态,则可以忽略SIGCHLD
。
signal(SIGCHLD, SIG_IGN);
如果您关心退出状态,则可以安装信号处理程序,并在信号处理程序中执行waitpid()
调用,以从退出的子进程中获取退出状态值。 此调用应该循环,直到没有更多的退出子项。 您将在第一个参数中使用-1
在最后一个参数中使用WNOHANG
以非阻塞方式等待任何子项。 如果waitpid()
返回0,则表示调用成功,但没有其他子节点等待。
void sigchld_handler (int sig) { assert(sig == SIGCHLD); int status; pid_t child; while ((child = waitpid(-1, &status, WNOHANG)) > 0) { /*...do something with exited child's status */ } } /*...*/ signal(SIGCHLD, sigchld_handler);