¿Cómo hacer que el proceso infantil muera después de que los padres salgan?

209

Supongamos que tengo un proceso que genera exactamente un proceso hijo. Ahora, cuando el proceso padre se cierra por cualquier razón (normal o anormalmente, por kill, ^ C, afirmar falla o cualquier otra cosa) quiero que el proceso hijo muera. ¿Cómo hacer eso correctamente?


Alguna pregunta similar sobre stackoverflow:


Alguna pregunta similar sobre stackoverflow para Windows :

Paweł Hajdan
fuente

Respuestas:

189

El niño puede pedirle al kernel que entregue SIGHUP(u otra señal) cuando el padre muere especificando la opción PR_SET_PDEATHSIGen prctl()syscall de esta manera:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Ver man 2 prctlpara más detalles.

Editar: esto es solo para Linux

qrdl
fuente
55
Esta es una solución pobre porque el padre ya podría haber muerto. Condición de carrera. Solución correcta: stackoverflow.com/a/17589555/412080
Maxim Egorushkin
16
Llamar una respuesta deficiente no es muy agradable, incluso si no aborda una condición de carrera. Vea mi respuesta sobre cómo usar de prctl()forma libre en condiciones de carrera. Por cierto, la respuesta vinculada por Maxim es incorrecta.
maxschlepzig
44
Esto es solo una respuesta incorrecta. Enviará la señal al proceso hijo en el momento en que muere el hilo que se llama fork, no cuando muere el proceso padre.
Lothar
2
@Lothar Sería bueno ver algún tipo de prueba. man prctldice: 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 muera su padre. Este valor se borra para el hijo de una bifurcación (2) y (desde Linux 2.4.36 / 2.6.23) al ejecutar un binario set-user-ID o set-group-ID.
qrdl
1
@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 una API para configurar opciones en el lado principal. Qué pena.
rox
68

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 un padre muere. Así que decidí la siguiente mejor opción: hacer la encuesta infantil.

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

Esto no es genial, pero funciona, y es más fácil que las soluciones de sondeo TCP socket / lockfile sugeridas en otra parte de esta página.

Schof
fuente
66
Excelente solucion. Invocando continuamente getppid () hasta que devuelve 1 y luego sale. Esto es bueno y ahora lo uso también. Sin embargo, una solución que no sea pollig sería buena. Gracias Schof
neoneye
10
Solo para información, en Solaris, si está en una zona, gettpid()no se convierte en 1 sino que obtiene el pidplanificador de zona (proceso zsched).
Patrick Schlüter
44
Si alguien se pregunta, en los sistemas Android, pid parece ser 0 (proceso pid del sistema) en lugar de 1, cuando el padre muere.
Rui Marques
2
Para tener una forma más robusta e independiente de la plataforma de hacerlo, antes de bifurcar () - simplemente getpid () y si getppid () de child es diferente, salga.
Sebastien
2
Esto no funciona si no controlas el proceso secundario. Por ejemplo, estoy trabajando en un comando que envuelve find (1), y quiero asegurarme de que el hallazgo se elimine si el contenedor muere por alguna razón.
Lucretiel
34

He logrado esto en el pasado ejecutando el código "original" en el "hijo" y el código "generado" en el "padre" (es decir: invierte el sentido habitual de la prueba después fork()). Luego atrape SIGCHLD en el código "generado" ...

Puede que no sea posible en tu caso, pero lindo cuando funciona.

dmckee --- gatito ex moderador
fuente
Muy buena solución, gracias! El actualmente aceptado es más genérico, pero el tuyo es más portátil.
Paweł Hajdan
1
El gran problema de hacer el trabajo en el padre es que está cambiando el proceso padre. En el caso de un servidor que tiene que ejecutarse "para siempre", esa no es una opción.
Alexis Wilke
29

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(0, pipes[0]); /* Use reader end as stdin */
    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 hijo desde un proceso de shell con control de trabajo habilitado. El proceso hijo se genera en segundo plano. El caparazón 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 de la tubería. El shell secundario obtendrá un EOF de la lectura y procederá a eliminar el proceso secundario en segundo plano.

Phil Rutschman
fuente
2
Agradable, pero cinco llamadas al sistema, y ​​un sh generado en diez líneas de códigos me deja un poco escéptico sobre este desempeño de código.
Oleiade
+1. Puede evitar el dup2stdin y hacerse cargo de él utilizando la read -ubandera 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 el terminal.
Greg Hewgill
El orden de argumento de la dup2()llamada es incorrecto. Si desea usar pipes[0]como stdin, debe escribir en dup2(pipes[0], 0)lugar de dup2(0, pipes[0]). Es dup2(oldfd, newfd)donde la llamada cierra un newfd previamente abierto.
maxschlepzig
@Oleiade, estoy de acuerdo, especialmente porque el sh generado genera una bifurcación más para ejecutar el proceso hijo real ...
maxschlepzig
16

En Linux, puede instalar una señal de muerte de los padres en el niño, 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 principal 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 hijo.

También tenga en cuenta que la señal de muerte del padre del niño se borra en los niños 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 se encarga de adoptar a todos los 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 ...

Sin initembargo, confiar en que el proceso del sistema sea y tener PID 1 no es portátil. POSIX.1-2008 especifica :

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

Tradicionalmente, el proceso del sistema que adopta a todos los huérfanos es PID 1, es decir, init, que es el antepasado 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)para establecerse como un proceso del sistema que hereda todos los huérfanos de cualquiera de sus descendientes (véase un ejemplo en Fedora 25).

maxschlepzig
fuente
No entiendo "Esa prueba se puede simplificar si estamos seguros de que el abuelo es siempre el proceso de inicio". Cuando un proceso padre muere, un proceso se convierte en un hijo del proceso init (pid 1), no un hijo del abuelo, ¿verdad? Entonces la prueba siempre parece ser correcta.
Johannes Schaub - litb
Interesante, gracias. aunque no veo qué tiene que ver con el abuelo.
Johannes Schaub - litb
1
@ JohannesSchaub-litb, no siempre se puede suponer que el padre de un proceso será un init(8)proceso ... lo único que se puede suponer es que cuando un proceso primario muere, es que su identificación principal cambiará. Esto realmente sucede una vez en la vida de un proceso ... y es cuando el padre del proceso muere. Solo hay una excepción principal para esto, y es para init(8)niños, pero usted está protegido de esto, como init(8)nunca exit(2)(kernel entra en pánico en ese caso)
Luis Colorado
1
Desafortunadamente, si un hijo se bifurca de un hilo, y luego el hilo sale, el proceso hijo obtendrá el SIGTERM.
rox
14

Por completo. En macOS puedes usar kqueue:

void noteProcDeath(
    CFFileDescriptorRef fdref, 
    CFOptionFlags callBackTypes, 
    void* info) 
{
    // LOG_DEBUG(@"noteProcDeath... ");

    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    unsigned int dead_pid = (unsigned int)kev.ident;

    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example

    int our_pid = getpid();
    // when our parent dies we die as well.. 
    LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
    exit(EXIT_SUCCESS);
}


void suicide_if_we_become_a_zombie(int parent_pid) {
    // int parent_pid = getppid();
    // int our_pid = getpid();
    // LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);

    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}
neoneye
fuente
Puede hacer esto con una API un poco mejor, utilizando fuentes de envío con DISPATCH_SOURCE_PROC y PROC_EXIT.
Russbishop
Por alguna razón, esto está causando que mi Mac entre en pánico. Ejecutar un proceso con este código tiene una probabilidad del 50% de congelarse, lo que hace que los fanáticos giren a una velocidad que nunca antes los había escuchado (súper rápido), y luego la Mac simplemente se apaga. Tenga mucho cuidado con este código .
Qix - MONICA FUE MALTRATADA
Parece que en mi macOS, el proceso hijo se cierra automáticamente después de que sale el padre. No se porque.
Yi Lin Liu
@YiLinLiu iirc Utilicé NSTasko posix spawn. Vea la startTaskfunción en mi código aquí: github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye
11

¿El proceso hijo tiene una tubería hacia / desde el proceso padre? Si es así, recibirías un SIGPIPE si escribes, o recibirías EOF al leer, estas condiciones podrían detectarse.

MarkR
fuente
1
Descubrí que esto no sucedió de manera confiable, al menos en OS X.
Schof
Vine aquí para agregar esta respuesta. Esta es definitivamente la solución que usaría para este caso.
Dolda2000
punto de precaución: systemd deshabilita SIGPIPE por defecto en los servicios que administra, pero aún puede verificar el cierre de la tubería. Ver freedesktop.org/software/systemd/man/systemd.exec.html en IgnoreSIGPIPE
jdizzle
11

Inspirado por otra respuesta aquí, se me ocurrió la siguiente solución totalmente POSIX. La idea general es crear un proceso intermedio entre el padre y el niño, que tiene un propósito: observar cuándo muere el padre y matar explícitamente al niño.

Este tipo de solución es útil cuando el código en el niño no se puede modificar.

int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
    close(p[1]); // close write end of pipe
    setpgid(0, 0); // prevent ^C in parent from stopping this process
    child = fork();
    if (child == 0) {
        close(p[0]); // close read end of pipe (don't need it here)
        exec(...child process here...);
        exit(1);
    }
    read(p[0], 1); // returns when parent exits for any reason
    kill(child, 9);
    exit(1);
}

Hay dos pequeñas advertencias con este método:

  • Si matas deliberadamente el proceso intermedio, entonces el niño no será asesinado cuando el padre muera.
  • Si el hijo sale antes que el padre, entonces el proceso intermedio intentará matar al hijo original pid, que ahora podría referirse a un proceso diferente. (Esto podría solucionarse con más código en el proceso intermedio).

Por otro lado, el código real que estoy usando está en Python. Aquí está para completar:

def run(*args):
    (r, w) = os.pipe()
    child = os.fork()
    if child == 0:
        os.close(w)
        os.setpgid(0, 0)
        child = os.fork()
        if child == 0:
            os.close(r)
            os.execl(args[0], *args)
            os._exit(1)
        os.read(r, 1)
        os.kill(child, 9)
        os._exit(1)
    os.close(r)
Greg Hewgill
fuente
Tenga en cuenta que hace un tiempo, bajo IRIX, utilicé un esquema padre / hijo donde tenía una tubería entre ambos y la lectura de la tubería generaba un SIGHUP si alguno de los dos fallecía. Esa era la forma en que solía matar a mis hijos de fork (), sin la necesidad de un proceso intermedio.
Alexis Wilke
Creo que tu segunda advertencia es incorrecta. El pid de un hijo es un recurso que pertenece a su padre y no se puede liberar / reutilizar hasta que el padre (el proceso intermedio) lo espere (o termine y deje que init lo espere).
R .. GitHub DEJA DE AYUDAR AL HIELO
7

No creo que sea posible garantizar que solo se usen llamadas POSIX estándar. Al igual que la vida real, una vez que un niño es engendrado, tiene vida propia.

Que es posible que el proceso padre para ponerse al mayor número posible de eventos de terminación, y el intento de matar al proceso hijo en ese momento, pero siempre hay algunos que no puede ser capturado.

Por ejemplo, ningún proceso puede atrapar a SIGKILL. Cuando el kernel maneja esta señal, matará 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 única de hacerlo con Linux prctl(2): vea otras respuestas.

Alnitak
fuente
6

Como han señalado otras personas, confiar en que el padre pide que se convierta en 1 cuando el padre sale no es portátil. En lugar de esperar una ID de proceso principal específica, solo espere a que cambie la ID:

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;

Agregue un micro sueño como desee si no desea sondear a toda velocidad.

Esta opción me parece más simple que usar una tubería o confiar en las señales.

usuario2168915
fuente
Desafortunadamente, esa solución no es sólida. ¿Qué pasa si el proceso padre muere antes de obtener el valor inicial? El niño nunca saldrá.
dgatwood
@dgatwood, ¿qué quieres decir? El primero getpid()se realiza en el padre antes de llamar fork(). Si el padre muere antes, el niño no existe. Lo que puede suceder es que el niño viva fuera del padre por un tiempo.
Alexis Wilke
En este ejemplo un tanto artificial, funciona, pero en el código del mundo real, fork es seguido casi siempre por exec, y el nuevo proceso debe comenzar de nuevo pidiendo su PPID. En el tiempo entre esos dos controles, si el padre se va, el niño no tendría idea. Además, es poco probable que tenga control sobre el código primario y secundario (o de lo contrario podría pasar el PPID como argumento). Entonces, como solución general, ese enfoque no funciona muy bien. Y de manera realista, si un sistema operativo tipo UNIX salió sin init siendo 1, se romperían tantas cosas que no puedo imaginar que alguien lo haga de todos modos.
dgatwood
pass padre pid es un argumento de línea de comando cuando se ejecuta exec para child.
Nish
2
El sondeo a toda velocidad es una locura.
maxschlepzig
4

Instale un controlador de trampa para atrapar a SIGINT, que mata el proceso de su hijo si todavía está vivo, aunque otros carteles son correctos para que no atrape a SIGKILL.

Abra un archivo .lock con acceso exclusivo y haga que el elemento secundario realice un sondeo al intentar abrirlo; si la apertura se realiza correctamente, el proceso secundario debería salir

Ana Betts
fuente
O bien, el niño podría abrir el archivo de bloqueo en un hilo separado, en modo de bloqueo, en cuyo caso esta podría ser una solución bastante agradable y limpia. Sin embargo, probablemente tenga algunas limitaciones de portabilidad.
Jean
4

Esta solución funcionó para mí:

  • Pase la tubería estándar al niño: no tiene que escribir ningún dato en la secuencia.
  • El niño lee indefinidamente desde stdin hasta EOF. Un EOF indica que el padre se ha ido.
  • Esta es una manera 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 fue para un proceso de tipo trabajador cuya existencia solo tenía sentido cuando el padre estaba vivo.

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

Creo que una manera rápida y sucia es crear una tubería entre el niño y el padre. Cuando los padres salgan, los niños recibirán un SIGPIPE.

Yefei
fuente
SIGPIPE no se envía al cierre de la tubería, solo se envía cuando el niño intenta escribirle.
Alcaro
3

Algunos carteles ya han mencionado tuberías y kqueue. De hecho, también puede crear un par de sockets de dominio Unix conectados por socketpair()llamada. El tipo de socket 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á los fds. En el padre cierra fd2 y en el niño cierra fd1. Ahora cada proceso puede poll()abrir el fd restante en su propio extremo para el POLLINevento. Mientras cada lado no explícitamente close()su fd durante la vida útil normal, puede estar bastante seguro de que una POLLHUPbandera 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 un terminal como ./a.out &. Tiene aproximadamente 100 segundos para experimentar con matar el PID padre 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 usa el SIGPIPEcontrolador, este método no requiere probar la write()llamada.

Este método también es simétrico , es decir, los procesos pueden usar el mismo canal para monitorear la existencia del otro.

Esta solución solo llama a las funciones POSIX. Intenté 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 páginas man de Linux, unix(4)FreeBSD, poll(2), socketpair(2), socket(7)en Linux.
Cong Ma
fuente
Muy bien, realmente me pregunto si esto tiene algún problema de confiabilidad. ¿Has probado esto en producción? Con diferentes aplicaciones?
Aktau
@ Aktau, he estado usando el equivalente a Python de este truco en un programa de Linux. Lo necesitaba porque la lógica de trabajo del niño es "hacer la mejor limpieza después de que los padres salgan y luego salir". Sin embargo, realmente no estoy seguro acerca de otras plataformas. El fragmento C funciona en Linux y FreeBSD, pero eso es todo lo que sé ... Además, hay casos en los que debe tener cuidado, como que el padre se bifurque nuevamente o que el padre abandone el fd antes de salir realmente (creando así una ventana de tiempo para condición de carrera).
Cong Ma
@ Aktau: esto será completamente confiable.
Omnifarious
1

Bajo POSIX , las exit(), _exit()y las _Exit()funciones se definen a:

  • Si el proceso es un proceso de control, la señal SIGHUP se enviará a cada proceso en el grupo de procesos en primer plano del terminal de control que pertenece al proceso de llamada.

Por lo tanto, si organiza que el proceso padre sea un proceso de control para su grupo de procesos, el niño debería recibir una señal SIGHUP cuando salga el padre. No estoy absolutamente seguro de que eso suceda cuando el padre falla, pero creo que sí. Ciertamente, para los casos sin bloqueo, debería funcionar bien.

Tenga en cuenta que puede que tenga que leer un buen montón de letra pequeña - incluyendo la sección Definiciones Base (Definiciones), así como la información de los servicios del sistema de exit()e setsid()y setpgrp()- para obtener la imagen completa. (¡Yo también lo haría!)

Jonathan Leffler
fuente
3
Hmm La documentación es vaga y contradictoria al respecto, pero parece que el proceso principal debe ser el proceso principal para la sesión, no solo el grupo de procesos. El proceso principal para la sesión siempre fue iniciar sesión, y hacer que mi proceso tomara el control como proceso principal para una nueva sesión estaba más allá de mis habilidades en este momento.
Schof
2
SIGHUP efectivamente solo se envía a procesos secundarios si el proceso de salida es un shell de inicio de sesión. opengroup.org/onlinepubs/009695399/functions/exit.html "La terminación de un proceso no termina directamente a sus hijos. El envío de una señal SIGHUP como se describe a continuación termina indirectamente a los hijos / en algunas circunstancias /".
Rob K
1
@Rob: correcto, eso es lo que dice la cita que di también: que solo en algunas circunstancias el proceso secundario obtiene un SIGHUP. Y es estrictamente una simplificación excesiva decir que solo es un shell de inicio de sesión que envía SIGHUP, aunque ese es el caso más común. Si un proceso con varios hijos se configura como el proceso de control para sí mismo y sus hijos, entonces el SIGHUP se enviará (convenientemente) a sus hijos cuando el maestro muera. OTOH, los procesos rara vez van a tener tantos problemas, por lo que yo soy más quisquilloso que plantear una objeción realmente significativa.
Jonathan Leffler
2
Lo engañé durante un par de horas y no pude hacerlo funcionar. Hubiera manejado muy bien un caso en el que tengo un demonio con algunos niños que todos necesitan morir cuando el padre sale.
Rob K
1

Si envía una señal al pid 0, usando por ejemplo

kill(0, 2); /* SIGINT */

esa señal se envía a todo el grupo de proceso, matando efectivamente al niño.

Puedes probarlo fácilmente con algo como:

(cat && kill 0) | python

Si luego presiona ^ D, verá el texto "Terminated"como una indicación de que el intérprete de Python realmente fue asesinado, en lugar de simplemente salir debido a que stdin se cerró.

Thorbiörn Fritzon
fuente
1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" felizmente imprime 4 en lugar de Terminado
Kamil Szot
1

En caso de que sea relevante para cualquier otra persona, cuando engendro instancias JVM en procesos secundarios bifurcados de C ++, la única forma en que podría hacer que las instancias JVM terminen correctamente después de que se complete el proceso principal fue hacer lo siguiente. Esperemos que alguien pueda proporcionar comentarios en los comentarios si esta no fuera la mejor manera de hacerlo.

1) Llame prctl(PR_SET_PDEATHSIG, SIGHUP)al proceso secundario bifurcado como se sugiere antes de iniciar la aplicación Java a través de execv, y

2) Agregue un gancho de apagado a la aplicación Java que sondea hasta que su PID primario sea igual a 1, luego haga un hard Runtime.getRuntime().halt(0). El sondeo se realiza iniciando un shell separado que ejecuta el pscomando (Ver: ¿Cómo encuentro mi PID en Java o JRuby en Linux? ).

EDITAR 130118:

Parece que no fue una solución robusta. Todavía estoy luchando un poco por comprender los matices de lo que está sucediendo, pero a veces todavía recibía procesos JVM huérfanos cuando ejecutaba estas aplicaciones en sesiones de pantalla / SSH.

En lugar de sondear el PPID en la aplicación Java, simplemente hice que el gancho de apagado realizara la limpieza seguido de un alto como se indicó anteriormente. Luego me aseguré de invocar waitpiden la aplicación principal de C ++ en el proceso secundario generado cuando llegó el momento de terminar todo. Esta parece ser una solución más sólida, ya que el proceso secundario garantiza que finalice, mientras que el principal utiliza las referencias existentes para asegurarse de que sus hijos finalicen. Compare esto con la solución anterior que hizo que el proceso padre terminara cuando quisiera, y que los niños trataran de averiguar si habían quedado huérfanos antes de terminar.

jasterm007
fuente
1
La PID equals 1espera no es válida. El nuevo padre podría ser algún otro PID. Debe verificar si cambia del padre original (getpid () antes de la bifurcación ()) al nuevo padre (getppid () en el niño que no es igual al getpid () cuando se llama antes de la bifurcación ()).
Alexis Wilke
1

Otra forma de hacer esto que es específica de Linux es hacer que el padre se cree en un nuevo espacio de nombres PID. Entonces será PID 1 en ese espacio de nombres, y cuando salga, todos sus hijos serán asesinados inmediatamente SIGKILL.

Desafortunadamente, para crear un nuevo espacio de nombres PID debe tener CAP_SYS_ADMIN. Pero, este método es muy efectivo y no requiere ningún cambio real para el padre o los hijos más allá del lanzamiento inicial del padre.

Ver clon (2) , pid_namespaces (7) y unshare (2) .

De todo género
fuente
Necesito editar de otra manera. Es posible usar prctl para hacer que un proceso actúe como el proceso de inicio para todos sus hijos y nietos, y bisnietos, etc ...
Omnifarious
0

Si el padre muere, el PPID de los huérfanos cambia a 1: solo necesita verificar su propio PPID. En cierto modo, esta es una encuesta, mencionada anteriormente. Aquí hay una pieza para eso:

check_parent () {
      parent=`ps -f|awk '$2=='$PID'{print $3 }'`
      echo "parent:$parent"
      let parent=$parent+0
      if [[ $parent -eq 1 ]]; then
        echo "parent is dead, exiting"
        exit;
      fi
}


PID=$$
cnt=0
while [[ 1 = 1 ]]; do
  check_parent
  ... something
done
alex K
fuente
0

Encontré 2 soluciones, ambas no perfectas.

1.Matar a todos los niños mediante kill (-pid) cuando reciba la señal SIGTERM.
Obviamente, esta solución no puede manejar "kill -9", pero funciona para la mayoría de los casos y es muy simple porque no necesita recordar todos los procesos secundarios.


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }

De la misma manera, puede instalar el controlador de 'salida' como se indicó anteriormente si llama a process.exit en alguna parte. Nota: Ctrl + C y el bloqueo repentino han sido procesados ​​automáticamente por el sistema operativo para eliminar el grupo de proceso, así que no hay más aquí.

2. Use chjj / pty.js para generar su proceso con el terminal de control conectado.
Cuando matas el proceso actual de todos modos, incluso kill -9, todos los procesos secundarios también se eliminarán automáticamente (¿por OS?). Supongo que debido a que el proceso actual tiene otro lado del terminal, por lo que si el proceso actual muere, el proceso secundario obtendrá SIGPIPE, por lo que muere.


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */
osexp2003
fuente
0

Logré hacer una solución portátil sin sondeo con 3 procesos abusando del control de terminal y las sesiones. Esto es masturbación mental, pero funciona.

El truco es:

  • se inicia el proceso A
  • el proceso A crea una tubería P (y nunca lee de ella)
  • el proceso A se bifurca en el proceso B
  • el proceso B crea una nueva sesión
  • el proceso B asigna un terminal virtual para esa nueva sesión
  • el proceso B instala el controlador SIGCHLD para morir cuando el niño sale
  • el proceso B establece un manejador SIGPIPE
  • el proceso B se bifurca en el proceso C
  • el proceso C hace lo que necesita (por ejemplo, exec () es el binario no modificado o ejecuta cualquier lógica)
  • el proceso B escribe en la tubería P (y bloquea de esa manera)
  • proceso A espera () en el proceso B y sale cuando muere

De esa manera:

  • si el proceso A muere: el proceso B obtiene un SIGPIPE y muere
  • si el proceso B muere: el proceso A (wait) regresa y muere, el proceso C obtiene un SIGHUP (porque cuando el líder de una sesión con una terminal conectada muere, todos los procesos en el grupo de procesos en primer plano obtienen un SIGHUP)
  • si el proceso C muere: el proceso B obtiene un SIGCHLD y muere, entonces el proceso A muere

Deficiencias:

  • el proceso C no puede manejar SIGHUP
  • el proceso C se ejecutará en una sesión diferente
  • el proceso C no puede usar la sesión / proceso de la API del grupo porque romperá la configuración frágil
  • Crear una terminal para cada operación no es la mejor idea
Marek Dopiera
fuente
0

A pesar de que han pasado 7 años, me he encontrado con este problema, ya que estoy ejecutando la aplicación SpringBoot que necesita iniciar webpack-dev-server durante el desarrollo y debe eliminarlo cuando se detiene el proceso de fondo.

Intento usarlo Runtime.getRuntime().addShutdownHookpero funcionó en Windows 10 pero no en Windows 7.

Lo cambié para usar un hilo dedicado que espera a que se cierre el proceso o InterruptedExceptionque parece funcionar correctamente en ambas versiones de Windows.

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}
Ido Ran
fuente
0

Históricamente, desde UNIX v7, el sistema de procesos ha detectado la orfandad de los procesos al verificar la identificación principal de un proceso. Como digo, históricamente, el init(8)proceso del sistema es un proceso especial por una sola razón: no puede morir. No puede morir porque el algoritmo del kernel para tratar de asignar una nueva identificación de proceso padre depende de este hecho. cuando un proceso ejecuta su exit(2)llamada (mediante una llamada al sistema de proceso o mediante una tarea externa que le envía una señal o similar), el núcleo reasigna a todos los hijos de este proceso la identificación del proceso init como su identificación de proceso principal. Esto lleva a la prueba más fácil y la forma más portátil de saber si un proceso se ha quedado huérfano. Simplemente verifique el resultado de la getppid(2)llamada al sistema y si es la identificación del proceso delinit(2) proceso, entonces el proceso quedó huérfano antes de la llamada del sistema.

De este enfoque surgen dos problemas que pueden conducir a problemas:

  • primero, tenemos la posibilidad de cambiar el initproceso a cualquier proceso de usuario, entonces ¿Cómo podemos asegurar que el proceso init siempre sea el padre de todos los procesos huérfanos? Bueno, en el exitcódigo de llamada del sistema hay una comprobación explícita para ver si el proceso que ejecuta la llamada es el proceso de inicio (el proceso con pid igual a 1) y, si ese es el caso, el núcleo entra en pánico (ya no debería poder mantenerse la jerarquía del proceso), por lo que no está permitido que el proceso init realice una exit(2)llamada.
  • segundo, hay una condición de carrera en la prueba básica expuesta anteriormente. Históricamente, se supone que la identificación del proceso Init es 1, pero eso no está garantizado por el enfoque POSIX, que establece (como se expone en otra respuesta) que solo la identificación del proceso de un sistema está reservada para ese propósito. Casi ninguna implementación posix hace esto, y puede asumir en los sistemas derivados de Unix originales que tener una 1respuesta a la getppid(2)llamada del sistema es suficiente para asumir que el proceso es huérfano. Otra forma de verificar es hacer un getppid(2)justo después de la bifurcación y comparar ese valor con el resultado de una nueva llamada. Esto simplemente no funciona en todos los casos, ya que ambas llamadas no son atómicas juntas, y el proceso padre puede morir después fork(2)y antes de la primera getppid(2)llamada al sistema. La parent id only changes once, when its parent does ansalida del proceso (2) call, so this should be enough to check if thegetppid (2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children ofinit (8) `, pero puede asumir con seguridad que estos procesos tampoco tienen padre (excepto cuando sustituye en un sistema el proceso init)
Luis Colorado
fuente
-1

Le pasé el padre pid usando el entorno al hijo, luego verifiqué periódicamente si / proc / $ ppid existe desde el hijo.

Sergei Kirjanov
fuente