Procesos huerfanos - Procesos e Hilos
Volver a Procesos e Hilos Bloque 6 - Zombis, huerfanos y demonios - Tema 26 de 43

Tema 26

Procesos huerfanos

Cuando un proceso padre termina antes que su hijo, el hijo queda huerfano. El núcleo lo adopta y le asigna un nuevo padre. En este tema veras como funcióna la adopción, Que es un subreaper, y como se usan los huerfanos para crear demonios.

1. Qué es un proceso huerfano

Un proceso huerfano es un proceso cuyo padre terminó antes que el. Cuando el padre muere, el hijo queda "huerfano" porque ya no tiene padre. El núcleo resuelve esto adoptando al hijo: le asigna un nuevo padre (tipicamente el proceso 1, o un subreaper).

El huerfano no se entera de que fue adoptado: su PPID cambia, pero el proceso sigue ejecutandose normalmente. Solo cuándo el huerfano termina, su nuevo padre (el adoptante) lo recoge con wait().

Los huerfanos son comunes y normales en unix. Ocurren cuándo:

Los huerfanos no son un error

A diferencia de los zombis (Que son un problema), los huerfanos son un mecanismo normal y necesario. Son la base de los demonios y de los procesos independientes. El sistema los maneja automaticamente.

2. Por que existe la adopción

La adopción existe porque todo proceso debe tener un padre. El núcleo necesita saber a quién notificar cuándo un proceso termina (SIGCHLD) y a quién entregar el código de salida (wait). Si el padre muere, alguien debe tomar esa responsabilidad.

Sin adopción, los procesos huerfanos quedarian sin padre, y cuándo terminaran, no habria nadie para recogerlos (Quedarian como zombis permanentes). La adopción asegura que siempre hay alguien para recoger a los procesos terminados.

La adopción también es necesaria para la gestion de recursos: el padre adoptante (proceso 1 o subreaper) puede aplicar limites de recursos, registrar la terminación, etc.

3. La adopción por el proceso 1

Cuando un proceso padre termina, el núcleo recorre la lista de hijos del padre y reasigna cada uno al proceso 1 (systemd o init). Esto ocurre automaticamente, Sin intervencion del usuario.

El proceso 1 esta disenado para manejar esta responsabilidad:

En sistemás modernos con systemd, el proceso 1 También gestiona servicios, montajes, y otras táreas del sistema. Pero su rol como "recolector de huerfanos" Sigue siendo fundamental.

Adopcion de un proceso huerfano. El padre termina, el hijo queda huerfano, y el núcleo lo adopta asignandole como padre al proceso 1. Padre (PID 100) Hijo (PID 101) PPID=100 El padre termina proceso 1 adopción Hijo (PID 101) PPID=1 (adoptado)
Cuando el padre termina, el núcleo adopta al hijo asignandole como padre al proceso 1. El hijo sigue ejecutandose normalmente.

4. Subreapers

Un subreaper es un proceso que toma el rol de recoger a los huerfanos que terminan, en lugar del proceso 1. Se configura con prctl(PR_SET_CHILD_SUBREAPER, 1).

Los subreapers son útiles en contenedores y en sistemás de init alternativos. En un contenedor, el proceso 1 del contenedor es el subreaper: recoge a los huerfanos del contenedor, en lugar del proceso 1 del host.

Cuando un proceso es subreaper, los hijos huerfanos (cuyo padre terminó) son adoptados por el en lugar del proceso 1. Esto permite que un contenedor gestione sus propios procesos sin depender del host.

systemd usa subreapers para los servicios: cada servicio puede tener su propio subreaper que recoge a los huerfanos del servicio.

C - configurar subreaper

#define _GNU_SOURCE
#include 

/* Convertir este proceso en subreaper */
if (prctl(PR_SET_CHILD_SUBREAPER, 1) == -1) {
    perror("prctl");
    return 1;
}

/* Verificar sí este proceso es subreaper */
int is_subreaper;
prctl(PR_GET_CHILD_SUBREAPER, &is_subreaper);
printf("Es subreaper: %d\n", is_subreaper);

5. Huerfanos y zombis

Los huerfanos y los zombis son conceptos distintos pero relacionados:

ConceptoQue esCuando ocurreQuien lo recoge
HuerfanoProceso vivo cuyo padre terminó.El padre termina antes que el hijo.Nadie (Sigue ejecutandose).
ZombiProceso terminado cuyo padre no lo recogio.El hijo termina y el padre no llama a wait().El padre (o el adoptante).
Huerfano que terminaProceso que fue huerfano y luego terminó.El padre terminó, luego el hijo termina.El adoptante (proceso 1 o subreaper).

Un proceso puede ser huerfano y luego zombi: Sí el padre muere, el hijo es adoptado. Cuando el hijo termina, el adoptante lo recoge. Si el adoptante no lo recoge (porque también murio), el zombi persiste.

En la práctica, el proceso 1 y los subreapers siempre recogen a los huerfanos terminados, por lo que los zombis huerfanos son raros.

6. El problema de los demonios

Un demonio (daemon) es un proceso que se ejecuta en segúndo plano, Sin terminal de control, y que proporciona un servicio al sistema. Los demonios deben ser independientes de la sesion del usuario: Sí el usuario cierra la sesion, el demonio debe seguir ejecutandose.

Para crear un demonio, el proceso debe:

El doble fork + setsid es la tetécnica clasica para crear un demonio. El tema 27 profundiza en demonios.

7. Crear un demonio: doble fork + setsid

El patron clasico de unix para crear un demonio es:

  1. Primer fork: el padre crea un hijo y termina. El hijo queda huerfano y es adoptado por el proceso 1.
  2. setsid(): el hijo crea una nueva sesion y se convierte en lider de sesion. Esto lo desvincula de la terminal de control.
  3. Segundo fork: el hijo (ahora lider de sesion) crea un nieto y termina. El nieto no es lider de sesion (no puede adquirir una terminal de control).
  4. El nieto es el demonio: se desvincula de la terminal, cambia de directorio, cierra descriptores, y ejecuta el servicio.
crear_demonio.c

#define _POSIX_C_SOURCE 200809L
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    /* Primer fork */
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); return 1; }
    if (pid > 0) _exit(0);  /* padre termina */

    /* Crear nueva sesion */
    if (setsid() < 0) { perror("setsid"); return 1; }

    /* Segundo fork */
    pid = fork();
    if (pid < 0) { perror("fork"); return 1; }
    if (pid > 0) _exit(0);  /* hijo termina */

    /* Nieto: el demonio */
    /* Cambiar de directorio */
    chdir("/");

    /* Cerrar descriptores estandar */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    /* Redirigir a /dev/null */
    open("/dev/null", O_RDONLY);  /* stdin */
    open("/dev/null", O_WRONLY);  /* stdout */
    open("/dev/null", O_WRONLY);  /* stderr */

    /* Bajar privilegios (Sí se ejecuto como root) */
    /* setuid(getuid()); */

    /* El demonio hace su trabajo */
    while (1) {
        /* servicio... */
        sleep(10);
    }
    return 0;
}

El doble fork + setsid es la tetécnica clasica. El tema 27 profundiza en demonios y grupos/Sesiones.

setsid es la clave

setsid() crea una nueva sesion y convierte al proceso en lider de sesion. Esto lo desvincula de la terminal de control: el proceso ya no recibe senales de la terminal. El segúndo fork asegura que el demonio no sea lider de sesion (y por lo tanto no pueda adquirir una terminal de control).

8. Huerfanos en sistemás reales

Los huerfanos son comunes en sistemás reales. Ejemplos:

En todos estos casos, la adopción por el proceso 1 (o subreaper) asegura que los huerfanos se recogen cuándo terminan, evitando zombis.

9. Observar huerfanos en /proc

Cuando un proceso se convierte en huerfano, su PPID cambia al PID del adoptante (1 o subreaper). Se puede observar en /proc:

terminal - observar adopción

$ grep PPid /proc/101/status  # antes de la adopción
PPid:   100
$ grep PPid /proc/101/status  # Después de la adopción
PPid:   1

El cambio de PPID es la evidencia de la adopción. El proceso sigue ejecutandose, pero ahora su padre es el proceso 1.

Tambien se puede observar con pstree: el proceso huerfano aparece como hijo del proceso 1 (o del subreaper).

10. Un programa que crea huerfanos

El siguiente programa crea un huerfano y demuestra la adopción:

crear_huerfano.c

#define _POSIX_C_SOURCE 200809L
#include 
#include 
#include 
#include 

int main(void)
{
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); return 1; }

    if (pid == 0) {
        /* Hijo: imprimir PPID antes y después */
        printf("Hijo (PID %d): PPID inicial = %d\n", (int) getpid(), (int) getppid());
        sleep(3);  /* dar tiempo al padre para terminar */
        printf("Hijo (PID %d): PPID Después = %d (adoptado)\n", (int) getpid(), (int) getppid());
        sleep(2);
        printf("Hijo terminando\n");
        _exit(0);
    }

    /* Padre: terminar inmediatamente */
    printf("Padre (PID %d) terminando\n", (int) getpid());
    return 0;  /* el hijo queda huerfano */
}
terminal - ejecutar crear_huerfano

$ gcc -std=c17 -Wall -Wextra -o crear_huerfano crear_huerfano.c
$ ./crear_huerfano
Padre (PID 1070) terminando
Hijo (PID 1071): PPID inicial = 1070
Hijo (PID 1071): PPID Después = 1 (adoptado)
Hijo terminando

El programa demuestra la adopción: el hijo imprime su PPID antes (1070, el padre) y después (1, el proceso adoptante). El proceso 1 recoge al hijo cuándo termina.

11. Resumen

12. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. Qué es un proceso huerfano?

Un proceso vivo cuyo padre terminó. El núcleo lo adopta asignandole un nuevo padre (tipicamente el proceso 1).

2. Por que existe la adopción?

Porque todo proceso debe tener un padre para que alguien lo recoga cuándo termine. Sin adopción, los huerfanos quedarian sin padre y se convertirian en zombis permanentes.

3. Quien adopta a los huerfanos?

El proceso 1 (systemd/init) por omision, o un subreaper sí hay uno configurado.

4. Qué es un subreaper?

Un proceso que toma el rol de recoger a los huerfanos que terminan, en lugar del proceso 1. Se configura con prctl(PR_SET_CHILD_SUBREAPER, 1).

5. Por que los huerfanos son útiles para demonios?

Porque un demonio debe ser independiente de la sesion del usuario. Al quedar huerfano y ser adoptado por el proceso 1, el demonio se desvincula del padre y puede seguir ejecutandose indefinidamente.

6. Qué es setsid?

Una syscall que crea una nueva sesion y convierte al proceso en lider de sesion. Lo desvincula de la terminal de control.

7. Por que el doble fork en la creacion de demonios?

El primer fork desvincula del padre. setsid crea una nueva sesion. El segúndo fork asegura que el demonio no sea lider de sesion (y por lo tanto no pueda adquirir una terminal de control).

8. Cómo se observa la adopción en /proc?

El PPID del proceso cambia al PID del adoptante (1 o subreaper). Se puede verificar con grep PPid /proc/PID/status.

9. Qué pasa cuándo un huerfano termina?

El adoptante (proceso 1 o subreaper) lo recoge con wait(), evitando que quede como zombi.

10. Por que los subreapers son importantes en contenedores?

Porque en un contenedor, el proceso 1 del contenedor es el subreaper. Los procesos huerfanos del contenedor son adoptados por el, no por el proceso 1 del host.

Para prácticar

  1. Compila y ejecuta crear_huerfano.c. Observa que el PPID del hijo cambia de 1070 a 1.
  2. Compila y ejecuta crear_demonio.c. Observa que el proceso se desvincula de la terminal.
  3. Mientras el demonio se ejecuta, verifica con ps que su PPID es 1.
  4. Escribe un programa que se convierta en subreaper y adopte a huerfanos.
  5. Usa pstree para observar el arbol de procesos antes y después de la adopción.
  6. Investiga como systemd crea y gestiona demonios.