O que acontece quando enviamos o SIGKILL para um processo de zumbis no Linux?

8

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

Se um SIGKILL for enviado para a criança, não deve haver nenhum efeito.

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

    
por user137481 22.01.2016 / 04:38

2 respostas

11

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

Cada processo é representado como task_struct dentro do kernel (a definição está no arquivo de cabeçalho sched.h e beginns aqui . Essa estrutura contém informações sobre o processo; por exemplo o pid. As informações importantes estão na linha 1566 onde o sinal associado é armazenado. Isso é definido apenas se um sinal for enviado ao processo.

Um processo morto ou um processo zumbi ainda tem task_struct . A estrutura permanece, até que o processo pai (natural ou por adoção) tenha chamado wait() depois de receber SIGCHLD para colher seu processo filho. Quando um sinal é enviado, o signal_struct é definido. Não importa se o sinal é acessível ou não, neste caso.

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

Por exemplo, o manipulador padrão do sinal SIGSTOP alterará o estado do processo atual para TASK_STOPPED e, em seguida, executará schedule() para selecionar um novo processo a ser executado. Observe que SIGSTOP não pode ser acessado (como SIGKILL ), portanto, não há possibilidade de registrar um manipulador de sinal manual. No caso de um sinal não detectável, a ação padrão será sempre executada.

Para sua pergunta:

Um processo extinto ou morto nunca será determinado pelo agendador para estar novamente no estado TASK_RUNNING . Assim, o kernel nunca executará o manipulador de sinais (padrão ou definido) para o sinal correspondente, qualquer que seja o sinal. Portanto, o exit_signal nunca será definido novamente. O sinal é "delievered" para o processo, definindo o signal_struct em task_struct do processo, mas nada mais acontecerá, porque o processo nunca será executado novamente. Não há código para rodar, tudo o que resta do processo é aquele struct do processo.

Se o processo pai, no entanto, colhe seus filhos por 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.

    
por 22.01.2016 / 10:56
8

Um processo de zumbis já está basicamente morto. A única coisa é que ninguém reconheceu sua morte ainda assim continua ocupando uma entrada na tabela de processos, bem como um bloco de controle (a estrutura que o kernel do Linux mantém para cada thread 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 este sinal. Mesmo sinais fatais como o KILL são inúteis, pois o processo já terminou sua execução. Você pode tentar você mesmo:

#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 está 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
    
por 22.01.2016 / 09:23