SIGKILL 上的 SIGCHLD
SIGCHLD on SIGKILL
程序fork_wait.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t a = fork();
if (a != 0) {
int status;
printf("I am your father. (...) You know it to be true !");
wait(&status);
}
else {
printf("NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!");
sleep(2000);
}
return 0;
}
Bash :
$ cc fork_wait.c -o fork_wait && ./fork_wait
I am your father. (...) You know it to be true !
NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
Bash 执行时:
$ pgrep fork_wait
37818
37819
$ kill -9 $(pgrep fork_wait | tail -1)
$ pgrep fork_wait
(nothing)
当进程被 SIGTERM 杀死时,谁发送了 SIGCHLD 信号?为什么杀了儿子就没有僵尸进程了?
这是对您的第一个程序的改编。它使用默认的 SIGCHLD 信号处理,这意味着当 child 死亡 (forkwait29.c
):
时,信号不会(由内核)传递给 parent 进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t a = fork();
if (a != 0)
{
printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("PID %5d exited with status 0x%.4X\n", corpse, status);
}
else
{
printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
sleep(2000);
}
return 0;
}
一个例子 运行 产生:
$ forkwait29
64001: I am your father. (...) You know it to be true!
64002: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 64002 exited with status 0x0009
$
您可以分析返回的状态(使用 WIFSIGNALED
、WIFTERMSIG
、WIFEXITED
、WEXITSTATUS
、WCOREDUMP
等),它会显示child 死掉是因为它收到信号 9,SIGKILL。正如评论中指出的那样,您的 parent 收集了已死的 child 进程(防止它成为 (long-lasting) 僵尸)并退出。
您可以像这样添加一些信号处理 (forkwait73.c
):
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
static volatile sig_atomic_t sig_caught = 0;
static void sig_handler(int signum)
{
sig_caught = signum;
}
int main(void)
{
pid_t a = fork();
if (a != 0)
{
printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
struct sigaction sa = { 0 };
sa.sa_handler = sig_handler;
sa.sa_flags = SA_RESTART;
if (sigemptyset(&sa.sa_mask) != 0)
return 1;
sigaction(SIGCHLD, &sa, NULL);
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
{
printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
corpse, status, sig_caught);
}
}
else
{
printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
sleep(2000);
}
return 0;
}
一个样品 运行 产生:
$ forkwait73
63964: I am your father. (...) You know it to be true!
63965: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 63965 exited with status 0x000F (caught = 20)
$
当我在 macOS Moneterey 12.2.1 上省略 SA_RESTART
标志时,我得到如下结果:
$ forkwait73
63929: I am your father. (...) You know it to be true!
63930: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
$
如果没有 SA_RESTART,parent 进程不会报告其 child 的死亡,因为 wait()
因 errno == EINTR
而失败。可以将循环修改为:
,而不是设置 SA_RESTART
while ((corpse = wait(&status)) > 0 || errno == EINTR)
{
printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
corpse, status, sig_caught);
errno = 0;
}
如果你想看到一个僵尸进程,你必须安排 parent 暂时不要 wait()
child。你也可以让它休眠,当 sleep()
调用结束时,它可以继续 wait()
循环。你也可以安排它处理另一个信号,这样你就可以发出信号让它醒来并发现它 child 已经死了。
程序fork_wait.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t a = fork();
if (a != 0) {
int status;
printf("I am your father. (...) You know it to be true !");
wait(&status);
}
else {
printf("NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!");
sleep(2000);
}
return 0;
}
Bash :
$ cc fork_wait.c -o fork_wait && ./fork_wait
I am your father. (...) You know it to be true !
NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
Bash 执行时:
$ pgrep fork_wait
37818
37819
$ kill -9 $(pgrep fork_wait | tail -1)
$ pgrep fork_wait
(nothing)
当进程被 SIGTERM 杀死时,谁发送了 SIGCHLD 信号?为什么杀了儿子就没有僵尸进程了?
这是对您的第一个程序的改编。它使用默认的 SIGCHLD 信号处理,这意味着当 child 死亡 (forkwait29.c
):
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t a = fork();
if (a != 0)
{
printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("PID %5d exited with status 0x%.4X\n", corpse, status);
}
else
{
printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
sleep(2000);
}
return 0;
}
一个例子 运行 产生:
$ forkwait29
64001: I am your father. (...) You know it to be true!
64002: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 64002 exited with status 0x0009
$
您可以分析返回的状态(使用 WIFSIGNALED
、WIFTERMSIG
、WIFEXITED
、WEXITSTATUS
、WCOREDUMP
等),它会显示child 死掉是因为它收到信号 9,SIGKILL。正如评论中指出的那样,您的 parent 收集了已死的 child 进程(防止它成为 (long-lasting) 僵尸)并退出。
您可以像这样添加一些信号处理 (forkwait73.c
):
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
static volatile sig_atomic_t sig_caught = 0;
static void sig_handler(int signum)
{
sig_caught = signum;
}
int main(void)
{
pid_t a = fork();
if (a != 0)
{
printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
struct sigaction sa = { 0 };
sa.sa_handler = sig_handler;
sa.sa_flags = SA_RESTART;
if (sigemptyset(&sa.sa_mask) != 0)
return 1;
sigaction(SIGCHLD, &sa, NULL);
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
{
printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
corpse, status, sig_caught);
}
}
else
{
printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
sleep(2000);
}
return 0;
}
一个样品 运行 产生:
$ forkwait73
63964: I am your father. (...) You know it to be true!
63965: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 63965 exited with status 0x000F (caught = 20)
$
当我在 macOS Moneterey 12.2.1 上省略 SA_RESTART
标志时,我得到如下结果:
$ forkwait73
63929: I am your father. (...) You know it to be true!
63930: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
$
如果没有 SA_RESTART,parent 进程不会报告其 child 的死亡,因为 wait()
因 errno == EINTR
而失败。可以将循环修改为:
while ((corpse = wait(&status)) > 0 || errno == EINTR)
{
printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
corpse, status, sig_caught);
errno = 0;
}
如果你想看到一个僵尸进程,你必须安排 parent 暂时不要 wait()
child。你也可以让它休眠,当 sleep()
调用结束时,它可以继续 wait()
循环。你也可以安排它处理另一个信号,这样你就可以发出信号让它醒来并发现它 child 已经死了。