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:
- (preguntado antes) ¿Cómo puedo hacer que un proceso secundario salga cuando los padres lo hacen?
- (preguntado más adelante) ¿Se eliminan automáticamente los procesos hijos creados con fork () cuando se mata al padre?
Alguna pregunta similar sobre stackoverflow para Windows :
prctl()
forma libre en condiciones de carrera. Por cierto, la respuesta vinculada por Maxim es incorrecta.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 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.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.
fuente
gettpid()
no se convierte en 1 sino que obtiene elpid
planificador de zona (procesozsched
).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.
fuente
Si no puede modificar el proceso secundario, puede intentar algo como lo siguiente:
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.
fuente
dup2
stdin y hacerse cargo de él utilizando laread -u
bandera para leer desde un descriptor de archivo específico. También agregué unsetpgid(0, 0)
en el niño para evitar que salga al presionar ^ C en el terminal.dup2()
llamada es incorrecto. Si desea usarpipes[0]
como stdin, debe escribir endup2(pipes[0], 0)
lugar dedup2(0, pipes[0])
. Esdup2(oldfd, newfd)
donde la llamada cierra un newfd previamente abierto.En Linux, puede instalar una señal de muerte de los padres en el niño, por ejemplo:
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 entreprctl()
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:
Sin
init
embargo, confiar en que el proceso del sistema sea y tener PID 1 no es portátil. POSIX.1-2008 especifica :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).fuente
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 parainit(8)
niños, pero usted está protegido de esto, comoinit(8)
nuncaexit(2)
(kernel entra en pánico en ese caso)Por completo. En macOS puedes usar kqueue:
fuente
NSTask
o posix spawn. Vea lastartTask
función en mi código aquí: github.com/neoneye/newton-commander-browse/blob/master/Classes/…¿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.
fuente
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.
Hay dos pequeñas advertencias con este método:
Por otro lado, el código real que estoy usando está en Python. Aquí está para completar:
fuente
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.fuente
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:
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.
fuente
getpid()
se realiza en el padre antes de llamarfork()
. 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.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
fuente
Esta solución funcionó para mí:
Esto fue para un proceso de tipo trabajador cuya existencia solo tenía sentido cuando el padre estaba vivo.
fuente
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.
fuente
Algunos carteles ya han mencionado tuberías y
kqueue
. De hecho, también puede crear un par de sockets de dominio Unix conectados porsocketpair()
llamada. El tipo de socket debe serSOCK_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 puedepoll()
abrir el fd restante en su propio extremo para elPOLLIN
evento. Mientras cada lado no explícitamenteclose()
su fd durante la vida útil normal, puede estar bastante seguro de que unaPOLLHUP
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).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
SIGPIPE
controlador, este método no requiere probar lawrite()
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.fuente
Bajo POSIX , las
exit()
,_exit()
y las_Exit()
funciones se definen a: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()
esetsid()
ysetpgrp()
- para obtener la imagen completa. (¡Yo también lo haría!)fuente
Si envía una señal al pid 0, usando por ejemplo
esa señal se envía a todo el grupo de proceso, matando efectivamente al niño.
Puedes probarlo fácilmente con algo como:
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ó.fuente
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -"
felizmente imprime 4 en lugar de TerminadoEn 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 deexecv
, y2) 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 elps
comando (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
waitpid
en 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.fuente
PID equals 1
espera 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 ()).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) .
fuente
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:
fuente
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.
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.
fuente
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:
De esa manera:
Deficiencias:
fuente
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().addShutdownHook
pero 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
InterruptedException
que parece funcionar correctamente en ambas versiones de Windows.fuente
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 suexit(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 lagetppid(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:
init
proceso 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 elexit
có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 unaexit(2)
llamada.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 una1
respuesta a lagetppid(2)
llamada del sistema es suficiente para asumir que el proceso es huérfano. Otra forma de verificar es hacer ungetppid(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ésfork(2)
y antes de la primeragetppid(2)
llamada al sistema. Laparent id only changes once, when its parent does an
salida del proceso (2)call, so this should be enough to check if the
getppid (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 of
init (8) `, pero puede asumir con seguridad que estos procesos tampoco tienen padre (excepto cuando sustituye en un sistema el proceso init)fuente
Le pasé el padre pid usando el entorno al hijo, luego verifiqué periódicamente si / proc / $ ppid existe desde el hijo.
fuente