Manejo de se帽ales Unix en Python

    Los sistemas UNIX / Linux ofrecen mecanismos especiales para comunicarse entre cada proceso individual. Uno de estos mecanismos son se帽ales, y pertenecen a los diferentes m茅todos de comunicaci贸n entre procesos (Inter Process Communication, abreviado como IPC).

    En resumen, las se帽ales son interrupciones de software que se env铆an al programa (o al proceso) para notificar al programa de eventos importantes o solicitudes al programa para ejecutar una secuencia de c贸digo especial. Un programa que recibe una se帽al detiene o contin煤a la ejecuci贸n de sus instrucciones, termina con o sin un volcado de memoria, o incluso simplemente ignora la se帽al.

    Aunque se define en el Est谩ndar POSIX, la reacci贸n en realidad depende de c贸mo el desarrollador escribi贸 el script e implement贸 el manejo de se帽ales.

    En este art铆culo te explicamos qu茅 son las se帽ales, te mostramos c贸mo enviar una se帽al a otro proceso desde la l铆nea de comandos, adem谩s de procesar la se帽al recibida. Entre otros m贸dulos, el c贸digo del programa se basa principalmente en el m贸dulo de se帽al. Este m贸dulo conecta los encabezados C correspondientes de su sistema operativo con el mundo de Python.

    Introducci贸n a las se帽ales

    En los sistemas basados 鈥嬧媏n UNIX, hay tres categor铆as de se帽ales:

    • Se帽ales del sistema (errores de hardware y del sistema): SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGKILL, SIGSEGV, SIGXCPU, SIGXFSZ, SIGIO
    • Se帽ales del dispositivo: SIGHUP, SIGINT, SIGPIPE, SIGALRM, SIGCHLD, SIGCONT, SIGSTOP, SIGTTIN, SIGTTOU, SIGURG, SIGWINCH, SIGIO
    • Se帽ales definidas por el usuario: SIGQUIT, SIGABRT, SIGUSR1, SIGUSR2, SIGTERM

    Cada se帽al est谩 representada por un valor entero, y la lista de se帽ales que est谩n disponibles es comparativamente larga y no consistente entre las diferentes variantes de UNIX / Linux. En un sistema Debian GNU / Linux, el comando kill -l muestra la lista de se帽ales de la siguiente manera:

    $ kill -l
     1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
     6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
    11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
    16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
    21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
    26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
    31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
    38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
    43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
    48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
    53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
    58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
    63) SIGRTMAX-1  64) SIGRTMAX
    

    Las se帽ales 1 a 15 est谩n aproximadamente estandarizadas y tienen el siguiente significado en la mayor铆a de los sistemas Linux:

    • 1 (SIGHUP): termina una conexi贸n o vuelve a cargar la configuraci贸n para los demonios
    • 2 (SIGINT): interrumpe la sesi贸n desde la estaci贸n de di谩logo
    • 3 (SIGQUIT): finaliza la sesi贸n desde la estaci贸n de di谩logo
    • 4 (SIGILL): se ejecut贸 una instrucci贸n ilegal
    • 5 (SIGTRAP): hacer una sola instrucci贸n (trap)
    • 6 (SIGABRT): terminaci贸n anormal
    • 7 (SIGBUS): error en el bus del sistema
    • 8 (SIGFPE): error de punto flotante
    • 9 (SIGKILL): finalice inmediatamente el proceso
    • 10 (SIGUSR1): se帽al definida por el usuario
    • 11 (SIGSEGV): fallo de segmentaci贸n por acceso ilegal a un segmento de memoria
    • 12 (SIGUSR2): se帽al definida por el usuario
    • 13 (SIGPIPE): escribiendo en una tuber铆a, y nadie lee de ella
    • 14 (SIGALRM): el temporizador termin贸 (alarma)
    • 15 (SIGTERM): finalizar el proceso de forma suave

    Para enviar una se帽al a un proceso en un terminal Linux, invoca el kill comando con el n煤mero de se帽al (o nombre de la se帽al) de la lista anterior y la identificaci贸n del proceso (pid). El siguiente comando de ejemplo env铆a la se帽al 15 (SIGTERM) al proceso que tiene el pid 12345:

    $ kill -15 12345
    

    Una forma equivalente es usar el nombre de la se帽al en lugar de su n煤mero:

    $ kill -SIGTERM 12345
    

    La forma que elija depender谩 de lo que sea m谩s conveniente para usted. Ambas formas tienen el mismo efecto. Como resultado, el proceso recibe la se帽al SIGTERM y termina inmediatamente.

    Usando la biblioteca de se帽ales de Python

    Desde Python 1.4, el signal La biblioteca es un componente regular de cada versi贸n de Python. Para utilizar el signal biblioteca, importe la biblioteca a su programa Python de la siguiente manera, primero:

    import signal
    

    La captura y la reacci贸n adecuada a una se帽al recibida se realiza mediante una funci贸n de devoluci贸n de llamada, un llamado controlador de se帽al. Un manejador de se帽ales bastante simple llamado receiveSignal() se puede escribir de la siguiente manera:

    def receiveSignal(signalNumber, frame):
        print('Received:', signalNumber)
        return
    

    Este manejador de se帽ales no hace m谩s que informar el n煤mero de la se帽al recibida. El siguiente paso es registrar las se帽ales captadas por el gestor de se帽ales. Para los programas Python, todas las se帽ales (excepto 9, SIGKILL) se pueden capturar en su secuencia de comandos:

    if __name__ == '__main__':
        # register the signals to be caught
        signal.signal(signal.SIGHUP, receiveSignal)
        signal.signal(signal.SIGINT, receiveSignal)
        signal.signal(signal.SIGQUIT, receiveSignal)
        signal.signal(signal.SIGILL, receiveSignal)
        signal.signal(signal.SIGTRAP, receiveSignal)
        signal.signal(signal.SIGABRT, receiveSignal)
        signal.signal(signal.SIGBUS, receiveSignal)
        signal.signal(signal.SIGFPE, receiveSignal)
        #signal.signal(signal.SIGKILL, receiveSignal)
        signal.signal(signal.SIGUSR1, receiveSignal)
        signal.signal(signal.SIGSEGV, receiveSignal)
        signal.signal(signal.SIGUSR2, receiveSignal)
        signal.signal(signal.SIGPIPE, receiveSignal)
        signal.signal(signal.SIGALRM, receiveSignal)
        signal.signal(signal.SIGTERM, receiveSignal)
    

    A continuaci贸n, agregamos la informaci贸n del proceso para el proceso actual y detectamos la identificaci贸n del proceso utilizando el m茅todo getpid() desde el os m贸dulo. En un sin fin while bucle esperamos las se帽ales entrantes. Implementamos esto usando dos m贸dulos de Python m谩s: os y hora. Tambi茅n los importamos al comienzo de nuestro script Python:

    import os
    import time
    

    En el while bucle de nuestro programa principal, la declaraci贸n de impresi贸n produce “Esperando …”. los time.sleep() La llamada a la funci贸n hace que el programa espere tres segundos.

        # output current process id
        print('My PID is:', os.getpid())
    
        # wait in an endless loop for signals 
        while True:
            print('Waiting...')
            time.sleep(3)
    

    Finalmente, tenemos que probar nuestro gui贸n. Habiendo guardado el gui贸n como signal-handling.py podemos invocarlo en una terminal de la siguiente manera:

    $ python3 signal-handling.py 
    My PID is: 5746
    Waiting...
    ...
    

    En una segunda ventana de terminal enviamos una se帽al al proceso. Identificamos nuestro primer proceso, la secuencia de comandos de Python, por la identificaci贸n del proceso que aparece en la pantalla, arriba.

    $ kill -1 5746
    

    El manejador de eventos de se帽al en nuestro programa Python recibe la se帽al que le hemos enviado al proceso. Reacciona en consecuencia y simplemente confirma la se帽al recibida:

    ...
    Received: 1
    ...
    

    Ignorando se帽ales

    El m贸dulo de se帽ales define formas de ignorar las se帽ales recibidas. Para hacer eso, la se帽al debe estar conectada con la funci贸n predefinida signal.SIG_IGN. El siguiente ejemplo demuestra que, y como resultado, el programa Python no puede ser interrumpido por CTRL+C nunca m谩s. Para detener el script de Python, se implement贸 una forma alternativa en el script de ejemplo: la se帽al SIGUSR1 termina el script de Python. Adem谩s, en lugar de un bucle sin fin usamos el m茅todo signal.pause(). Solo espera a que se reciba una se帽al.

    import signal
    import os
    import time
    
    def receiveSignal(signalNumber, frame):
        print('Received:', signalNumber)
        raise SystemExit('Exiting')
        return
    
    if __name__ == '__main__':
        # register the signal to be caught
        signal.signal(signal.SIGUSR1, receiveSignal)
    
        # register the signal to be ignored
        signal.signal(signal.SIGINT, signal.SIG_IGN)
    
        # output current process id
        print('My PID is:', os.getpid())
    
        signal.pause()
    

    Manejo adecuado de las se帽ales

    El manejador de se帽ales que hemos usado hasta ahora es bastante simple y solo informa una se帽al recibida. Esto nos muestra que la interfaz de nuestro script Python est谩 funcionando bien. Mejor茅moslo.

    Captar la se帽al ya es una buena base, pero requiere algunas mejoras para cumplir con las reglas del est谩ndar POSIX. Para una mayor precisi贸n, cada se帽al necesita una reacci贸n adecuada (consulte la lista anterior). Esto significa que el controlador de se帽ales en nuestro script de Python debe ampliarse mediante una rutina espec铆fica por se帽al. Esto funciona mejor si entendemos qu茅 hace una se帽al y cu谩l es una reacci贸n com煤n. Un proceso que recibe la se帽al 1, 2, 9 o 15 termina. En cualquier otro caso, se espera que tambi茅n escriba un volcado de memoria.

    Hasta ahora hemos implementado una 煤nica rutina que cubre todas las se帽ales y las maneja de la misma manera. El siguiente paso es implementar una rutina individual por se帽al. El siguiente c贸digo de ejemplo demuestra esto para las se帽ales 1 (SIGHUP) y 15 (SIGTERM).

    def readConfiguration(signalNumber, frame):
        print ('(SIGHUP) reading configuration')
        return
    
    def terminateProcess(signalNumber, frame):
        print ('(SIGTERM) terminating the process')
        sys.exit()
    

    Las dos funciones anteriores est谩n conectadas con las se帽ales de la siguiente manera:

        signal.signal(signal.SIGHUP, readConfiguration)
        signal.signal(signal.SIGTERM, terminateProcess)
    

    Ejecutar el script de Python y enviar la se帽al 1 (SIGHUP) seguida de una se帽al 15 (SIGTERM) por los comandos de UNIX kill -1 16640 y kill -15 16640 da como resultado la siguiente salida:

    $ python3 daemon.py
    My PID is: 16640
    Waiting...
    Waiting...
    (SIGHUP) reading configuration
    Waiting...
    Waiting...
    (SIGTERM) terminating the process
    

    El script recibe las se帽ales y las maneja correctamente. Para mayor claridad, este es el gui贸n completo:

    import signal
    import os
    import time
    import sys
    
    def readConfiguration(signalNumber, frame):
        print ('(SIGHUP) reading configuration')
        return
    
    def terminateProcess(signalNumber, frame):
        print ('(SIGTERM) terminating the process')
        sys.exit()
    
    def receiveSignal(signalNumber, frame):
        print('Received:', signalNumber)
        return
    
    if __name__ == '__main__':
        # register the signals to be caught
        signal.signal(signal.SIGHUP, readConfiguration)
        signal.signal(signal.SIGINT, receiveSignal)
        signal.signal(signal.SIGQUIT, receiveSignal)
        signal.signal(signal.SIGILL, receiveSignal)
        signal.signal(signal.SIGTRAP, receiveSignal)
        signal.signal(signal.SIGABRT, receiveSignal)
        signal.signal(signal.SIGBUS, receiveSignal)
        signal.signal(signal.SIGFPE, receiveSignal)
        #signal.signal(signal.SIGKILL, receiveSignal)
        signal.signal(signal.SIGUSR1, receiveSignal)
        signal.signal(signal.SIGSEGV, receiveSignal)
        signal.signal(signal.SIGUSR2, receiveSignal)
        signal.signal(signal.SIGPIPE, receiveSignal)
        signal.signal(signal.SIGALRM, receiveSignal)
        signal.signal(signal.SIGTERM, terminateProcess)
    
        # output current process id
        print('My PID is:', os.getpid())
    
        # wait in an endless loop for signals 
        while True:
            print('Waiting...')
            time.sleep(3)
    

    Otras lecturas

    Utilizando el signal m贸dulo y un controlador de eventos correspondiente, es relativamente f谩cil captar se帽ales. Conocer el significado de las diferentes se帽ales y reaccionar correctamente seg煤n lo definido en el est谩ndar POSIX es el siguiente paso. Requiere que el controlador de eventos distinga entre las diferentes se帽ales y tenga una rutina separada para todas ellas.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *