Introducción
Las señales son
interrupciones generadas por software (esto en analogías a las
interrupciones generadas por el hardware, como lo puede haber estudiado
en estructuras de computadores).
Resumen: Proceso 1 envía señal llamando a
función kill() u ocurre evento generador de señal -------->
Proceso 2 recibe señal y efectúa acción asociada. Esta acción puede ser: poner término al
Proceso 2, invocar función por omisión, o invocar función definida por el
usuario.
Conceptos
El archivo <
signum.h > contiene la
definición de todas las señales. Cada señal
tiene un nombre.
Ellas comienzan con SIG. Ej. SIGALRM (señala temporizador expirado)
La ubicación
definitiva de este archivo depende del sistema operativo de su
máquina. Generalmente se puede llegar a él vía
/usr/include/signal.h. En Aragorn este archivo está en (2023):
/usr/include/bits/signum.h
Una forma de ubicar este archivo es usar: $ locate
signum.h
Numerosas condiciones pueden generar señales, entre ellas:
SIGALRM: generada cuando el timer asociado a la función alarm expira. También cuando el timer de intervalo es configurado (setitimer)Función signal: Permite registrar la función que atenderá la llegada de la señal.
SIGCHLD: Cuando un proceso termina o para, el proceso envía esta señal a su padre. Por omisión esta señal es ignorada. Ante su llegada y en la función que maneja la señal, el proceso padre puede invoca la función wait para obtener el estado de término del proceso hijo. Se evita así la creación de procesos "zombies".
SIGCONT: es enviada para reanudar un proceso que ha sido detenido (suspendido) con SIGSTOP.
SIGINT: generada con Control-c
SIGKILL: Permite terminar un proceso.
SIGTSTP: generada cuando presionamos Control-z. Puede ser ignorada.
SIGSTOP: similar a SIGTSTP pero no puede ser ignorada o capturada.
SIGUSR1: Es una señal definida por el usuario para ser usada en programas de aplicación.
SIGUSR2: Otra como la anterior.
Para probar las señales se recomienda usar
% kill -SIG??? <pid>
No use los números de determinado sistema, pues éstos han cambiado en el tiempo.
#include
<signal.h>
void (*signal
(int signo, void (*func) (int)))(int);
Este prototipo es equivalente
a:
typedef void
Sigfunc(int);
Sigfunc * signal (int signo,
Sigfunc *);
signo: es el número (mejor la constante) de la
señal.
func: puede ser la constante
SIG_IGN para ignorar la señal, SIG_DFL para usar la
acción por omisión, o puede ser una función definida en
el programa. Esta función es conocida como el manejador de la
señal (signal handler). El valor retornado es un puntero al
manejador previamente instalado para esa señal.
Ejemplo: uso de SIGUSR1 y SIGUSR2
Interrupción de
llamados al sistema
Cuando llega
una señal, ésta es atendida por su manejador. En éste se
debe poner cuidado de no invocar funciones no re-rentrantes. Éstas son
las que no permiten su re-invocación antes de su término; por ejemplo
strtok. Esto se explica pues el proceso que recibe la señal pudo estar
ejecutando una de tales llamadas y si el manejador lo usa generalmente
se obtiene un resultado errado.
Cuando llega una señal a un proceso bloqueado, por omisión éste sigue bloqueado luego de atendida la señal. En ocasiones deseamos que éste salga del bloqueo. Por ejemplo para suspender una entrada de datos cuando ésta se demora mucho. Hay formas de desbloquear un proceso ante la llegada de una señal, ver ejemplo readInterrupted.c.
Un ejemplo de llegada de señales en
proceso bloqueado se presentó en la tarea "rapidez
aritmética". Ver soluciones de la época de Daniel
Durán y Gonzalo
Vásquez. Para entender estas soluciones es necesario estudiar pipes, un mecanismo de comunicación entre procesos.
Funciones kill y raise
kill
envía una señal a un proceso o a un grupo de procesos.
#include
<sys/types.h>
#include
<signal.h>
int kill
(pid_t pid, int signo);
raise permite enviar señales al mismo proceso (hacia uno mismo).
int
raise(int signo);
Este último es como kill( getpid(), signo);
Funciones alarm y pause
La
función alarm permite especificar un timer que expire en un
tiempo dado.
#include
<unistd.h>
unsigned int
alarm(unsigned int seconds);
En versiones actuales de sistema operativo también se puede usar
unsigned int
ualarm(unsigned int microseconds);
La
función pause suspende al proceso llamador hasta que llegua cualquier
señal.
int
pause(void); /* retorna -1 con errno en EINTR */
El control
del tiempo no es exacto por la incertidumbre del kernel, carga del
sistema, etc.
Hay
sólo una alarma por proceso!
La alarma se
puede cancelar con seconds=0
en el argumento. El valor retornado es el tiempo restante para la
expiración del temporizador.
Ejemplo 1 de uso de alarma:
sleep() vía programa .
Explicar por
qué esta función tiene los siguientes problemas: ¿Qué
pasa
si el llamador ya tiene la alarma configurada?. ¿Que pasa si el
programa
tenía otro manejador definido previamente? ¿hay alguna carrera
crítica?
Ejemplo 2 de uso de alarma, llamado a read con timeout: Versión de prueba para sistemas con read interrumpible, Versión para sistemas con read no interrumplible.
Función sleep
sleep suspende
al proceso llamador por la una cantidad de segundos indicada o hasta
que se reciba una señal.
#include
<unistd.h>
unsiged int
sleep (unsigned int seconds);
Hoy
también tenemos disponible
unsiged int
usleep(unsigned int microseconds);
Retorna 0 o el tiempo que faltó por dormir.
También se dispone hoy de:
#include <time.h>
int nanosleep(const struct
timespec *req, struct timespec *rem);