¿Qué sucede cuando se envía SIGKILL a un proceso Zombie en Linux?


10

En Linux, cuando un proceso hijo finaliza y su padre aún no lo ha esperado, se convierte en un proceso zombie. El código de salida del niño se almacena en el descriptor pid.

Si SIGKILLse envía un mensaje al niño, no se supone que haya ningún efecto.

¿Significa esto que el código de salida no será modificado por el SIGKILLcódigo de salida o será modificado para indicar que el niño salió porque recibió un SIGKILL?

Respuestas:


14

Para responder a esa pregunta, debe comprender cómo se envían las señales a un proceso y cómo existe un proceso en el núcleo.

Cada proceso se representa task_structdentro del núcleo (la definición está en el sched.harchivo de encabezado y comienza aquí ). Esa estructura contiene información sobre el proceso; por ejemplo el pid. La información importante está en la línea 1566 donde se almacena la señal asociada. Esto se establece solo si se envía una señal al proceso.

Un proceso muerto o un proceso zombie todavía tiene un task_struct. La estructura permanece, hasta que el proceso padre (natural o por adopción) ha llamado wait()después de recibir SIGCHLDpara cosechar su proceso hijo. Cuando se envía una señal, signal_structse establece. No importa si la señal es atrapable o no, en este caso.

Las señales se evalúan cada vez que se ejecuta el proceso. O para ser exactos, antes de que el proceso se ejecute. El proceso está entonces en el TASK_RUNNINGestado. El núcleo ejecuta la schedule()rutina que determina el siguiente proceso de ejecución de acuerdo con su algoritmo de programación. Suponiendo que este proceso es el siguiente proceso en ejecución, signal_structse evalúa el valor del mismo , ya sea que haya una señal de espera para ser manejada o no. Si un controlador de señal se define manualmente (a través de signal()o sigaction()), se ejecuta la función registrada, si no se ejecuta la acción predeterminada de la señal . La acción predeterminada depende de la señal que se envía.

Por ejemplo, el SIGSTOPcontrolador predeterminado de la señal cambiará el estado del proceso actual a TASK_STOPPEDy luego se ejecutará schedule()para seleccionar un nuevo proceso a ejecutar. Tenga en SIGSTOPcuenta que no es capturable (como SIGKILL), por lo tanto, no hay posibilidad de registrar un controlador de señal manual. En caso de una señal que no se puede capturar, la acción predeterminada siempre se ejecutará.


A su pregunta:

El planificador nunca determinará que un proceso inactivo o inactivo esté TASK_RUNNINGnuevamente en el estado. Por lo tanto, el núcleo nunca ejecutará el controlador de señal (predeterminado o definido) para la señal correspondiente, cualquiera que sea la señal. Por lo tanto exit_signal, nunca se volverá a configurar. La señal se "entrega" al proceso mediante el establecimiento de la signal_structen task_structel proceso, pero nada más va a pasar, porque el proceso nunca se ejecutará de nuevo. No hay código para ejecutar, todo lo que queda del proceso es esa estructura del proceso.

Sin embargo, si el proceso padre cosecha a sus hijos wait(), el código de salida que recibe es el que se produjo cuando el proceso "inicialmente" murió. No importa si hay una señal esperando a ser manejada.


Sí, pero ¿el comando en killsí devuelve 0 o 1?
Anthony Rutledge

9

Un proceso zombie básicamente ya está muerto. Lo único es que nadie ha reconocido su muerte todavía, por lo que continúa ocupando una entrada en la tabla de procesos, así como un bloque de control (la estructura que mantiene el kernel de Linux para cada hilo en actividad). Se reclaman otros recursos como bloqueos obligatorios en archivos, segmentos de memoria compartida, semáforos, etc.

No puede señalarlos porque nadie puede actuar sobre esta señal. Incluso las señales fatales como KILL son inútiles ya que el proceso ya ha terminado su ejecución. Puedes probarlo tu mismo:

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

Aquí, comienzo un proceso que se bifurca y duerme antes de esperar a su hijo. El niño no hace más que dormir un poco. Puedes matar al niño cuando duerme o justo después de que salga para ver la diferencia:

$ 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

Me gustaría encontrar el pasaje correspondiente en el código fuente del kernel para usted, pero estoy luchando para encontrar ...
lgeorget

@Igeorget Gracias pero está bien, no necesito ver el código del kernel.
user137481

Programa simple de Ruby que bifurca un proceso, y el niño sale inmediatamente ruby -e "loop while fork { exit! }"... imgur.com/SoRXErm
S.Goswami
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.