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 kernel.
Cada proceso se representa como un task_struct
dentro del núcleo (la definición está en el sched.h
archivo 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 zombi todavía tiene un task_struct
. La estructura permanece, hasta que el proceso padre (natural o por adopción) haya llamado wait()
después de recibir SIGCHLD
para cosechar su proceso hijo. Cuando se envía una señal, el signal_struct
Está establecido. No importa si la señal es detectable o no, en este caso.
Las señales se evalúan cada vez que se ejecuta el proceso. O para ser exactos, antes el proceso sería correr. El proceso está entonces en el TASK_RUNNING
estado. El núcleo ejecuta el schedule()
rutina que determina el siguiente proceso en ejecución de acuerdo con su algoritmo de programación. Suponiendo que este proceso es el siguiente proceso en ejecución, el valor de signal_struct
se evalúa si hay 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 la acción por defecto de la señal es ejecutado. La acción predeterminada depende de la señal que se envíe.
Por ejemplo, el SIGSTOP
el controlador predeterminado de la señal cambiará el estado del proceso actual a TASK_STOPPED
y luego ejecuta schedule()
para seleccionar un nuevo proceso para ejecutar. Aviso, SIGSTOP
no es capturable (como SIGKILL
), por lo tanto, no hay posibilidad de registrar un manejador de señal manual. En el caso de una señal indetectable, siempre se ejecutará la acción predeterminada.
A su pregunta:
El programador nunca determinará que un proceso inactivo o muerto esté en el TASK_RUNNING
estado de nuevo. Por lo tanto, el kernel nunca ejecutará el controlador de señal (predeterminado o definido) para la señal correspondiente, cualquiera que sea la señal. Por lo tanto el exit_signal
nunca se establecerá de nuevo. La señal se "entrega" al proceso configurando el signal_struct
en task_struct
del proceso, pero no pasará nada más, porque el proceso no volverá a ejecutarse nunca más. No hay código para ejecutar, todo lo que queda del proceso es esa estructura de proceso.
Sin embargo, si el proceso padre recoge a sus hijos por wait()
, el código de salida que recibe es el que tenía cuando el proceso murió "inicialmente". No importa si hay una señal esperando a ser manejada.
Un proceso zombie básicamente ya está muerto. Lo único es que nadie ha reconocido aún su muerte por lo que sigue ocupando una entrada en la tabla de procesos además de un bloque de control (la estructura que mantiene el kernel de Linux para cada hilo en actividad). Se recuperan otros recursos como bloqueos obligatorios de 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 tú 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 está durmiendo 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