Documente Academic
Documente Profesional
Documente Cultură
Sistemas Operativos
Practica No 8
Señales y manejo de hilos
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.
- 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.
Para mostrar las señales que nos proporciona el 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
URACCAN
Sistemas Operativos
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
#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:
URACCAN
Sistemas Operativos
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);
Ejemplo: Ignorar la señal. Ejemplo: Invocar a una rutina de Ejemplo: Invocar a una rutina propia
tratamiento por defecto. para tratar la señal
#include<stdio.h> #include<stdio.h> #include<signal.h>
#include<stdlib.h> #include<stdlib.h> #include<stdlib.h>
#include<signal.h> #include<signal.h> #include<stdio.h>
main() main() #include<sys/types.h>
{ {
int pid; int pid; void mifuncion()
pid=fork(); pid=fork(); {
if (pid==0) if (pid==0) printf("\n SOY LA RUTINA DE
{ { TRATAMIENTO POR EL
/*codigo hijo*/ /*codigo hijo*/ USUARIO\n");
signal(SIGTERM,SIG_IGN); signal(SIGTERM,SIG_DFL); exit(0);
while(1) while(1) }
{ {
printf("soy el hijo\n"); printf("soy el hijo\n"); main()
sleep(1); sleep(1); {
} } int pid;
} } pid=fork();
else else if (pid==0)
{ { {
sleep(5); sleep(5); /*codigo hijo*/
printf("\n Proceso Padre..\n"); printf("\n Proceso Padre...\n"); signal(SIGTERM,mifuncion);
kill(pid,SIGTERM); kill(pid,SIGTERM); while(1)
printf("\n FIN DEL PADRE \n"); printf("\n FIN DEL PADRE {
} \n"); printf(" soy el hijo\n");
} } sleep(1);
} }
}
else
{
sleep(10);
printf(" Fin del Padre\n");
kill(pid,SIGTERM);
exit(0);
}
}
URACCAN
Sistemas Operativos
1) Ejercicios:
- Realice el siguiente programa: el proceso padre envía una señal para indicarle a su
proceso hijo que debe acabar su ejecución, el proceso recibe la señal e imprime “he
recibido la señal SGTERM de mi proceso padre” y finaliza.
- Escriba un programa que cree dos procesos hijos. El proceso hijoUno enviará la
señal SIGUSR1 al proceso padre e imprimirá la cadena "hola, que tal" cada 5
segundos. El proceso padre, al recibir la señal SIGUSR1 enviará esta misma señal al
proceso hijoDos, que al recibir dicha señal mostrará por pantalla "es un saludo “.
- 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.
URACCAN
Sistemas Operativos
SIGALARM para crear temporizadores en nuestros programas
Con la función alarm() el 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. Si
llamamos seguidamente otra vez a alarm(), la alarma inicial será sobrescrita por la nueva.
Ejemplo: (En este ejemplo el proceso programa un señal SIGALRM a los 5 segundos, se queda a la espera de
dicha señal mediante la función pause(), pasados los 5 segundos recibe la señal e imprime “acabo de recibir un
sigalrm”, continua con la ejecución y finaliza).
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void fun_alarma()
{
printf("acabo de recibir una señal SIGALRM\n");
}
main()
{
signal(SIGALRM,fun_alarma);
printf("Desde ahora estaré atento para recibir la señal SIGALRM y tratarla con la función fun_alarma\n");
printf("continuo ejecutando\n");
alarm(5);
printf("he puesto la alarma para que venga en 5seg y con la función pause() esperare hasta recibirla!!!!!\n");
pause();
printf("ahora continúo con la ejecución normal y finalizo\n");
}
2) Ejercicios
- Realice un programa que mediante el uso de SIGALRM y utilizando un contador,
imprima en tiempo real los segundos transcurridos desde el momento que se ejecuta
(cada segundo).
Ejemplo:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *fhilo(void *p)
{
sleep(4);
printf ("Soy el Hilo\n");
}
void main( )
{
pthread_t hilo1, hilo2;
printf("Soy la tarea principal\n");
pthread_create(&hilo1, NULL, fhilo, NULL);
pthread_create(&hilo2, NULL, fhilo, NULL);
printf("He lanzado 2 hilos, ahora esperare que finalicen\n");
pthread_join(hilo1,NULL);
pthread_join(hilo2,NULL);
printf("Mis hilos han terminado, Adios\n");
}
Es necesario compilar con la siguiente línea, en la que se observa la invocación a la biblioteca de hilos:
cc hilos.c -o hilos -lpthread
3) Ejercicio:
- Realice un programa que lance 2 hilos. Para la ejecución de los hilos deberá escribir
una única función que recibirá un único parámetro entero “n”, mediante un ciclo
infinito los hilos deberán dormir el número de segundos indicados por “n”. La tarea
principal deberá indicar el parámetro “n” para cada hilo, luego deberá dormir 50
segundos, imprimir “Fin tarea principal” y terminar. Explique la salida. Que
ocurrió con los hilos al terminar la tarea principal?
URACCAN
Sistemas Operativos
SINCRONIZACION DE PTHREADS (HILOS)
Mutexes
Los mutex son mecanismos de sincronización a nivel de hilos (threads). Se utilizan sólo para garantizar la exclusión
mutua. Funcionan como un cerrojo. Existen dos operaciones básicas de acceso (funciones): cierre y apertura. Cada
mutex posee:
estados: abierto o cerrado
propietario: Un hilo es el propietario del mutex cuando ha ejecutado sobre él una operación de cierre con éxito.
Funcionamiento:
– Un mutex se crea inicialmente abierto y sin propietario
– Cuando un hilo invoca la operación de cierre:
Si el mutex estaba abierto (sin propietario), lo cierra y pasa a ser su propietario
Si el mutex ya estaba cerrado, el hilo invocante se suspende
– Cuando el propietario del hilo invoca la operación de apertura:
Se abre el mutex
Si existían hilos suspendidos en el mismo, se selecciona uno y se despierta, con lo que puede cerrar el mismo (y pasa a
ser el nuevo propietario).
Ejemplo: Acceso concurrente a una variable por parte de Ejemplo: Acceso concurrente a una variable por parte de
dos hilos sin sincronización (condiciones de carrera en dos hilos usando sincronización (mutex)
variable “x”) /*hilos3.c*/
/*hilos2.c*/ #include<stdio.h>
#include<stdio.h> #include<stdlib.h>
#include<stdlib.h> #include<pthread.h>
#include<pthread.h> int x=1;
int x=1; pthread_mutex_t m=PTHREAD_MUTEX_INITIALIZER;
void *fhilo1(void *p) void *fhilo1(void *p)
{ unsigned long int c; { unsigned long int c;
for(c=0;c<10000000;c++) for(c=0;c<10000000;c++)
x=x+1; { pthread_mutex_lock(&m);
} /* Seccion Critica*/
void *fhilo2(void *p) x=x+1;
{ unsigned long int c; pthread_mutex_unlock(&m);
for(c=0;c<10000000;c++) }
x=x-1; }
} void *fhilo2(void *p)
int main() { unsigned long int c;
{ pthread_t hilo1, hilo2; for(c=0;c<10000000;c++)
pthread_create(&hilo1, NULL, fhilo1, NULL); { pthread_mutex_lock(&m);
pthread_create(&hilo2, NULL, fhilo2, NULL); /*Seccion Critica */
pthread_join(hilo1,NULL); x=x-1;
pthread_join(hilo2,NULL); pthread_mutex_unlock(&m);
printf("\n EL VALOR FINAL DE LA VAR ES %d\n",x); }
} }
int main()
Compilar: cc hilos2.c –o hilos2 -lpthread { pthread_t hilo1, hilo2;
Salida: pthread_create(&hilo1, NULL, fhilo1, NULL);
pthread_create(&hilo2, NULL, fhilo2, NULL);
pthread_join(hilo1,NULL);
pthread_join(hilo2,NULL);
printf("\n EL VALOR FINAL DE LA VAR ES %d\n",x);
}
Compilar: cc hilos3.c –o hilos3 -lpthread
Salida:
4) Ejercicio:
- Realice un programa que resuelva el problema del productor-consumidor con hilos y MUTEXES.
Productor Consumidor
P (smf_vacíos); P (smf_llenos);
P (exmut); P (exmut);
Produce un dato; Consume el dato;
V (exmut); V (exmut);
V (smf_llenos); V (smf_vacíos);