Manejo de señales Unix en Python

M

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 ​​en 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.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad