Ejemplos java y C/linux

Tutoriales

Enlaces

Licencia

Creative Commons License
Esta obra está bajo una licencia de Creative Commons.
Para reconocer la autoría debes poner el enlace http://www.chuidiang.com

Señales y Alarmas en C de Unix/Linux

Conceptos

Una señal es un "aviso" que puede enviar un proceso a otro proceso. El sistema operativo unix se encarga de que el proceso que recibe la señal la trate inmediatamente. De hecho, termina la línea de código que esté ejecutando y salta a la función de tratamiento de señales adecuada. Cuando termina de ejecutar esa función de tratamiento de señales, continua con la ejecución en la línea de código donde lo había dibujado.

El sistema operativo envía señales a los procesos en determinadas circunstancias. Por ejemplo, si en el programa que se está ejecutando en una shell nosotros apretamos Ctrl-C, se está enviando una señal de terminación al proceso. Este la trata inmediatamente y sale. Si nuestro programa intenta acceder a una memoria no válida (por ejemplo, accediendo al contenido de un puntero a NULL), el sistema operativo detecta esta circunstancia y le envía una señal de terminación inmediata, con lo que el programa "se cae".

Las señales van identificadas por un número entero. No vamos a detallar todas, pero sí alguna de las más interesantes para el programador.

Nombre de señal Propósito. Se envia al proceso cuando...
SIGINT  2 ... se pulsa Ctrl-C
SIGFPE 8 ... hay un error en coma flotante (ejemplo, división por cero)
SIGPIPE 13 ... se intenta escribir en una conexión (socket, tubería, ...) rota
(no hay proceso leyendo al otro lado).
SIGALRM 14 ... cuando termina un temporizador.
SIGUSR1 16 ... el programador lo decide. Esta señal es para uso del programador.
No la utiliza el sistema operativo.
SIGUSR2 17 ... el programador lo decide. Idem a la anterior

En Senhales.txt tienes un cacho del fichero .h del sistema en el que están todas las posibles señales.

Nosotros, desde un shell de comandos unix, podemos enviar señales a los procesos con el comando kill. Su uso más conocido es el kill -9 idProceso, que envía una señal 9 al proceso, haciendo que termine. También es posible enviar señales desde un programa en C a otro programa en C que esté corriendo en el mismo ordenador.

Todas las señales tienen una función de tratamiento por defecto. Cuando nuestro programa está en ejecución, si recibe una señal, saltará a la función por defecto para tratamiento de esa señal y luego continuará su ejecución normal (si la señal no es de terminación). Todo esto es totalmente invisible para nosotros. Únicamente lo notaremos si nuestro programa se sale inesperadamente.

Sin embargo, nuestro programa puede cambiar casi todas las funciones por defecto de tratamiento de señales, pudiendo hacer que al recibir una señal el programa haga lo que nostros queramos.

Capturar señales

Como ejemplo, vamos a hacer un programita que capture la señal que se envía cuando se pulsa Ctrl-C y la ignore.

La función C que nos permite redefinir la función de tratamiento de señal es signal(). Esta función admite dos parámetros:

La función devuelve un puntero a la función de tratamiento que había antes de poner la nuestra, de esta forma podemos guardarla y restaurarla cuando nos interese. Si no se ha podido poner nuestra función, devuelve un SIG_ERR.

En nuestro código, para reemplazar la función de tratamiento del Ctrl-C, pondremos:

void controlador (int);
...
signal (SIGINT, controlador);

¡Ya está!. Cuando alguien pulse Ctrl-C sobre nuestro programa, se llamará a nuestra función controlador().

Un pequeño detalle. En algunos sistemas unix, cuando se recibe una señal y se ejecuta nuestra función, se restaura automáticamente la función por defecto. Por ello, a veces es necesario que nuestra función termine volviendo a ponerse ella misma como tratamiento de la esa señal.

También podemos hacer que una señal se ignore, es decir, que no la trate nadie, ni nuestra función ni la de por defecto. Para ello hay que poner

signal (numeroSenhal, SIG_IGN);

Para restaurar el tratamiento por defecto de la señal

signal (numeroSenhal, SIG_DFL);

En ctrlc.c tienes el código de ejemplo. Lo puedes descargar, quitarle la extensión .txt y compilarlo con make ctrlc. Al ejecutarlo, la primera vez que pulses Ctrl-C no te hará caso y a la segunda saldrá normalmente.

La función pause() hace que el proceso quede suspendido hasta que llegue una señal.

Enviar señales

Un proceso puede enviarle señales a otro. La función para ello es kill(). Admite dos parámetros:

En  pruebakill.c tienes un ejemplo en el que el main crea un proceso hijo y le envía una señal SIGUSR1 cada segundo. El proceso hijo está a la espera y escribe un aviso en pantalla cada vez que le llega la señal. Puedes bajarte el programa, quitarle la extensión .txt y compilarlo con make pruebakill.

Alarmas

Las alarmas las genera la función alarm(). Una vez llamada esta función, pasandole como parámetro un tiempo en segundos, se inicia una cuenta atrás de esa duración. Una vez terminada, se envía al proceso una señal SIGALRM, que se puede capturar. De esta forma podemos, por ejemplo, mantener en pantalla actualizado un reloj o mostrar una información durante un tiempo determinado y luego borrarla.

Para tiempos más finos, tenemos la función setitimer(), algo más compleja, pero que permite definir tiempos de micro-segundos. Esta función permite además que se nos avise repetidamente a intervalos de tiempo fijos.

Puesto que la función alarm() es más sencilla, vamos a hacer un ejemplo con la funcion setitimer(). El mecanismo es exactamente igual para las dos, pero setitimer() lleva unos parámetros más complejos.

Unix proporciona tres contadores de tiempo distintos para cada proceso.

Los dos segundos son más complejos y nos vamos a limitar al primero. El primero, además, lo utiliza también la función alarm(), por lo que es incompatible usarlo y usar también esta función.

La función setitimer() admite tres parámetros:

Las estructuras struct itimerval contienen dentro dos struct timerval:

struct itimerval
{
    struct timeval it_interval;
    struct timeval it_value;
};

En la primera estructura it_interval se indica cada cuanto queremos recibir la señal. Es decir, si queremos actualizar un reloj en pantalla con milésimas de segundo, en este campo debemo meter una milésima de segundo. Cada milésima de segundo nos enviará una señal SIGALRM. En la segunda it_value, se indica cuándo queremos recibir el primer aviso. Es decir, si it_value es 10 segundos e it_interval es 1 segundo, recibiremos una primera señal a los 10 segundos y a partir de ahí, cada segundo.

Como vemos, estos dos campos son a su vez otras dos estructuras. Estas son

struct timeval
{
    long tv_sec;
    long tv_usec;
};

El campo tv_sec indica tiempo en segundos. El campo tv_usec indica tiempo en microsegundos. Combinando estos dos campos podemos, por ejemplo, hacer tiempos de 2 segundos y 3 centésimas (tv_sec=2 y tv_usec=30000).

Bueno, ya estamos en disposición de ver el programita de ejemplo, pruebatimer.c. En él se lanza un contador de tiempo real con setitimer(), de forma que la primera vez salte a los 2 segundos y luego cada medio segundo. Se pone una función tratamientoSenhal() para tratar la señal SIGALRM que salta cada vez que el contador termina. Esta funcíón simplemente escribe un texto en pantalla.

Puedes bajarte el fichero, quitarle la extensión .txt y compilarlo con make pruebatimer. El ejecutarlo verás un texto a los dos segundos y luego se repite cada medio.

Estadísticas y comentarios

Numero de visitas desde el 4 Feb 2007: