Linux bloqueando señales a Python init

Este es un seguimiento de mi otro post Instalación del controlador de señales con Python . En resumen, Linux bloquea todas las señales a PID 1 (incluido SIGKILL) a menos que Init haya instalado un controlador de señales para una señal en particular; como para evitar el pánico del kernel si alguien enviara una señal de terminación a PID1. El problema que he tenido es que parece que el módulo de signal en Python no instala los manejadores de señales de una manera que el sistema reconoce. Mi script de inicio de Python aparentemente estaba ignorando completamente todas las señales, ya que creo que estaban siendo bloqueadas.

Parece que he encontrado una solución; utilizando ctypes para instalar los manejadores de señales con la función signal() en libc (en este caso uClibc). A continuación se muestra una prueba basada en python. Abre un shell en TTY2 desde el cual puedo enviar señales a PID1 para realizar pruebas. Parece funcionar en el KVM que estoy usando para las pruebas (estoy dispuesto a compartir la máquina virtual con cualquier persona interesada)

¿Es esta la mejor manera de solucionar este problema? ¿Hay una forma “mejor” de instalar los manejadores de señales sin el módulo de señales? (No estoy preocupado en absoluto por portátil)

¿Es esto 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) 

Hice un poco de depuración bajo KVM y descubrí que el kernel está entregando señales a pid 1 cuando los controladores de señales están instalados por el módulo de señal estándar. Sin embargo, cuando se recibe la señal, “algo” hace que se genere un clon del proceso, en lugar de imprimir la salida esperada.

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

salida de strace

Lo que resulta en un nuevo proceso en ejecución (pid 23) que es un clon de init.sig-mod:

clon de init como pid 23

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

Información antigua : no estoy seguro de que esto resuelva tu problema, pero podría acercarte más. Comparé estas diferentes maneras en que se instalan los manejadores de señales:

  • Instalación de un controlador a través del módulo de señal de Python.
  • Los manejadores de señales de Upstart.
  • Usando ctypes para llamar a la signal() directamente a syscall.
  • Algunas pruebas rápidas en C.

Tanto la llamada al sistema de la signal() invocada por ctypes como las llamadas sigaction() Upstart SA_RESTART indicador SA_RESTART cuando se registra el controlador. La configuración de este indicador indica que cuando se recibe una señal mientras el proceso se está ejecutando o bloqueando dentro de ciertas llamadas (lectura, escritura, espera, nanosleep, etc.), una vez que el manejador de señales se completa, la llamada debe reiniciarse automáticamente. La aplicación no será consciente de esto.

Cuando el módulo de señal de Python registra un controlador, pone a cero el indicador siginterrupt(signum, 1) llamando a siginterrupt(signum, 1) . Esto le dice al sistema “cuando una llamada del sistema es interrumpida por una señal, después de que el manejador de señales complete, establezca errno en EINTR y regrese del syscall”. Esto lo deja el desarrollador para manejar esto y decidir si reiniciar la llamada del sistema.

Puede establecer el indicador SA_RESTART registrando su señal de esta manera:

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

El problema fue un problema de compatibilidad con Python comstackdo contra uClibc 0.9.31 con subprocesos de linux antiguos. Comstackr contra 0.9.32-rc3 y usar NPTL ha solucionado el problema.