Breaking

Post Top Ad

Your Ad Spot

viernes, 5 de julio de 2019

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 las señales y pertenecen a los diferentes métodos de comunicación entre procesos (Inter Process Communication, abreviados con IPC).
En resumen, las señales son interrupciones de software que se envían al programa (o al proceso) para notificar al programa eventos significativos 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 simplemente ignora la señal.
Aunque está definido en el estándar POSIX , la reacción en realidad depende de cómo el desarrollador escribió el script e implementó el manejo de las señales.
En este artículo le explicamos qué son las señales, le mostramos cómo enviar una señal a otro proceso desde la línea de comando y cómo procesa 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 Python.

Una introducción a las señales

En los sistemas basados ​​en UNIX, hay tres categorías de señales:
  • Señales del sistema (errores de hardware y 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 disponibles es comparativamente larga y no es consistente entre las diferentes variantes de UNIX / Linux. En un sistema Debian GNU / Linux, el comando kill -lmuestra 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 más o menos estandarizadas y tienen el siguiente significado en la mayoría de los sistemas Linux:
  • 1 (SIGHUP): finaliza una conexión o vuelve a cargar la configuración para los demonios
  • 2 (SIGINT): interrumpir la sesión desde la estación de diálogo
  • 3 (SIGQUIT): termina la sesión desde la estación de diálogo
  • 4 (SIGILL): instrucción ilegal fue ejecutada
  • 5 (SIGTRAP): hacer una sola instrucción (trampa)
  • 6 (SIGABRT): terminación anormal
  • 7 (SIGBUS): error en el bus del sistema
  • 8 (SIGFPE): error de punto flotante
  • 9 (SIGKILL): terminar inmediatamente el proceso
  • 10 (SIGUSR1): señal definida por el usuario
  • 11 (SIGSEGV): falla de segmentación debido al acceso ilegal de un segmento de memoria
  • 12 (SIGUSR2): señal definida por el usuario
  • 13 (SIGPIPE): escribiendo en una tubería, y nadie está leyendo de ella
  • 14 (SIGALRM): el temporizador terminó (alarma)
  • 15 (SIGTERM): termina el proceso de manera suave
Para enviar una señal a un proceso en un terminal de Linux, invoca el killcomando con el número de señal (o nombre de 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 opción 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 de inmediato.

Usando la biblioteca de señales de Python

Desde Python 1.4, la signalbiblioteca es un componente regular de cada versión de Python. Para usar la signalbiblioteca, importe la biblioteca a su programa de Python de la siguiente manera:
import signal  
Capturar y reaccionar adecuadamente en una señal recibida se realiza mediante una función de devolución de llamada, el llamado controlador de señales. Un controlador 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 nada más que informar el número de la señal recibida. El siguiente paso es registrar las señales que son capturadas por el manejador de señales. Para los programas de Python, todas las señales (excepto 9, SIGKILL) se pueden capturar en su script:
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 de proceso para el proceso actual y detectamos la identificación del proceso utilizando el método getpid()del osmódulo. En un whilebucle sin fin esperamos las señales entrantes. Implementamos esto usando dos módulos más de Python - os y time . Los importamos al principio de nuestro script en Python, también:
import os  
import time  
En el whilebucle de nuestro programa principal, la instrucción de impresión muestra "Esperando ...". La time.sleep()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 script como signal-handling.pypodemos invocarlo en un 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 impresa en la pantalla, arriba.
$ kill -1 5746
El controlador de eventos de señal en nuestro programa Python recibe la señal que hemos enviado al proceso. Reacciona en consecuencia, y simplemente confirma la señal recibida:
...
Received: 1  
...

Ignorando las señales

El módulo de señal define formas de ignorar las señales recibidas. Para hacer eso, la señal debe estar conectada con la función predefinida signal.SIG_IGNEl siguiente ejemplo demuestra que, y como resultado, el programa Python ya no puede ser interrumpido CTRL+CPara detener el script de Python, se ha implementado 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 utilizamos el método signal.pause()Simplemente 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()

Manipulación de señales correctamente

El manejador de señales que hemos usado hasta ahora es bastante simple, y solo reporta una señal recibida. Esto nos muestra que la interfaz de nuestro script Python está funcionando bien. Vamos a mejorarlo.
La captura de 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 extenderse a una rutina específica por señal. Esto funciona mejor si entendemos lo que hace una señal y lo que 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 escriba un volcado de núcleo, también.
Hasta ahora hemos implementado una rutina única 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 lo demuestra 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)
Ejecutando el script de Python, y enviando la señal 1 (SIGHUP) seguida de una señal 15 (SIGTERM) por los comandos de UNIX kill -1 16640kill -15 16640da 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 adecuadamente. Para mayor claridad, este es el script 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

El uso del signalmódulo y un controlador de eventos correspondiente es relativamente fácil de detectar señales. Conocer el significado de las diferentes señales y reaccionar adecuadamente como se define en el estándar POSIX es el siguiente paso. Requiere que el controlador de eventos distinga entre las diferentes señales y tiene una rutina separada para todas ellas.

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Post Top Ad

Your Ad Spot

Páginas