Что происходит при отправке SIGKILL в Zombie Process в Linux?

10

В Linux, когда дочерний процесс завершается, а его родитель еще не ждал его, он становится процессом зомби. Код выхода дочернего элемента хранится в дескрипторе pid.

Если а SIGKILLотправлено ребенку, не должно быть никакого эффекта.

Означает ли это, что код выхода не будет изменен SIGKILLили будет изменен код выхода, чтобы указать, что ребенок вышел из-за того, что он получил SIGKILL?

user137481
источник

Ответы:

14

Чтобы ответить на этот вопрос, вы должны понять, как сигналы отправляются процессу и как процесс существует в ядре.

Каждый процесс представлен task_structвнутри ядра (определение находится в sched.hзаголовочном файле и начинается здесь ). Эта структура содержит информацию о процессе; например пид. Важная информация находится в строке 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в task_structпроцессе, но ничего не произойдет, потому что процесс никогда не будет работать снова. Код для запуска отсутствует, все, что осталось от процесса, - это структура процесса.

Однако, если родительский процесс wait()получает свои дочерние элементы , код выхода, который он получает, является тем, когда процесс «изначально» умер. Неважно, есть ли сигнал, ожидающий обработки.

хаос
источник
Да, но сама команда killвозвращает 0 или 1?
Энтони Ратледж
9

Процесс зомби в основном уже мертв. Единственное, что никто еще не признал его смерть, поэтому он продолжает занимать запись в таблице процессов, а также в блоке управления (структура, поддерживаемая ядром 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
lgeorget
источник
Я хотел бы найти соответствующий отрывок в исходном коде ядра для вас, но я изо всех сил пытаюсь найти его ...
lgeorget
@Igeorget Спасибо, но это нормально, мне не нужно видеть код ядра.
user137481
Простая Ruby-программа, которая разветвляется на процесс, и ребенок сразу же выходит ruby -e "loop while fork { exit! }"... imgur.com/SoRXErm
S.Goswami