2011-04-30 12 views
9

Este es un seguimiento de mi otra publicación Installing signal handler with Python. En resumen, Linux bloquea todas las señales a PID 1 (incluyendo SIGKILL) a menos que Init haya instalado un manejador de señal para una señal particular; para evitar el pánico del núcleo si alguien enviara una señal de terminación a PID1. El problema que he tenido, es que parece que el módulo signal en Python no instala manejadores de señal de una manera que el sistema reconoce. El guión de My Python Init aparentemente ignoraba por completo todas las señales, ya que creo que estaban bloqueadas.señales de bloqueo de Linux a Python init

Parece que he encontrado una solución; utilizando ctypes para instalar los manejadores de señal con la función signal() en libc (en este caso uClibc). A continuación, se muestra un init de prueba basado en Python. Abre un shell en TTY2 desde el cual puedo enviar señales a PID1 para probar. Parece que funciona en el KVM im que se usa para probar (estoy dispuesto a compartir el VM con cualquier persona interesada)

¿Es esta la mejor manera de solucionar este problema? ¿Hay una "mejor" forma de instalar los manejadores de señal sin el módulo de señal? (No estoy para nada preocupado con portably)

¿Esto es un error en Python?

#!/usr/bin/python 

import os 
import sys 
import time 

from ctypes import * 

def SigHUP(): 
    print "Caught SIGHUP" 
    return 0 

def SigCHLD(): 
    print "Caught SIGCHLD" 
    return 0 

SIGFUNC = CFUNCTYPE(c_int) 
SigHUPFunc = SIGFUNC(SigHUP) 
SigCHLDFunc = SIGFUNC(SigCHLD) 

libc = cdll.LoadLibrary('libc.so.0') 
libc.signal(1, SigHUPFunc) # 1 = SIGHUP 
libc.signal(17, SigCHLDFunc) # 17 = SIGCHLD 

print "Mounting Proc: %s" % libc.mount(None, "/proc", "proc", 0, None) 

print "forking for ash" 
cpid = os.fork() 
if cpid == 0: 
    os.closerange(0, 4) 
    sys.stdin = open('/dev/tty2', 'r') 
    sys.stdout = open('/dev/tty2', 'w') 
    sys.stderr = open('/dev/tty2', 'w') 
    os.execv('/bin/ash', ('ash',)) 

print "ash started on tty2" 

print "sleeping" 
while True: 
    time.sleep(0.01) 
+0

Esto realmente pertenece en codereview.SE, pero +1 para la genial idea de implementar 'init' en Python. –

+0

pensé que podría ser donde debería publicar, pero no me fijo en este método de hacerlo y pensé que podría haber muchas ideas en las que no había pensado. – tMC

Respuesta

6

Hice un poco de depuración bajo KVM y encontré que el kernel es señales de entrega en PID 1 cuando los gestores de señales son instalados por el módulo de señal estándar. Sin embargo, cuando se recibe la señal "algo" hace que se reproduzca un clon del proceso, en lugar de imprimir el resultado esperado.

Aquí está la salida de strace cuando envío HUP al que no trabaja init.sig-mod:

strace output

que se traduce en un nuevo proceso en ejecución (PID 23), que es un clon de init .sig-mod:

clone of init as pid 23

yo no tenía tiempo para profundizar en la causa, pero esto reduce aún más las cosas. Probablemente algo que ver con la lógica de entrega de señal de Python (registra un gancho C que invoca su función bytecode cuando se llama). La técnica de ctypes pasa por alto esto. Los archivos fuente de Python relevantes son Python/pythonrun.c y Modules/signalmodule.c, en caso de que quiera verlos de cerca.

Información antigua - No estoy seguro de que esto resuelva su problema, pero podría acercarlo más. I comparó estas diferentes formas en que se instalan los controladores de señal:

  • Instalación de un controlador a través del módulo de señal de Python.
  • Controladores de señal de Upstart.
  • Usando ctypes para llamar directamente a la llamada del sistema signal().
  • algunas pruebas rápidas en C.

Tanto el ctypes invocadas signal() llamada de sistema y de Upstart sigaction() syscalls establecer el indicador SA_RESTART cuando se ha registrado en el controlador.Estableciendo esta bandera indica que cuando se recibe una señal mientras el proceso es ejecutándose o bloqueándose dentro de ciertas llamadas (leer, escribir, esperar, nanosleep, etc.), después de que el manejador de señal complete el syscall debe ser reiniciado automáticamente. La aplicación no tendrá conocimiento de esto.

Cuando el módulo de señal de Python registra un controlador, este pone a cero el indicador SA_RESTART llamando al siginterrupt(signum, 1). Esto le dice al sistema "cuando una llamada al sistema es interrumpida por una señal, después de que el manejador de señal completa establece errno en EINTR y regresa desde syscall". Esto deja al desarrollador el manejar esto y decidir si reiniciar la llamada del sistema.

Puede configurar la bandera SA_RESTART mediante el registro de la señal de esta manera:

import signal 
signal.signal(signal.SIGHUP, handler) 
signal.siginterrupt(signal.SIGHUP, False) 
+0

Estoy ansioso por probar esto, incluso si no resuelve este problema, ¡esta es una gran referencia! Volveré a comentar una vez que haya tenido la oportunidad de probarlo. – tMC

+0

Eso no funcionó, gracias por la idea, muy perspicaz – tMC

+0

OK, pero para aclarar: su código init basado en ctypes está funcionando, ¿verdad? – samplebias

1

La cuestión era un problema de compatibilidad con Python compilado con uClibc 0.9.31 de Linux con hilos de edad. Compilar contra 0.9.32-rc3 y usar NPTL ha solucionado el problema.

Cuestiones relacionadas