在 Linux 中向僵尸进程发送 SIGKILL 会发生什么?

在 Linux 中向僵尸进程发送 SIGKILL 会发生什么?

在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_structin来将信号“传递”到进程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

相关内容