1. Qué es un zombi
Un proceso zombi es un proceso que ha terminado su ejecución pero cuyo bloque de control de proceso (PCB) se conserva en la tabla de procesos, esperando que su padre lo recoga con wait() o waitpid(). El zombi no ejecuta código, no usa memoria (Más alla del PCB minimo), y no responde a senales. Solo ocupa una entrada en la tabla de procesos.
En la salida de ps, un zombi se reconoce por la letra Z en la columna STAT y por el nombre del proceso entre corchetes (por ejemplo, [worker]). En /proc/PID/status, el estado es Z (zombie).
El nombre "zombi" es metaforico: el proceso esta "muerto" (Terminó) pero sigue "vivo" en la tabla de procesos (su PCB existe). Es un estado intermedio entre "terminado" y "liberado".
Un zombi no usa CPU ni memoria (el núcleo libera la memoria, los archivos y los descriptores cuándo el proceso termina). Solo conserva el PCB minimo con el PID, el PPID y el código de salida. Pero ocupa una entrada en la tabla de procesos, y sí se acumulan, pueden agotar el limite de procesos.
2. Por que existe el zombi
El zombi existe porque el padre necesita saber Cómo terminó el hijo. Cuando un hijo termina, el núcleo guarda su código de salida (o la senal que lo terminó) en el PCB. El padre puede consultar esta información con wait() o waitpid().
Si el PCB se liberara inmediatamente al terminar el hijo, el padre no podria obtener el código de salida. El zombi es el mecanismo que permite al padre recoger esta información en cualquier momento después de que el hijo terminó.
El zombi es una solución al problema de la sincronizacion: el hijo puede terminar en cualquier momento, y el padre puede no estar listo para recogerlo inmediatamente. El zombi "guarda" el resultado hasta que el padre este listo.
En sistemás dónde el padre no necesita el código de salida (por ejemplo, Sí ignora SIGCHLD), el núcleo puede recoger al hijo automaticamente sin crear un zombi. Pero por omision, el zombi se crea para que el padre pueda consultar el resultado.
3. El ciclo de vida de un zombi
El ciclo de vida de un proceso que termina y queda como zombi es:
- El hijo termina: llama a exit() o return en main, o recibe una senal fatal.
- El núcleo libera los recursos: Memoria, archivos, descriptores. El proceso ya no se ejecuta.
- El núcleo guarda el código de salida: en el PCB, junto con el estado de terminación.
- El núcleo marca el proceso como zombi: cambia el estado a Z. El PCB se conserva.
- El núcleo envia SIGCHLD al padre: Sí el padre tiene un manejador, se ejecuta.
- El padre llama a wait(): el núcleo devuelve el estado de terminación y libera el PCB.
- El proceso desaparece: el PCB se libera y el PID Queda disponible para reutilizacion.
Si el padre nunca llama a wait(), el proceso queda como zombi indefinidamente (paso 5 Sin paso 6). El PCB se conserva hasta que el padre lo recoge o hasta que el padre termina.
Si el padre termina antes de recoger al zombi, el zombi queda huerfano y es adoptado por el proceso 1 (o un subreaper), Que lo recoge automaticamente. Esto se ve en el tema 26.
4. Cómo detectar un zombi
Un zombi se detecta fácilmente con las herramientas estandar:
$ ps -o pid,ppid,stat,comm -p 999
PID PPID STAT COMMAND
999 998 Z [worker]
# Buscar todos los zombis del sistema
$ ps -e -o pid,ppid,stat,comm | grep " Z"
999 998 Z [worker]
1000 998 Z [worker]
1001 998 Z [worker]
La letra Z en la columna STAT y el nombre entre corchetes son los indicadores de zombi. Tambien se puede contar:
$ ps -e -o stat | grep -c "^Z"
3
5. El problema de los zombis acumulados
Un zombi individual no es un problema: ocupa una entrada en la tabla de procesos y nada más. Pero sí un programa crea hijos y no los espera, los zombis se acumulan. Cada hijo que termina queda como zombi, y después de miles de hijos, la tabla de procesos se llena.
Cuando la tabla de procesos se llena, el sistema no puede crear más procesos: fork falla con EAGAIN. Esto puede hacer que el sistema deje de funciónar: no se pueden ejecutar comandos, no se pueden crear conexiones, etc.
El problema es especialmente grave en servidores que crean un proceso por conexion (Como apache en modo prefork). Si el servidor no recoge a los hijos terminados, se acumulan zombis y el servidor deja de funciónar.
Los zombis son el síntoma de un programa que no recoge a sus hijos. La causa es la falta de wait() o de un manejador de SIGCHLD. Arreglar el programa (agregar wait o SIGCHLD) elimina los zombis. Matar al padre (Que es el que debe recogerlos) También elimina los zombis, pero es una solución drastica.
6. Zombis en /proc
En /proc, un zombi tiene un directorio con su PID, pero la mayoria de los archivos estan vacios o no disponibles, porque el núcleo ya libero los recursos:
$ grep -E "^(Name|State|Pid|PPid)" /proc/999/status
Name: worker
State: Z (zombie)
Pid: 999
PPid: 998
$ ls /proc/999/
attr auxv cgroup clear_refs cmdline comm environ exe fd ...
$ cat /proc/999/cmdline # vacio
$ ls -l /proc/999/fd # vacio
$ cat /proc/999/maps # vacio
El directorio existe, pero los archivos estan vacios porque el núcleo ya libero la memoria, los descriptores y los argumentos. Solo el PCB minimo se conserva.
7. Zombis y el limite de procesos
Los zombis cuentan para el limite de procesos por usuario (ulimit -u) y para el limite del sistema (pid_max). Cada zombi ocupa una entrada en la tabla de procesos y un PID. Si se acumulan suficientes zombis, fork falla con EAGAIN.
Esto es especialmente grave en sistemás con muchos procesos de corta duracion (servidores web, scripts de automatizacion). Si el programa no recoge a los hijos, se agota el limite y el sistema deja de funciónar.
El limite de zombis que un sistema puede tolerar es el mismo que el limite de procesos: tipicamente miles. Pero en sistemás con limites bajos (contenedores, Sistemás embebidos), unos pocos cientos de zombis pueden ser suficientes para agotar el limite.
8. Zombi vs proceso detenido
Es importante no confundir un zombi (Z) con un proceso detenido (T). Aunque ambos no se ejecutan, son estados muy diferentes:
| Caracteristica | Zombi (Z) | Detenido (T) |
|---|---|---|
| Ya terminó? | Si | No |
| Se puede reanudar? | No | Si (con SIGCONT) |
| Responde a senales? | No | Si (SIGCONT lo reanuda) |
| Usa memoria? | No (Sólo PCB) | Si (toda su memoria) |
| Usa CPU? | No | No |
| Cómo se elimina? | El padre lo recoge con wait() | SIGCONT o SIGKILL |
| Nombre en ps | Entre corchetes [nombre] | Nombre normal |
9. Zombis en sistemás reales
En sistemás reales, los zombis son un problema comun. Las causas más frecuentes son:
- Programás que crean hijos y no los esperan: un servidor que crea un proceso por conexion y no llama a wait().
- Manejadores de SIGCHLD incorrectos: un manejador que no recoge a todos los hijos (Sin bucle while).
- Procesos que terminan inesperadamente: Sí el padre no maneja la senal SIGCHLD, los hijos terminados quedan como zombis.
- Procesos que crean hijos en bucle: Sin esperar a los anteriores, se acumulan zombis rápidamente.
Para diagnosticar zombis en un sistema:
# Contar zombis
$ ps -e -o stat | grep -c "^Z"
47
# Encontrar el padre de los zombis
$ ps -e -o pid,ppid,stat,comm | grep " Z"
999 998 Z [worker]
1000 998 Z [worker]
1001 998 Z [worker]
# El padre es el PID 998
$ ps -o pid,comm -p 998
PID COMMAND
998 servidor
Una vez identificado el padre, las opciones son:
- Arreglar el programa: agregar wait() o un manejador de SIGCHLD.
- Reiniciar el servicio: matar al padre (Que recoge a los zombis al terminar) y reiniciar.
- Matar al padre: los zombis quedan huerfanos y son adoptados por el proceso 1, Que los recoge.
10. Un programa que crea zombis
El siguiente programa crea zombis intencionalmente para demostrar como se detectan:
#define _POSIX_C_SOURCE 200809L
#include
#include
#include
int main(void)
{
printf("Padre PID: %d\n", (int) getpid());
printf("Creando 5 hijos que terminaran inmediatamente...\n");
for (int i = 0; i < 5; i++) {
pid_t pid = fork();
if (pid < 0) { perror("fork"); return 1; }
if (pid == 0) {
/* Hijo: terminar inmediatamente */
_exit(0);
}
/* Padre: NO espera al hijo */
}
printf("Hijos creados. Observa los zombis con: ps -o pid,ppid,stat,comm -p %d,...\n", (int) getpid());
printf("Presiona enter para recoger a los hijos y terminar...\n");
getchar();
/* Recoger a todos los hijos */
while (wait(NULL) > 0);
printf("Todos los hijos recogidos. Terminando.\n");
return 0;
}
$ gcc -std=c17 -Wall -Wextra -o crear_zombis crear_zombis.c
$ ./crear_zombis &
[1] 1050
Padre PID: 1050
Creando 5 hijos que terminaran inmediatamente...
Hijos creados. Observa los zombis con: ps -o pid,ppid,stat,comm -p 1050,...
$ ps -e -o pid,ppid,stat,comm | grep " Z"
1051 1050 Z [crear_zombis]
1052 1050 Z [crear_zombis]
1053 1050 Z [crear_zombis]
1054 1050 Z [crear_zombis]
1055 1050 Z [crear_zombis]
$ # Presionar enter en la terminal del programa
Todos los hijos recogidos. Terminando.
El programa crea 5 hijos que terminan inmediatamente. Como el padre no los espera, Quedan como zombis. Despues de presionar enter, el padre los recoge con wait() y los zombis desaparecen.
Mientras los zombis existen, puedes observarlos en /proc: sus directorios existen pero estan vacios (Sin memoria, Sin descriptores, Sin cmdline).
11. Resumen
- Un zombi es un proceso que terminó pero cuyo PCB se conserva hasta que el padre lo recoge.
- El zombi existe para que el padre pueda obtener el código de salida del hijo.
- Se detecta por la letra Z en ps y el nombre entre corchetes.
- No consume CPU ni memoria, Sólo una entrada en la tabla de procesos.
- Se acumulan sí el padre no recoge a los hijos, agotando el limite de procesos.
- En /proc, el directorio existe pero los archivos estan vacios.
- Se recoge con wait() o waitpid(), o automaticamente sí se ignora SIGCHLD.
- Si el padre termina, los zombis son adoptados por el proceso 1 y recogidos.
- Los zombis son un síntoma de un programa que no recoge a sus hijos.
12. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. Qué es un proceso zombi?
Un proceso que terminó su ejecución pero cuyo PCB se conserva en la tabla de procesos, esperando que el padre lo recoga.
2. Por que existe el zombi?
Para que el padre pueda obtener el código de salida del hijo. El PCB se conserva con el estado de terminación hasta que el padre lo recoge con wait().
3. Cómo se detecta un zombi en ps?
Por la letra Z en la columna STAT y el nombre del proceso entre corchetes (por ejemplo, [worker]).
4. Un zombi consume CPU o memoria?
No. No consume CPU ni memoria (el núcleo libera los recursos al terminar). Solo ocupa una entrada en la tabla de procesos y un PCB minimo.
5. Qué pasa sí los zombis se acumulan?
Se agota el limite de procesos (ulimit -u, pid_max). fork falla con EAGAIN y el sistema no puede crear más procesos.
6. Cómo se recoge a un zombi?
El padre llama a wait() o waitpid(). O, en linux, ignorando SIGCHLD, Que hace que los hijos se recogan automaticamente.
7. Qué pasa sí el padre termina antes de recoger al zombi?
El zombi queda huerfano y es adoptado por el proceso 1 (o un subreaper), Que lo recoge automaticamente.
8. Cómo se diferencia un zombi de un proceso detenido?
El zombi (Z) ya terminó y no se puede reanudar. El detenido (T) no terminó y se puede reanudar con SIGCONT.
9. Qué se ve en /proc para un zombi?
El directorio existe, pero los archivos (cmdline, maps, fd, etc.) estan vacios porque el núcleo ya libero los recursos.
10. Cómo se diagnostican zombis en un sistema?
Con ps -e -o pid,ppid,stat,comm | grep Z para listarlos, y luego identificar al padre (PPID) para arreglar el programa o reiniciar el servicio.
Para prácticar
- Compila y ejecuta crear_zombis.c. Observa los zombis con ps mientras el programa espera.
- Mientras los zombis existen, lee /proc/PID/status y /proc/PID/cmdline de uno de ellos.
- Presiona enter en el programa para recoger a los hijos y verifica que los zombis desaparecen.
- Cuenta los zombis de tu sistema con ps -e -o stat | grep -c "^Z".
- Escribe un programa que cree 100 hijos sin esperarlos y observa como se acumulan los zombis.
- Modifica el programa para ignorar SIGCHLD y verifica que no se crean zombis.