practica no 3
DESCRIPTION
señalesTRANSCRIPT
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
Practica No 3
Señales
Contenido
Introducción
Envío de una señal a otros procesos
Tratamiento de una señal
SIGALARM para crear temporizadores en nuestros programas
Ejemplo del uso de Señales entre procesos padre e hijo
Introducción
En muchas situaciones los programas deben de estar preparados para tratar situaciones inesperadas o impredecibles,
como:
error en operación en punto flotante,
aviso de un reloj de alarma,
la muerte de un proceso hijo,
solicitud de terminación por parte del usuario (Control-C),
solicitud de suspensión por parte del usuario (Control-Z),
etc.
Cuando una de estas situaciones se produce, el kernel envía una señal al proceso correspondiente. Además, cualquier
proceso puede enviar una señal a otro proceso, si tiene permiso. En System V hay definidas 19 señales, mientras que
BSD define 11 señales más. Cuando un proceso recibe una señal puede tratarla de tres formas diferentes:
Ignorar la señal, con lo cual es inmune a la misma.
Invocar a una rutina de tratamiento por defecto. Esta rutina la posee el kernel.
Invocar a una rutina propia para tratar la señal.
La rutina de tratamiento por defecto de una señal realiza una de las siguientes acciones:
Termina el proceso y genera un fichero core, que contiene un volcado de memoria del contexto del proceso
(dump).
Termina el proceso sin generar un fichero core (quit).
Ignora la señal (ignore).
Suspende el proceso (suspend).
Reanuda la ejecución del proceso.
Envío de señal a otros procesos:
kill() int kill ( int pid,int sig ) ;
- kill() envía la señal con valor sig al proceso cuyo PID es pid.
- La señal se envía de forma satisfactoria si el proceso que envía y el que recibe son del mismo usuario, o bien si el
proceso que envía es del superusuario.
- kill() funciona de forma diferente dependiendo del valor de pid:
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
Si pid > 0 la señal se envía al proceso cuyo PID es pid.
Si pid = 0 la señal se envía a todos los procesos que pertenecen al mismo grupo del proceso
Si pid = -1 la señal se envía a todos procesos cuyo UID real es igual al UID efectivo del proceso que la envía. Si el
proceso que la envía tiene UID efectivo de superusuario, la señal es enviada a todos los procesos, excepto al proceso 0
(swapper) y 1 (init).
Si pid < -1 la señal es enviada a todos los procesos cuyo ID de grupo coincide con el valor absoluto de pid.
Para mostrar las señales que nos proporciona nuestro núcleo y su identificativo numérico asociado, usaremos
el siguiente comando:
~$ 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 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 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX
Algunas señales importantes <signal.h>:
SIGTERM = Finalización controlada. Se envía para indicarle a un proceso que debe acabar su ejecución. Puede ser
ignorada.
SIGKILL = Finalización abrupta. No se puede ignorar
SIGINT = Interrupción. Se envía cuando se pulsa la tecla de interrupción (Ctrl+C). Por defecto, se interrumpe el
programa.
SIGCLD = Terminación de algún proceso hijo. Se envía al proceso padre. Ignorada por defecto
SIGCHLD = Cuando un proceso termina o para, el proceso envía esta señal a su padre.
Señales disponibles para el programador:
SIGUSR1 y SIGUSR2: Su significado es el que quiera definir el programador en su aplicación.
Ejemplo envío de una señal a un proceso hijo mediante el uso de kill:
Programa que crea un proceso hijo que imprime cada segundo su pid. El proceso padre duerme 10 segundos y luego
mandará una señal al hijo para que termine.
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
main()
{
int pid;
if ((pid = fork()) = = 0)
{
while(1){
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
printf(“HIJO. PID = %d\n”,getpid());
sleep(1);
}
}
sleep(10);
printf(“ PADRE. Terminacion del Proceso %d \n”, pid);
kill (pid, SIGTERM);
exit(0);
}
Tratamiento de una señal
Para tratar una señal se utiliza la función: signal().
Cuando un proceso recibe una señal puede tratarla de tres formas diferentes: Ignorar la señal.
Invocar a una rutina de tratamiento por defecto. Esta rutina la posee el kernel.
Invocar a una rutina creada por el usuario para tratar la señal.
#include <signal.h>
void (*signal (int sig, void (*acción) ())) ();
- signal() permite a un proceso especificar la acción a tomar cuando reciba una señal en particular.
- sig especifica el número de la señal a tratar.
- acción puede tomar:
SIG_DFL: indica que se use el manejador por defecto del kernel.
SIG_IGN: indica que la señal se debe ignorar.
Cuando queremos que un proceso espere a que le llegue una señal, usaremos la función pause(). Esta función
provoca que el proceso (o thread) en cuestión “duerma” hasta que le llegue una señal. Para capturar esa señal, el
proceso deberá haber establecido un tratamiento de la misma con la función signal(). La función pause() no recibe
ningún parámetro y retorna –1 cuando la llamada a la función que captura la señal ha terminado.
#include <unistd.h>
int pause(void);
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
Ejemplo: Ignorar la señal. Ejemplo: Invocar a una rutina de
tratamiento por defecto.
Ejemplo: Invocar a una rutina propia
para tratar la señal
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
main()
{
int pid;
pid=fork();
if (pid==0)
{
/*codigo hijo*/
signal(SIGTERM,SIG_IGN);
while(1)
{
printf("soy el hijo\n");
sleep(1);
}
}
else
{
sleep(5);
printf("\n Proceso Padre..\n");
kill(pid,SIGTERM);
printf("\n FIN DEL PADRE \n");
}
}
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
main()
{
int pid;
pid=fork();
if (pid==0)
{
/*codigo hijo*/
signal(SIGTERM,SIG_DFL);
while(1)
{
printf("soy el hijo\n");
sleep(1);
}
}
else
{
sleep(5);
printf("\n Proceso Padre...\n");
kill(pid,SIGTERM);
printf("\n FIN DEL PADRE \n");
}
}
#include<signal.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
void mifuncion()
{
printf("\n SOY LA RUTINA DE
TRATAMIENTO POR EL
USUARIO\n");
exit(0);
}
main()
{
int pid;
pid=fork();
if (pid==0)
{
/*codigo hijo*/
signal(SIGTERM,mifuncion);
while(1)
{
printf(" soy el hijo\n");
sleep(1);
}
}
else
{
sleep(10);
printf(" Fin del Padre\n");
kill(pid,SIGTERM);
exit(0);
}
}
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
Ejemplo (Captura la señal cuyo Id asociado esta entre 1 y 64 ):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void trapper(int);
main()
{
int i;
for(i=1;i<=64;i++)
signal(i, trapper);
printf("Proceso \n");
pause();
printf("Continuando...\n");
return 0;
}
void trapper(int sig)
{
signal(sig, trapper);
printf("Recibida la señal: %d\n", sig);
}
Explicación del programa:
Inicialmente declaramos una función que va a recibir un entero como parámetro y se encargará de capturar una
señal (trapper() ). Seguidamente capturamos todas las señales de 1 a 64 haciendo 64 llamadas a signal(), pasando como
primer parámetro el número de la señal (i) y como segundo parámetro la función que se hará cargo de dicha señal
(trapper). Seguidamente el programa espera a que le llegue una señal con la función pause(). El programa esperará
indefinidamente la llegada de esa señal, y cuando le enviemos una (por ejemplo, pulsando Control+C), la función
encargada de gestionarla (trapper() ) será invocada. Lo primero que hace trapper() es volver a enlazar la señal en
cuestión a la función encargada de gestionarla, es decir, ella misma, y luego saca por la salida estándar la señal
recibida. Al terminal la ejecución de trapper(), se vuelve al punto donde estábamos ( pause() ) y se continua:
SIGALARM para crear temporizadores en nuestros programas
Una utilización bastante potente de las señales es el uso de SIGALARM para crear temporizadores en nuestros
programas. Con la función alarm() lo que conseguimos es que nuestro proceso se envíe a sí mismo una señal
SIGALARM en el número de segundos que especifiquemos. El prototipo de alarm() es el siguiente:
unsigned int alarm(unsigned int seconds);
En su único parámetro indicamos el número de segundos que queremos esperar desde la llamada a alarm() para recibir
la señal SIGALARM.
La llamada a la función alarm() generará una señal SIG_ALARM hacia el mismo proceso que la invoca.
El valor devuelto es el número de segundos que quedaban en la anterior alarma antes de fijar esta nueva alarma. Esto es
importante: sólo disponemos de un temporizador para usar con alarm(), por lo que si llamamos seguidamente otra vez a
alarm(), la alarma inicial será sobrescrita por la nueva.
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
Ejemplo de su utilización:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void trapper(int);
main()
{
int i;
signal(14, trapper);
printf("Identificativo de proceso: %d\n", getpid() );
alarm(5);
pause();
alarm(3);
pause();
for(;;)
{
alarm(1);
() pause();
}
return 0;
}
void trapper(int sig)
{
signal(sig, trapper);
printf("RIIIIIIIIING!\n");
}
Explicación del programa:
Este programa es bastante similar al que hemos diseñado antes para capturar señales, sólo que ahora en lugar
de capturarlas todas, capturará únicamente la 14, SIGALARM. Cuando reciba una señal SIGALARM, sacará
“RIIIIIIIIING” por pantalla. El cuerpo del programa indica que se fijará una alarma de 5 segundos y luego se esperará
hasta recibir una señal, luego la alarma se fijará a los 3 segundos y se volverá a esperar, y finalmente se entrará en un
bucle en el que se fije una alarma de 1 segundo todo el rato. El resultado es que se mostrará un mensaje “RIIIIIIIIING”
a los 5 segundos, luego a los 3 segundos y después cada segundo:
Ejemplo del uso de Señales entre procesos padre e hijo
En este ejemplo, el hijo envía varias señales SIGUSR1 a su padre y al final termina por matarlo, enviándole la señal
SIGKILL:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void trapper(int sig)
{
signal(sig, trapper);
printf("SIGUSR1\n");
}
UNAN-Leon
Sistemas Operativos
Msc. Rina Arauz
main()
{
pid_t padre, hijo;
padre = getpid();
signal( SIGUSR1, trapper );
if ( (hijo=fork()) == 0 )
{ /* hijo */
sleep(1);
kill(padre, SIGUSR1);
sleep(1);
kill(padre, SIGUSR1);
sleep(1);
kill( padre, SIGUSR1);
sleep(1);
kill(padre, SIGKILL);
exit(0);
}
else
{ /* padre */
for (;;);
}
return 0;
}
EJERCICIOS
1. Realice un programa que cree un proceso hijo, deberá entrar en un bucle infinito y esperar. El proceso padre
deberá matar a su proceso hijo después de 10 segundos. Y aparecerá por pantalla el PID del padre, el PID del
hijo y un mensaje indicando la muerte del hijo y la finalización del padre.
2. Cree un proceso que nos informe de la cantidad de veces que se pulsa (CTRL+C) en un periodo de 25
segundos.
3. Realice un programa que mediante el uso de la señal SIGALARM realice lo siguiente. Un programa espera por
una cadena introducida por teclado. Si pasados 20 segundos esta acción no se realiza, el proceso deberá
decir”Termino el tiempo de espera… FIN”.
4. Implementar un programa que cree un proceso hijo, el cual deberá imprimir su PID, y PPID. El padre deberá
esperar su finalización y capturar la señal de muerte del proceso hijo e imprimir el mensaje “MI HIJO HA
MUERTO”.