2009-12-10 19 views
17

Tengo un programa que genera y se comunica con la CPU procesos pesados ​​e inestables, no creados por mí. Si mi aplicación se bloquea o se destruye por SIGKILL, también quiero que se eliminen los subprocesos, por lo que el usuario no tiene que rastrearlos y matarlos manualmente.Asesinando a los niños con el padre

Sé que este tema ya se trató anteriormente, pero probé todos los métodos descritos, y ninguno de ellos parece estar a la altura para sobrevivir a la prueba.

Sé que debe ser posible, ya que los terminales lo hacen todo el tiempo. Si ejecuto algo en una terminal y mato a la terminal, las cosas siempre mueren.

He intentado atexit, doble tenedor y ptys. atexit no funciona para sigkill; la doble horquilla no funciona en absoluto; y ptys No he encontrado forma de trabajar con Python.

Hoy en día, descubrí prctl(PR_SET_PDEATHSIG, SIGKILL), que debería ser una forma de que los procesos secundarios ordenen una muerte en sí mismos, cuando su padre muere. traté de usarlo con popen, pero las costuras no tener ningún efecto en absoluto:

import ctypes, subprocess 
libc = ctypes.CDLL('/lib/libc.so.6') 
PR_SET_PDEATHSIG = 1; TERM = 15 
implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM) 
subprocess.Popen(['gnuchess'], preexec_fn=implant_bomb) 

En lo anterior, se crea el niño y las salidas de los padres. Ahora esperaría que gnuchess recibiera un SIGKILL y muera, pero no es así. Todavía puedo encontrarlo en mi administrador de procesos usando 100% de CPU.

¿Alguien puede decirme si hay algún problema con mi uso de prctl?, o ¿sabe cómo las terminales logran matar a sus hijos?

+23

Me parece muy preocupante título :( – Dal

+5

Offtopic: Nice xD título –

+5

Debería estar en moms4moms.com StackExchange sitio ... – Antony

Respuesta

6

prctl 's PR_SET_DEATHSIG sólo puede modificarse para este mismo proceso que está llamando prctl - no para ningún otro proceso, incluidos los hijos de este proceso específico. La forma en que la página man que estoy señalando expresa esto es "Este valor se borra en un fork()" - fork, por supuesto, es la forma en que se generan otros procesos (en Linux y en cualquier otro sistema operativo Unix-y).

Si no tiene control sobre el código que desea ejecutar en subprocesos (como sería el caso, esencialmente, para su ejemplo gnuchess), le sugiero que genere primero un pequeño proceso de "monitor" con la función de mantener seguimiento de todos sus hermanos (el proceso de los padres puede informar al monitor acerca de los pids de los hermanos a medida que los engendra) y enviándoles señales de muerte cuando el padre común muere (el monitor debe sondear para eso, despertando cada N segundos para algunas N de su elección para comprobar si el padre todavía está vivo, use select para esperar más información del padre con un tiempo de espera de N segundos, dentro de un bucle).

No es trivial, pero las tareas del sistema a menudo no lo son. Las terminales lo hacen de manera diferente (a través del concepto de "terminal de control" para un grupo de procesos) pero, por supuesto, es trivial que un niño lo bloquee (doble horquillas, nohup, etc.).

+5

'preexec_fn' se llama después de' fork() ', y la página man no dice que esta bandera se borre en' exec() '. –

+0

Cuando Denis escribe, tengo la impresión de que el parámetro preexec_fn colocará mi llamada de prctl entre el tenedor y las llamadas del ejecutivo. La idea de un monitor es bastante buena. A menos que se bloquee por supuesto, así que tiene que ser bastante simple. ¿Puedo saber que su parrent está muerto cuando recibe SIGHUP? Y luego deje que sigterm sus hermanos y hermanas. ¿Puede decirme más sobre las terminales de control de desove? –

+0

De [\ [Python \]: Constructor de Popen] (https://docs.python.org/3/library/subprocess.html#popen-constructor) (probablemente cambiado en el camino): _ "Si se establece preexec_fn a un objeto invocable, este objeto se llamará ** en el proceso hijo ** justo antes de que se ejecute el hijo. (POSIX solamente). "_ – CristiFati

1

Pensé que la doble horquilla debía soltarse de una terminal de control. No estoy seguro de cómo estás tratando de usarlo.

Es un truco, pero siempre puedes llamar a 'ps' y buscar el nombre del proceso que intentas matar.

+0

Pero, ¿cómo puedo llamar a ps cuando mi proceso de control está muerto? Ese es todo el punto. El usuario de mi aplicación no sabrá que los niños no están muertos, y sentirán que su computadora es mucho más lenta hasta que se reinicie –

+0

¿Puedes envolver el programa que se está creando con tu propio ejecutable y luego escribir el PID en un archivo cuando se inició? – BillMan

+0

Probablemente podría encontrar una manera de limpiar cosas en el reinicio, pero no sé si voy a reiniciar. Por lo tanto, necesito una forma de limpiarme en el momento en que muera. –

1

que he visto maneras muy desagradables de "limpieza" usando cosas como ps xuawww | grep myApp | awk '{ print $1}' | xargs -n1 kill -9

El proceso de cliente, si popened, puede coger SIG_PIPE y morir. Hay muchas maneras de hacerlo, pero realmente depende de muchos factores. Si arroja un código de ping (ping al padre) en el niño, puede asegurarse de que se emita un SIG_PIPE al morir. Si lo atrapa, lo que debería, terminará. Necesitaría una comunicación bidireccional para que esto funcione correctamente ... o para bloquear siempre al cliente como creador de la comunicación. Si no desea modificar el elemento secundario, ignore esto.

Suponiendo que no espere que el intérprete de Python actual segfault, puede agregar cada PID a una secuencia, y luego matar al salir. Esto debería ser seguro para las excepciones e incluso las no detectadas. Python tiene instalaciones para realizar el código de salida ... para la limpieza.

He aquí algo más desagradable: anexe cada PID secundario a un archivo, incluido el proceso principal (archivo separado). Use bloqueo de archivos. Crea un daemon watchdog que mire el estado flock() de tu pid maestro. Si no está bloqueado, elimine todos los PID de la lista PID de su hijo. Ejecuta el mismo código al inicio.

más desagradable: Escribir los PID de archivos, como el anterior, a continuación, invocar su aplicación en un sub-shell: (./myMaster; ./killMyChildren)

+0

Si lee mi codenap, verá que ejecuto gnuchess en el nuevo proceso, por lo que ahora hay forma de ejecutarlo como un hilo. ¿Popen no usa tenedor? –

1

Me pregunto si la bandera PR_SET_PDEATHSIG se está desactivada, aunque establece que después de fork (y antes de exec), por lo que parece a partir de los documentos como si no debería de ser anulado.

Para probar esa teoría, podría intentar lo siguiente: usar el mismo código para ejecutar un subproceso que está escrito en C y básicamente simplemente llama a prctl(PR_GET_PDEATHSIG, &result) e imprime el resultado.

Otra cosa que puede intentar: agregar ceros explícitos para arg3, arg4 y arg5 cuando llama al prctl. Es decir .:

>>> implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM, 0, 0, 0) 
11

Sé que han pasado años, pero encontré una solución simple (ligeramente hacky) a este problema. Desde su proceso principal, envolver todas sus llamadas en un programa C muy simple que llama a prctl() y luego a exec() resuelve este problema en Linux. Lo llamo "yeshup":

#include <linux/prctl.h> 
#include <signal.h> 
#include <unistd.h> 

int main(int argc, char **argv) { 
    if(argc < 2) 
      return 1; 
    prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0); 
    return execvp(argv[1], &argv[1]); 
} 

Durante el desove sus procesos secundarios desde Python (o cualquier otro idioma), puede ejecutar "yeshup gnuchess [argments]." Descubrirá que, cuando se mata el proceso principal, todos los procesos de su hijo (deberían) se deben administrar SIGHUP muy bien.

Esto funciona porque Linux respetará la llamada a prctl (no borrarla) incluso después de llamar a execvp (que efectivamente "transforma" el proceso yeshup en un proceso gnuchess, o cualquier comando que especifique allí), a diferencia de fork() .

+0

¿Puede alguna variante de esta mosca para un caso de prueba basado en bash? He estado jugando con él sin suerte. –

+1

Pero, ¿cómo es esto diferente de llamar 'fork(); prctl(); exec() '? –

+0

Thomas: Eso podría funcionar, pero recuerde, OP intenta llamar a un subproceso de Python. – coolbho3k

4

En realidad me encontré con que el enfoque original funcionaba bien para mí - aquí está el código de ejemplo exacta Probé con la que trabajaron:

echoer.py

#!/bin/env python 

import time 
import sys 
i = 0 
try: 
    while True: 
     i += 1 
     print i 
     time.sleep(1) 
except KeyboardInterrupt: 
    print "\nechoer caught KeyboardInterrupt" 
    exit(0) 

parentProc.py

#!/bin/env python 

import ctypes 
import subprocess 
import time 

libc = ctypes.CDLL('/lib64/libc.so.6') 
PR_SET_PDEATHSIG = 1 
SIGINT = 2 
SIGTERM = 15 

def set_death_signal(signal): 
    libc.prctl(PR_SET_PDEATHSIG, signal) 

def set_death_signal_int(): 
    set_death_signal(SIGINT) 

def set_death_signal_term(): 
    set_death_signal(SIGTERM) 

#subprocess.Popen(['./echoer.py'], preexec_fn=set_death_signal_term) 
subprocess.Popen(['./echoer.py'], preexec_fn=set_death_signal_int) 
time.sleep(1.5) 
print "parentProc exiting..." 
+0

¿Funciona mejor con 'sigint' que' sigterm'? –

+0

no funciona "mejor", simplemente diferente: para ver el diff por ti mismo (en un sistema Linux), crea ambos archivos en el mismo directorio, hazlos ejecutables y luego ejecuta "parentProc.py". El proceso secundario debe recibir una señal KeyboardInterrupt, que sabrá porque imprimirá "echoer caught KeyboardInterrupt". Si cambia parentProc.py para usar set_death_signal_term, el ecocard seguirá siendo eliminado, pero de una manera más abrupta. –

1

Hay una cierta restricción de seguridad a tener en cuenta porque si llamamos setuid después execv que el niño no puede recibir la señal. La lista completa de estas restricciones es here

¡buena suerte!
/Mohamed

+0

Interesante, por lo que 'la señal se entrega solo si el proceso principal tiene suficientes privilegios para enviar señales a procesos secundarios. Por lo general, cualquier proceso secundario que se ejecute con privilegios superiores a los de sus padres no recibirá ninguna señal. "No he notado tal cosa. ¿Cómo puedo detectar si de alguna manera aumenta su privilegio? –

Cuestiones relacionadas