![在 Linux 中向僵尸进程发送 SIGKILL 会发生什么?](https://linux22.com/image/81901/%E5%9C%A8%20Linux%20%E4%B8%AD%E5%90%91%E5%83%B5%E5%B0%B8%E8%BF%9B%E7%A8%8B%E5%8F%91%E9%80%81%20SIGKILL%20%E4%BC%9A%E5%8F%91%E7%94%9F%E4%BB%80%E4%B9%88%EF%BC%9F.png)
在Linux中,当子进程终止并且其父进程尚未等待它时,它就会成为僵尸进程。子进程的退出代码存储在 pid 描述符中。
如果将 aSIGKILL
发送给孩子,则不会有任何影响。
这是否意味着退出代码不会被修改,SIGKILL
或者退出代码会被修改以指示子进程因为收到了一个而退出SIGKILL
?
答案1
要回答这个问题,您必须了解信号如何发送到进程以及进程如何存在于内核中。
task_struct
每个进程在内核内部都被表示为一个(定义在sched.h
头文件中并开始这里)。该结构体保存有关进程的信息;例如pid。重要信息在1566行存储相关信号的位置。仅当信号发送到进程时才设置此值。
死亡进程或僵尸进程仍然具有task_struct
.该结构将保留,直到父进程(自然的或通过收养的)在wait()
接收后调用SIGCHLD
以获取其子进程。当发送信号时,将signal_struct
被设置。在这种情况下,信号是否可捕获并不重要。
每次进程运行时都会评估信号。或者准确地说,前过程会跑步。然后进程就处于TASK_RUNNING
状态。内核运行schedule()
例程,根据其调度算法确定下一个运行的进程。假设该进程是下一个正在运行的进程,则评估 的值signal_struct
,判断是否有等待信号需要处理。如果手动定义信号处理程序(通过signal()
或者sigaction()
),则执行注册的函数,如果不是信号的默认动作被执行。默认操作取决于发送的信号。
例如,SIGSTOP
信号的默认处理程序会将当前进程的状态更改为TASK_STOPPED
,然后运行schedule()
以选择要运行的新进程。请注意,SIGSTOP
是不可捕获的(如SIGKILL
),因此不可能注册手动信号处理程序。如果无法捕获信号,将始终执行默认操作。
对于你的问题:
调度程序永远不会确定失效或死亡的进程再次处于该TASK_RUNNING
状态。因此,内核永远不会为相应的信号运行信号处理程序(默认的或定义的),无论是哪个信号。因此exit_signal
永远不会被再次设置。通过设置进程的signal_struct
in来将信号“传递”到进程task_struct
,但不会发生其他任何事情,因为进程永远不会再运行。没有要运行的代码,进程剩下的就是进程结构。
但是,如果父进程通过 收割其子进程wait()
,则它收到的退出代码是进程“最初”死亡时的退出代码。是否有信号等待处理并不重要。
答案2
僵尸进程基本上已经死了。唯一的事情是,还没有人承认它的死亡,因此它继续占用进程表中的一个条目以及一个控制块(Linux 内核为活动中的每个线程维护的结构)。其他资源(例如文件强制锁、共享内存段、信号量等)将被回收。
您无法向他们发出信号,因为没有人可以根据该信号采取行动。即使像 KILL 这样的致命信号也是无用的,因为进程已经终止了执行。你可以自己尝试一下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if (pid == -1)
exit(-1);
if (pid > 0) {
//parent
printf("[parent]: I'm the parent, the pid of my child is %i\n"
"I'll start waiting for it in 10 seconds.\n", pid);
sleep(10);
int status;
wait(&status);
if (WIFSIGNALED(status)) {
printf("[parent]: My child has died from a signal: %i\n", WTERMSIG(status));
} else if (WIFEXITED(status)) {
printf("[parent]: My child has died from natural death\n");
} else {
printf("[parent]: I don't know what happened to my child\n");
}
} else {
//child
printf("[child]: I'm dying soon, try to kill me.\n");
sleep(5);
printf("[child]: Dying now!\n");
}
return 0;
}
在这里,我启动一个进程,在等待它的子进程之前分叉和休眠。孩子除了睡一会儿什么也不做。你可以在孩子睡觉时或刚退出时杀死它,看看有什么不同:
$ make zombie
cc zombie.c -o zombie
$ ./zombie
[parent]: I'm the parent, the pid of my child is 16693
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
# Here, I did "kill -15 16693" in another console
[parent]: My child has died from a signal: 15
$ ./zombie
[parent]: I'm the parent, the pid of my child is 16717
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
[child]: Dying now!
# Here, I did "kill -15 16717" in another console
[parent]: My child has died from natural death