semáforos - BUCOMSEC - Comunicaciones y Seguridad

Anuncio
Sistemas Operativos I
Práctica 4
Sincronización entre procesos.
Semáforos.
L
os sistemas operativos, además de proporcionar diferentes mecanismos para comunicar
procesos, pueden ofrecer herramientas que permiten sincronizar varios procesos. El paquete
denominado IPC (InterProcess Comunication) del sistema operativo UNIX proporciona un
conjunto de componentes para la comunicación y sincronización de procesos, entre los que
destaca la gestión de semáforos y la gestión de memoria analizada en la práctica anterior. A lo largo
de esta práctica estudiaremos las llamadas al sistema que proporciona el sistema operativo para
gestionar los semáforos.
SEMÁFOROS
Los semáforos que emplea el sistema operativos UNIX es muy similar a la definición clásica de
Dijkstra, en el sentido de que es una variable entera con operaciones atómicas de inicialización,
incremento y decremento con bloqueo.
UNIX define tres operaciones fundamentales sobre semáforos:
ƒ semget. Crea o toma el control de un semáforo
ƒ semctl. Operaciones de lectura y escritura del estado del semáforo. Destrucción del semáforo
ƒ semop. Operaciones de incremento o decremento con bloqueo
UNIX no ofrece las operaciones clásicas P y V, sino que dispone de la llamada al sistema semop
que permite realizar varias operaciones que incluyen las P y V. Previamente a utilizar estas
operaciones será necesario crear el semáforo e inicializarlo.
A continuación se describen las tres llamadas al sistema que permiten gestionar los semáforos en
UNIX.
LLAMADA SEMGET
int semget ( key_t key, int nsems, int semflg )
La llamada semget se emplea para solicitar al sistema operativo que cree un conjunto de semáforos
(un vector de semáforos). El valor devuelto (si no ha habido error) es el identificador del vector de
semáforos y permite referirse a él en sucesivas llamadas al sistema.
Semget tiene los siguientes argumentos:
ƒ key es la clave con la que se crea el semáforo, de modo que siempre que se utilice la misma clave
se podrá acceder al mismo semáforo. Puede ser IPC_PRIVATE (obliga a semget a crear un
nuevo y único identificador, nunca devuelto por anteriores llamadas a semget hasta que sea
liberado con semctl) o bien obtenerse de la función ftok que devuelve una clave (un número)
a partir de un nombre de archivo existente y accesible y un número que lo identifica (ver ejemplo de
uso y consultar man).
ƒ nsems es el número de semáforos que se van a crear (el tamaño del vector de semáforos).
ƒ semflg es un flag que permite establecer los permisos del semáforo y en caso de que ya esté
creado obtener su identificador.
2
Si la ejecución se realiza con éxito, entonces devolverá un valor no negativo identificando el
conjunto de semáforos. En caso contrario, devolverá -1 y la variable global errno tomará en código
del error producido.
LLAMADA SEMCTL
int semctl ( int semid, int semnun, int cmd, union semun arg )
La llamada semctl se emplea para realizar operaciones de control sobre los semáforos. Las
operaciones que más se emplean son:
ƒ SETVAL permite establecer el valor de los elementos del vector de semáforos.
ƒ IPC_RMID elimina el vector de semáforos del sistema y debe ser llamada antes de que el proceso
finalice, pues los semáforos no son eliminados del sistema cuando el proceso finaliza.
ƒ GETVAL devuelve el valor actual del semáforo.
Los argumentos de la llamada semctl son los siguientes:
ƒ semid es el identificador del vector de semáforos (obtenido de semget).
ƒ semnun es el índice del vector de semáforos, es decir, el semáforo a utilizar en esta operación.
ƒ cmd representa el tipo de operación de control a realizar (por ejemplo: SETVAL).
ƒ El cuarto parámetro está relacionado con el tercero, se pasará un tipo de parámetro u otro
dependiendo de lo que se haya escogido en el tercer parámetro.
Si ocurre un error la llamada devolverá -1 y la variable global errno tomará el código del error
producido.
LLAMADA SEMOP
int semop ( int semid, struct sembuf *sops, unsigned nsops)
Esta llamada permite realizar modificar el contador del semáforo. En UNIX es posible añadir
cualquier valor al semáforo y no solamente 1 y -1 (funciones P() y V()). A semop se le pasa como
argumento:
ƒ semid que indica el identificador del semáforo
ƒ struct sembuf representa el vector de operaciones sobre semáforos.
ƒ nsops es el número de operaciones a realizar
3
La estructura struct sembuf está formada por:
ƒ sem_num. Índice del elemento del vector de semáforos sobre el que se desea realizar la operación.
ƒ sem_op. Cantidad a añadir al valor del semáforo (puede ser negativa, en nuestro caso ±1)
ƒ sem_flg. Modificadores para describir cómo se desea que se realice la operación (en nuestro caso
no es necesario ningún modificador, por lo que el valor de este campo será 0 para todas las
operaciones).
Si el campo sem_op de una operación es positivo, se incrementa el valor del semáforo. Asimismo, si
sem_op es negativo, se decrementa el valor del semáforo si el resultado no es negativo. En caso
contrario el proceso espera a que se dé esa circunstancia. Es decir, sem_op==1 produce una
operación V y sem_op==-1, una operación P.
La llamada semop devolverá el valor 0 si se ejecuta con éxito, o -1 si se produce un error, tomando
además la variable global errno el valor del código del error producido.
EJEMPLO DE USO
Como ejemplo vamos a crear una serie de funciones base para construir las operaciones P y V de los
semáforos.
Lo primero hay que definir las variables y declarar los prototipos de las funciones a utilizar.
/* Definimos las constantes utilizadas para identificar las operaciones P
y V */
#define OP_P ((short) -1)
#define OP_V ((short) 1)
/* variables globales */
int semid;
int max;
/* Prototipos de las funciones que podemos utilizar sobre los semaforos */
int creaSEM(int cuantosSem, char *queClave);
void iniSEM(int semID, ushort queSemaforo, int queValor);
void P(int semID, ushort queSemaforo);
void V(int semID, ushort queSemaforo);
void destruyeSEM(int semID);
Para utilizar las llamadas de semáforos hay que incluir los archivos de cabecera:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
4
Con la función creaSEM creamos el semáforo basándolo en la llave generada con la cadena
queClave y con la letra 'k'. Indicamos que lo cree con permisos de lectura y escritura para el
usuario y que si ya existía, simplemente devuelva el identificador. El número de semáforos a crear lo
indicamos con cuantosSEM.
int creaSEM(int cuantosSEM, char *queClave)
{
int idSEM;
key_t llave;
llave = ftok(queClave, 'k');
if ((idSEM=semget(llave, cuantosSEM, IPC_CREAT|0600))==-1)
{
perror("semget");
exit(-1);
}
}
return idSEM;
Con la función iniSEM inicializamos el valor del contador de semáforo queSemaforo
perteneciente al grupo de semáforos semID.
void iniSEM(int semID, ushort queSemaforo, int queValor)
{
semctl(semID, queSemaforo, SETVAL, queValor);
}
Ya podemos realizar las operaciones P y V del semáforo queSemaforo perteneciente al grupo de
semáforos semID.
void P(int semID, ushort queSemaforo)
{
struct sembuf operacion;
operacion.sem_num
=
queSemaforo;
/*
semáforo
sobre
el
que
trabajaremos */
operacion.sem_op = OP_P; /* operación a realizar: P */
operacion.sem_flg = 0; /* Siempre 0 */
semop(semID, &operacion, 1); /* Finalmente, ejecutamos la operación
*/
}
void V(int semID, ushort queSemaforo)
{
struct sembuf operacion;
operacion.sem_num
=
queSemaforo;
/*
semáforo
sobre
el
que
trabajaremos */
operacion.sem_op = OP_V; /* operación a realizar: V */
operacion.sem_flg = 0; /* Siempre 0 */
semop(semID, &operacion, 1); /* Finalmente, ejecutamos la operación
*/
}
Por último con la función destruyeSEM liberamos el semáforo de la memoria.
5
void destruyeSEM(int semID)
{
semctl(semID, 0, IPC_RMID, 0);
}
PROBLEMA DE LECTORES/ESCRITORES
Un problema clásico en el estudio de los Sistemas Operativos es aquél que implica la existencia de
procesos que han de acceder de forma concurrente a un recurso compartido, sobre el cual se realizan
dos operaciones distintas.
Cada proceso se clasifica dependiendo del tipo de operación que realiza:
ƒ Un proceso Lector se caracteriza porque accede al recurso de modo que no altera el contenido del
mismo
ƒ Un proceso Escritor accede al recuso crítico con la posibilidad de modificar su contenido.
En el sistema puede haber varios procesos Lectores accediendo concurrentemente al recuso sin
importar que se superpongan, puesto que nunca comprometerán su consistencia. Sin embargo, cada
Escritor ha de acceder de forma exclusiva al recurso, y no debe nunca entrelazarse con otro proceso.
El orden de atención de la lista de procesos dependerá de del tipo de gestión: prioridad lectores,
prioridad escritores y prioridad FIFO.
REFERENCIAS
BIBLIOGRAFÍA
ƒ F. M. Márquez: UNIX. Programación avanzada, Rama, 1996.
ƒ K. A. Robbins y S. Robbins: UNIX. Programación práctica, Prentice may, 1997.
ƒ F. Maciá Pérez y A. Soriano Payá: El Shell Korn. Manual de Usuario y Programador, Servicio de
Publicaciones de la Universidad de Alicante, 1999.
ƒ J. Tackett y D. Gunter: Linux 4ª Edición, Prentice may, 2000.
WEB
Existe una gran cantidad de manuales y tutoriales de Unix y Linux en Internet que puedes localizar a
partir de cualquier buscador (ej. www.google.com).
6
AUTOEVALUACIÓN
1.
El programa realizado en la práctica anterior incrementaba una variable compartida por dos
procesos sin ninguna restricción (qué proceso comienza, dependiente del núcleo, etc.). Teniendo
en cuenta que los semáforos permiten sincronizar procesos, realizar un programa llamado
sumaalt.c que cree un proceso hijo. Tanto el padre como el hijo alternadamente tienen que
ir sumando un valor a una variable compartida hasta el máximo especificado. Además, el
proceso que empieza se indicará en la línea de comandos. (2.5 puntos)
sumaalt inicio_variable incr_padre incr_hijo maximo_variable padre|hijo
$ sumaalt 0 1 2 15 padre
Padre(345): variable=1
Hijo(349): variable=3
Padre(345): variable=4
Hijo(349): variable=6
Padre(345): variable=7
Hijo(349): variable=9
Padre(345): variable=10
Hijo(349): variable=12
Padre(345): variable=13
Hijo(349): variable=15
$
2.
Realizar un programa que resuelva el problema de lectores/escritores con priopidad FIFO
(lefifo.c) que tenga como argumento un nombre de archivo en el que figuren los datos
relativos a cada proceso en cuanto a tipo de proceso, tiempo de inicio y duración en la sección
crítica. El programa leerá el archivo y creará los procesos.
Los procesos irán mostrando en pantalla una traza de su ejecución, es decir, cada vez que
realicen una P(), una V(), entren o salgan de la sección crítica mostrarán un mensaje con el
tiempo, su identificador y su acción. El tipo de proceso L indicará que en un lector mientras que
el tipo E indicará que es un escritor.
Cada línea del archivo contiene el tipo de proceso, el retardo de comienzo y el tiempo dentro de
la sección crítica. (2.5 puntos)
Ejemplo:
$ lefifo traza.txt
Tiempo
Tipo Acción
20:21:01
L
P(MUTEX)
20:21:01
L
Entra_Sección
20:21:06
L
Sale_Sección
20:21:07
L
V(MUTEX)
..............
..............
$ cat traza.txt
L
1
5
L
8
6
E
12
3
L
13
2
$
7
3.
Realizar un programa que resuelva el problema de lectores/escritores con prioridad lectores
(lelect.c) teniendo en cuenta las consideraciones anteriores. (2.5 puntos)
4.
Realizar un programa que resuelva el problema de lectores/escritores con prioridad escritores
(leescr.c) teniendo en cuenta las consideraciones anteriores. (2.5 puntos)
FECHA DE ENTREGA:
Semana del 30 de mayo en la sesión de prácticas
8
Descargar