Sunteți pe pagina 1din 8

URACCAN

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.

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:


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 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

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){
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:
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.

- Realice un programa que pregunte por el PID de un proceso y el número de señal


que se le va a enviar, luego deberá enviar dicha señal al proceso indicado.

- Realice un programa que imprima el mensaje "han sido X segundos" cada 2


segundos, cambiando X por la cantidad de segundos transcurridos desde que se
lanzó el programa.

- 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).

- 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”.
URACCAN
Sistemas Operativos
HILOS
Biblioteca de Hilos:
#include <pthread.h>
Funciones POSIX de gestión básica de Hilos:
pthread_create Crea un Hilo para ejecutar una función determinada
pthread_exit Causa la terminación de un Hilo
pthread_attr_init Inicializa los atributos del Hilo a su valor por defecto
pthread_join Hace que el Hilo que la invoca espere a que termine un Hilo determinado
pthread_self devuelve la identidad del Hilo que lo invoca

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).

Clasificación de llamadas POSIX para gestión de mutex:


– Creación y destrucción:
pthread_mutex_init
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
– Se el mutex con los atributos por defecto (PTHREAD_MUTEX_INITIALIZER)
pthread_mutex_destroy
– Cierre y apertura
pthread_mutex_lock (Operación de cierre (P))
pthread_mutex_unlock (Operación de apertura (V))
URACCAN
Sistemas Operativos

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);

S-ar putea să vă placă și