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.
Te puede interesar:Conjuntos en PythonIntroducción a las señales
Contenido
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.
Te puede interesar:Biblioteca Pandas para la visualización de datos en PythonCaptar 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.