¿Cómo hacer que el proceso hijo muera después de que el padre salga?

13 minutos de lectura

avatar de usuario
Paweł Hajdan

Supongamos que tengo un proceso que genera exactamente un proceso secundario. Ahora, cuando el proceso principal finaliza por cualquier motivo (normal o anormalmente, por eliminación, ^C, falla de afirmación o cualquier otra cosa), quiero que el proceso secundario muera. ¿Cómo hacer eso correctamente?


Alguna pregunta similar sobre stackoverflow:

  • (Preguntado anteriormente) ¿Cómo puedo hacer que un proceso secundario salga cuando el padre lo hace?
  • (Preguntado más tarde) ¿Los procesos secundarios creados con fork () se eliminan automáticamente cuando se elimina el padre?

Alguna pregunta similar sobre stackoverflow para ventanas:

  • ¿Cómo destruyo automáticamente los procesos secundarios en Windows?
  • Matar proceso hijo cuando se mata proceso padre

avatar de usuario
maxschlepzig

En Linux, puede instalar una señal de muerte principal en el hijo, por ejemplo:

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() != ppid_before_fork)
        exit(1);
    // continue child execution ...

Tenga en cuenta que almacenar la identificación del proceso padre antes de la bifurcación y probarla en el hijo después prctl() elimina una condición de carrera entre prctl() y la salida del proceso que llamó al niño.

También tenga en cuenta que la señal de muerte del padre del hijo se borra en los hijos propios recién creados. No se ve afectado por un execve().

Esa prueba se puede simplificar si estamos seguros de que el proceso del sistema que está a cargo de adoptar todos huérfanos tiene PID 1:

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() == 1)
        exit(1);
    // continue child execution ...

Confiando en que el proceso del sistema sea init y tener PID 1 no es portátil, sin embargo. POSIX.1-2008 especifica:

El Id. de proceso principal de todos los procesos secundarios existentes y los procesos zombis del proceso de llamada se establecerá en el Id. de proceso de un proceso de sistema definido por la implementación. Es decir, estos procesos serán heredados por un proceso de sistema especial.

Tradicionalmente, el proceso del sistema que adopta todos los huérfanos es PID 1, es decir, init, que es el ancestro de todos los procesos.

En sistemas modernos como linux o FreeBSD otro proceso podría tener ese papel. Por ejemplo, en Linux, un proceso puede llamar prctl(PR_SET_CHILD_SUBREAPER, 1) establecerse como un proceso del sistema que hereda todos los huérfanos de cualquiera de sus descendientes (cf. un ejemplo en Fedora 25).

avatar de usuario
Phil Rütschman

Si no puede modificar el proceso secundario, puede intentar algo como lo siguiente:

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(pipes[0], STDIN_FILENO); /* Use reader end as stdin (fixed per  maxschlepzig */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

Esto ejecuta al niño desde dentro de un proceso de shell con el control de trabajo habilitado. El proceso secundario se genera en segundo plano. El shell espera una nueva línea (o un EOF) y luego mata al niño.

Cuando el padre muere, sin importar la razón, cerrará su extremo del conducto. El shell secundario obtendrá un EOF de la lectura y procederá a eliminar el proceso secundario en segundo plano.

  • Bien, pero cinco llamadas al sistema y un sh generado en diez líneas de códigos me deja un poco escéptico sobre el rendimiento de este código.

    – Oleiada

    17 de enero de 2014 a las 9:53

  • +1. Puedes evitar el dup2 y hacerse cargo de stdin usando el read -u marca para leer desde un descriptor de archivo específico. También agregué un setpgid(0, 0) en el niño para evitar que salga al presionar ^C en la terminal.

    – Greg Hewgill

    1 de mayo de 2014 a las 0:09


  • El orden argumental de la dup2() la llamada esta mal Si quieres usar pipes[0] como stdin tienes que escribir dup2(pipes[0], 0) en vez de dup2(0, pipes[0]). Estádup2(oldfd, newfd) donde la llamada cierra un newfd previamente abierto.

    – maxschlepzig

    30 de abril de 2016 a las 8:23

  • @Oleiade, estoy de acuerdo, especialmente porque el sh generado hace solo otra bifurcación para ejecutar el proceso del niño real …

    – maxschlepzig

    30 de abril de 2016 a las 8:26

avatar de usuario
qrdl

El niño puede pedirle al kernel que entregue SIGHUP (u otra señal) cuando el padre muere especificando la opción PR_SET_PDEATHSIG en prctl() llamada al sistema como esta:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Ver man 2 prctl para detalles.

Editar: Esto es solo para Linux

  • Esta es una mala solución porque es posible que el padre ya haya muerto. Condición de carrera. Solución correcta: stackoverflow.com/a/17589555/412080

    – Máximo Egorushkin

    22 de diciembre de 2015 a las 17:49


  • Decir que una respuesta es mala no es muy agradable, incluso si no aborda una condición de carrera. Ver mi respuesta sobre cómo usar prctl() de forma libre de condiciones de carrera. Por cierto, la respuesta vinculada por Maxim es incorrecta.

    – maxschlepzig

    29/04/2016 a las 18:36

  • Esto es solo una respuesta incorrecta. Enviará la señal al proceso secundario en el momento en que muere el subproceso que llamó a la bifurcación, no cuando muere el proceso principal.

    – Lotario

    10 de diciembre de 2016 a las 1:01

  • @Lothar Sería bueno ver algún tipo de prueba. man prctl dice: Establezca la señal de muerte del proceso principal del proceso de llamada en arg2 (ya sea un valor de señal en el rango 1..maxsig, o 0 para borrar). Esta es la señal que recibirá el proceso de llamada cuando su padre muera. Este valor se borra para el hijo de una bifurcación (2) y (desde Linux 2.4.36 / 2.6.23) cuando se ejecuta un binario set-user-ID o set-group-ID.

    – qrdl

    10 dic 2016 a las 19:46


  • @maxschlepzig Gracias por el nuevo enlace. Parece que el enlace anterior no es válido. Por cierto, después de años, todavía no hay API para configurar opciones en el lado principal. Qué pena.

    – Rox

    27 de abril de 2017 a las 3:47


avatar de usuario
Alnitak

No creo que sea posible garantizar que use solo llamadas POSIX estándar. Como en la vida real, una vez que se genera un niño, tiene vida propia.

Eso es Es posible que el proceso principal atrape la mayoría de los posibles eventos de finalización e intente eliminar el proceso secundario en ese punto, pero siempre hay algunos que no se pueden detectar.

Por ejemplo, ningún proceso puede atrapar un SIGKILL. Cuando el kernel maneja esta señal, eliminará el proceso especificado sin notificación alguna a ese proceso.

Para extender la analogía, la única otra forma estándar de hacerlo es que el niño se suicide cuando descubre que ya no tiene un padre.

Hay una forma exclusiva de Linux de hacerlo con prctl(2) – ver otras respuestas.

Estoy tratando de resolver el mismo problema, y ​​dado que mi programa debe ejecutarse en OS X, la solución solo para Linux no funcionó para mí.

Llegué a la misma conclusión que las otras personas en esta página: no hay una forma compatible con POSIX de notificar a un niño cuando muere un padre. Así que hice la siguiente mejor cosa: tener la encuesta infantil.

Cuando un proceso principal muere (por cualquier motivo), el proceso principal del hijo se convierte en el proceso 1. Si el hijo simplemente sondea periódicamente, puede verificar si su padre es 1. Si es así, el hijo debe salir.

Esto no es muy bueno, pero funciona, y es más fácil que las soluciones de sondeo TCP socket/lockfile sugeridas en otras partes de esta página.

  • Excelente solución. Continuamente invocando getppid() hasta que devuelva 1 y luego salga. Esto es bueno y ahora lo uso también. Sin embargo, una solución sin encuestas sería buena. Gracias Schof.

    – ojo de neón

    11 de abril de 2010 a las 11:58

  • Solo para información, en Solaris si está en una zona, el gettpid() no se convierte en 1 pero obtiene el pid del programador de zonas (proceso zsched).

    – Patrick Schlüter

    14/10/2010 a las 13:32

  • Si alguien se pregunta, en los sistemas Android, el pid parece ser 0 (procesar el pid del sistema) en lugar de 1, cuando el padre muere.

    – Rui Marqués

    2 oct 2012 a las 17:38

  • Para tener una forma más robusta e independiente de la plataforma de hacerlo, antes de fork()-ing, simplemente getpid() y si getppid() del niño es diferente, salga.

    – Sebastián

    10 de agosto de 2017 a las 13:42

  • Esto no funciona si no controla el proceso secundario. Por ejemplo, estoy trabajando en un comando que envuelve find(1), y quiero asegurarme de que find se elimine si el contenedor muere por algún motivo.

    – Lucretiel

    19 de junio de 2018 a las 2:03

Algunos carteles ya han mencionado pipas y kqueue. De hecho, también puede crear un par de conectados Conectores de dominio Unix por el socketpair() llamar. El tipo de enchufe debe ser SOCK_STREAM.

Supongamos que tiene los dos descriptores de archivo de socket fd1, fd2. Ahora fork() para crear el proceso hijo, que heredará el fds. En el padre cierras fd2 y en el hijo cierras fd1. Ahora cada proceso puede poll() el resto abierto fd en su propio extremo para el POLLIN evento. Siempre y cuando cada lado no explícitamente close() su fd durante la vida normal, puede estar bastante seguro de que un POLLHUP La bandera debe indicar la terminación del otro (no importa si está limpio o no). Al ser notificado de este evento, el niño puede decidir qué hacer (por ejemplo, morir).

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}

Puede intentar compilar el código de prueba de concepto anterior y ejecutarlo en una terminal como ./a.out &. Tiene aproximadamente 100 segundos para experimentar con la eliminación del PID principal mediante varias señales, o simplemente saldrá. En cualquier caso, debería ver el mensaje “hijo: el padre colgó”.

En comparación con el método que utiliza SIGPIPE controlador, este método no requiere probar el write() llamar.

Este método también es simétricoes decir, los procesos pueden usar el mismo canal para monitorear la existencia de los demás.

Esta solución solo llama a las funciones POSIX. Probé esto en Linux y FreeBSD. Creo que debería funcionar en otros Unixes, pero realmente no lo he probado.

Ver también:

  • unix(7) de las páginas man de Linux, unix(4) para FreeBSD, poll(2), socketpair(2), socket(7) en Linux.

  • Excelente solución. Continuamente invocando getppid() hasta que devuelva 1 y luego salga. Esto es bueno y ahora lo uso también. Sin embargo, una solución sin encuestas sería buena. Gracias Schof.

    – ojo de neón

    11 de abril de 2010 a las 11:58

  • Solo para información, en Solaris si está en una zona, el gettpid() no se convierte en 1 pero obtiene el pid del programador de zonas (proceso zsched).

    – Patrick Schlüter

    14/10/2010 a las 13:32

  • Si alguien se pregunta, en los sistemas Android, el pid parece ser 0 (procesar el pid del sistema) en lugar de 1, cuando el padre muere.

    – Rui Marqués

    2 oct 2012 a las 17:38

  • Para tener una forma más robusta e independiente de la plataforma de hacerlo, antes de fork()-ing, simplemente getpid() y si getppid() del niño es diferente, salga.

    – Sebastián

    10 de agosto de 2017 a las 13:42

  • Esto no funciona si no controla el proceso secundario. Por ejemplo, estoy trabajando en un comando que envuelve find(1), y quiero asegurarme de que find se elimine si el contenedor muere por algún motivo.

    – Lucretiel

    19 de junio de 2018 a las 2:03

Esta solución funcionó para mí:

  • Pase la tubería stdin al niño: no tiene que escribir ningún dato en la transmisión.
  • El niño lee indefinidamente desde stdin hasta EOF. Un EOF indica que el padre se ha ido.
  • Esta es una forma infalible y portátil de detectar cuando el padre se ha ido. Incluso si el padre falla, el sistema operativo cerrará la tubería.

Esto era para un proceso de tipo trabajador cuya existencia solo tenía sentido cuando el padre estaba vivo.

  • @SebastianJylanki No recuerdo si lo intenté, pero probablemente funcione porque las primitivas (transmisiones POSIX) son bastante estándar en todos los sistemas operativos.

    – joonas.fi

    18/04/2018 a las 12:50

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad