O que acontece ao enviar o SIGKILL para um processo de zumbi no Linux?

10

No Linux, quando um processo filho termina e seu pai ainda não o aguardou, ele se torna um processo zumbi. O código de saída da criança é armazenado no descritor pid.

Se a SIGKILLé enviado para a criança, não há efeito algum.

Isso significa que o código de saída não será modificado pelo SIGKILLcódigo de saída ou será modificado para indicar que o filho saiu porque recebeu um SIGKILL?

user137481
fonte

Respostas:

14

Para responder a essa pergunta, você precisa entender como os sinais são enviados para um processo e como existe um processo no kernel.

Cada processo é representado como um task_structdentro do kernel (a definição está no sched.harquivo de cabeçalho e começa aqui ). Essa estrutura contém informações sobre o processo; por exemplo, o pid. A informação importante está na linha 1566, onde o sinal associado é armazenado. Isso é definido apenas se um sinal for enviado ao processo.

Um processo morto ou um processo de zumbi ainda tem um task_struct. A estrutura permanece até que o processo pai (natural ou por adoção) seja chamado wait()após receber SIGCHLDpara colher seu processo filho. Quando um sinal é enviado, o signal_structé definido. Não importa se o sinal é captável ou não, neste caso.

Os sinais são avaliados sempre que o processo é executado. Ou para ser mais exato, antes de o processo seria executado. O processo está no TASK_RUNNINGestado. O kernel executa a schedule()rotina que determina o próximo processo em execução de acordo com seu algoritmo de agendamento. Assumindo que esse processo é o próximo processo em execução, o valor de signal_structé avaliado, independentemente de haver um sinal de espera a ser tratado ou não. Se um manipulador de sinal for definido manualmente (via signal()ou sigaction()), a função registrada será executada, caso contrário a ação padrão do sinal será executada. A ação padrão depende do sinal que está sendo enviado.

Por exemplo, o SIGSTOPmanipulador padrão do sinal alterará o estado do processo atual para TASK_STOPPEDe depois será executado schedule()para selecionar um novo processo a ser executado. Observe que SIGSTOPnão é alcançável (como SIGKILL), portanto, não há possibilidade de registrar um manipulador de sinal manual. No caso de um sinal inacessível, a ação padrão será sempre executada.


Para sua pergunta:

Um processo desativado ou inoperante nunca será determinado pelo planejador como estando no TASK_RUNNINGestado novamente. Portanto, o kernel nunca executará o manipulador de sinal (padrão ou definido) para o sinal correspondente, seja qual for o sinal. Portanto, o exit_signalnunca será definido novamente. O sinal é "entregue" ao processo, definindo o signal_structno task_structdo processo, mas nada mais vai acontecer, porque o processo nunca vai ficar novamente. Não há código para executar, tudo o que resta do processo é essa estrutura do processo.

No entanto, se o processo pai colhe seus filhos wait(), o código de saída que recebe é aquele em que o processo "inicialmente" morreu. Não importa se há um sinal aguardando para ser tratado.

caos
fonte
Sim, mas o killpróprio comando retorna 0 ou 1?
Anthony Rutledge
9

Um processo de zumbi já está basicamente morto. A única coisa é que ninguém reconheceu sua morte ainda; portanto, continua ocupando uma entrada na tabela de processos e um bloco de controle (a estrutura que o kernel do Linux mantém para cada encadeamento em atividade). Outros recursos, como bloqueios obrigatórios em arquivos, segmentos de memória compartilhada, semáforos etc., são recuperados.

Você não pode sinalizá-los porque ninguém pode agir sobre esse sinal. Até sinais fatais como KILL são inúteis, pois o processo já terminou sua execução. Você pode tentar:

#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;
}

Aqui, inicio um processo que bifurca e dorme antes de esperar pelo filho. A criança não faz nada além de dormir um pouco. Você pode matar a criança quando ela estiver dormindo ou logo após sair para ver a diferença:

$ 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
fonte
Eu gostaria de encontrar a passagem relevante no código-fonte do kernel para você, mas eu estou lutando para encontrá-lo ...
lgeorget
@ Igeorget Obrigado, mas tudo bem, não preciso ver o código do kernel.
precisa saber é o seguinte
Programa de Ruby simples que bifurca um processo, e que a criança sai imediatamente ruby -e "loop while fork { exit! }"... imgur.com/SoRXErm
S.Goswami