Semaforos y Memoria Compartida en UNIX

13
Semáforos y Memoria Compartida en UNIX. Semáforos UNIX System V. Los semáforos que proporciona Unix son más potentes que los formulados originalmente por Dijkstra. Como inconveniente, tienen su más compleja manipulación. Con una única llamada al sistema se pueden crear varios semáforos (un array) con un único identificador, y con otra función se ejecutan primitivas sobre todos los semáforos con un mismo identificador. Los semáforos con un mismo identificador se distinguen entre sí por el índice (posición). En todo programa C para UNIX que haga uso de operaciones con semáforos debe incluirse los siguientes ficheros de cabecera: #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> La creación e inicialización de un semáforo se puede llevar a cabo con la siguiente función: inicia (valor) int valor; { int semval; int id; union semun { int val; struct semid_ds *buf; ushort *array; } arg; if ((id=semget(IPC_PRIVATE, 1, (IPC_CREAT|0666))) == -1) { perror("Error al crear el semáforo."); return(-1); } arg.val = valor; if (semctl(id, 0, SETVAL, arg) == -1) { perror("Error al inicializar el semáforo."); return (-1); /*error en inicializacion*/ } return(id); } Las operaciones elementales sobre semáforos (esperar y señalar) se pueden implantar con el siguiente código: /*Rutina P */ P (semaforo) int semaforo; { if ( semcall(semaforo, -1) == -1 ) perror("Error en operación P."); } /*Rutina V */

Transcript of Semaforos y Memoria Compartida en UNIX

Page 1: Semaforos y Memoria Compartida en UNIX

Semáforos y Memoria Compartida en UNIX.

Semáforos UNIX System V.

Los semáforos que proporciona Unix son más potentes que los formulados originalmente por Dijkstra. Como inconveniente,tienen su más compleja manipulación. Con una única llamada al sistema se pueden crear varios semáforos (un array) con unúnico identificador, y con otra función se ejecutan primitivas sobre todos los semáforos con un mismo identificador. Lossemáforos con un mismo identificador se distinguen entre sí por el índice (posición).

En todo programa C para UNIX que haga uso de operaciones con semáforos debe incluirse los siguientes ficheros de cabecera:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

La creación e inicialización de un semáforo se puede llevar a cabo con la siguiente función:

inicia (valor) int valor; { int semval; int id; union semun { int val; struct semid_ds *buf; ushort *array; } arg;

if ((id=semget(IPC_PRIVATE, 1, (IPC_CREAT|0666))) == -1) { perror("Error al crear el semáforo."); return(-1); }

arg.val = valor; if (semctl(id, 0, SETVAL, arg) == -1) { perror("Error al inicializar el semáforo."); return (-1); /*error en inicializacion*/ }

return(id); }

Las operaciones elementales sobre semáforos (esperar y señalar) se pueden implantar con el siguiente código:

/*Rutina P */

P (semaforo) int semaforo; { if ( semcall(semaforo, -1) == -1 ) perror("Error en operación P."); }

/*Rutina V */

Page 2: Semaforos y Memoria Compartida en UNIX

V (semaforo) int semaforo; { if ( semcall(semaforo, 1) == -1 ) perror("Error en operación V."); }

semcall (semaforo, operacion) int semaforo, operacion; { struct sembuf sb; sb.sem_num = 0; sb.sem_op = operacion; sb.sem_flg = 0;

return ( semop(semaforo, &sb, 1) ); /*devuelve -1 si error */ }

Todo recurso de un sistema informático que ya no se va a necesitar ha de ser liberado. Si se trata de semáforos UNIX unaposibilidad para hacer esto es:

borra_s (semaforo) int semaforo; {

if ( semctl(semaforo, 0, IPC_RMID, 0) == -1) { perror("Error al eliminar el semáforo."); return(-1); }

}

Ejemplo de utilización de semáforos para exclusión mutua.

En este ejemplo, dos procesos concurrentes compiten por el uso del recurso 'stdout'. Cada proceso escribe cadenas de 80caracteres, uno de '+', y el otro de '-'. Si el acceso al recurso no es ordenado, las salidas se entremezclarán debido a que elcuantum de los procesos se agotará de forma no determinista. Provocamos de forma deliberada el agotamiento de estequantum utilizando la función retardo(). El código para la función main(), la función retardo() y los dos procesos concurrentes esel indicado a continuación:

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/time.h> #include <unistd.h> #include <errno.h>

#define ESPERA 1000 // Son los microsegundos de espera usados para asegurar // la finalización del quantum.

/********************************************************************** * * PROBAR EL SISTEMA CON Y SIN LAS OPERACIONES P Y V * **********************************************************************/

Page 3: Semaforos y Memoria Compartida en UNIX

**********************************************************************/

main() { int pid; /* identifica el proceso hijo */ int mutex; /* semaforo binario */

mutex=inicia(1);

if (0==(pid=fork())) proceso_hijo(mutex); else proceso_padre(mutex);

borra_s(mutex); }

/********************************************************************** * * Tanto el proceso hijo como el padre escriben 30 secuencias de 80 caracteres * **********************************************************************/

proceso_hijo(critica) int critica; { /* escribe 30 ristras de 80 caracteres '+' */ int i,j;

for (i=0;i< 30; i++) { P(critica); for (j=0; j<80; j++) { printf("+"); fflush(stdout); // Provocamos la finalización del quantum de tiempo retardo (); } printf("\n"); V(critica); }

exit(); }

proceso_padre(critica) int critica; { /* escribe 30 ristras de 80 caracteres '-' */ int i,j;

for (i=0;i< 30; i++) { P(critica); for (j=0; j<80; j++) { printf("-"); fflush (stdout); // Provocamos la finalización del quantum de tiempo retardo (); }

printf("\n");

Page 4: Semaforos y Memoria Compartida en UNIX

printf("\n"); V(critica); } wait(0); /* espera a que finalice el hijo */ }

/********************************************************************** * * Provocamos la espera durante ESPERA microsegundos * **********************************************************************/ retardo() {

struct timeval tiempo; struct timezone tz; unsigned long inicio, ahora;

gettimeofday(&tiempo, &tz); ahora = inicio = tiempo.tv_sec * 1000000 + tiempo.tv_usec;

// ESPERA microsegs while (ahora < inicio + ESPERA) { gettimeofday(&tiempo, &tz); ahora = tiempo.tv_sec * 1000000 + tiempo.tv_usec; }

}

/*********************************************************************** * * Rutinas de manejo de semáforos * ***********************************************************************/

inicia(valor) int valor; { int semval; int id; union semun { int val; struct semid_ds *buf; ushort *array; } arg;

if ((id=semget(IPC_PRIVATE, 1, (IPC_CREAT|0666))) == -1) { perror("Error al crear el semáforo."); return(-1); }

arg.val = valor; if (semctl(id, 0, SETVAL, arg) == -1) { perror("Error al inicializar el semáforo."); return (-1); /*error en inicializacion*/ } return(id); }

/*Rutina P */

P(semaforo)

Page 5: Semaforos y Memoria Compartida en UNIX

P(semaforo) int semaforo; { if ( semcall(semaforo, -1) == -1 ) perror("Error en operación P."); }

/*Rutina V */

V(semaforo) int semaforo; { if ( semcall(semaforo, 1) == -1 ) perror("Error en operación V."); }

semcall(semaforo, operacion) int semaforo, operacion; { struct sembuf sb; sb.sem_num = 0; sb.sem_op = operacion; sb.sem_flg = 0;

return ( semop(semaforo, &sb, 1) ); /*devuelve -1 si error */ }

borra_s(semaforo) int semaforo; {

if ( semctl(semaforo, 0, IPC_RMID, 0) == -1) { perror("Error al eliminar el semáforo."); return(-1); }

}

Descargar el código fuente de este ejemplo.

Para compilar el anterior programa:

gcc -o critica critica.c

Preste atención a la función fflush (). Esta función obliga a escribir los datos en los ficheros abiertos sin necesidad de esperar aque el sistema operativo lo haga cuando él estime oportuno. Es una función muy útil para la depuración de programas.

Problema 3

Crear una biblioteca de funciones llamada libsem.a que contenga todas las funciones relacionadas con semáforos que se

han descrito más arriba.

Codificar el ejemplo anterior y comprobar su comportamiento según se utilicen o no las primitivas de entrada y salida desección crítica.

Page 6: Semaforos y Memoria Compartida en UNIX

Problema 4

Completar el problema 2 sincronizando los 4 procesos mediante semáforos de manera que escriban, en estricto turno, una líneacada uno. Asegurar que cada proceso escribe la línea completa, o no escribe nada. Un ejemplo de la salida es:

000 001 002 003 004 005 006 007 008 009 010 011 ...

Semáforos POSIX

Utilizaremos dos tipos de semáforos POSIX: los semáforos binarios y los semáforos generales.

Semáforos POSIX binarios

Los semáforos binarios sólo pueden estar en uno de dos estados posibles. Se declaran como variables de tipo pthread_mutex_ty las funciones que los manejan son:

pthread_mutex_lock() Operación Ppthread_mutex_destroy()Libera el semáforopthread_mutex_unlock() Operación Vpthread_mutex_init() Inicialización

Si son declarados como variables externas, se pueden inicializar de forma estática sin necesidad de invocar apthread_mutex_init(). Por ejemplo:

// // Semaforo binario pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER;

Como las funciones lock() y unlock() modifican el semáforo recibirán como argumento de entrada su dirección; por ejemplo:

pthread_mutex_lock(&buffer_lock); *itemp = buffer[bufout]; bufout = (bufout + 1) % TAMBUF; pthread_mutex_unlock(&buffer_lock);

La función pthread_mutex_destroy() libera la memoria asociada a dicho semáforo. Normalmente no existe tal estructura dememoria y en la mayor parte de los sistemas esta primitiva no hace nada. Puede obviarse.

Semáforos POSIX genéricos

Se trata de semáforos del mismo tipo que los explicados para System V, aunque diseñados para sincronizar hilos. El estándar

Page 7: Semaforos y Memoria Compartida en UNIX

Se trata de semáforos del mismo tipo que los explicados para System V, aunque diseñados para sincronizar hilos. El estándartiene en cuenta la posibilidad de su utilización para la sincronización de procesos (además de hilos) pero esta posibilidad noestá soportada en todas las implementaciones y por ello, en esta asignatura, sólo serán utilizados entre hilos de un mismoproceso.

Se declaran como variables de tipo sem_t. Se manejan con las siguientes funciones:

int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_wait(sem_t * sem); int sem_post(sem_t * sem); int sem_destroy(sem_t * sem);

La función de inicialización recibe como segundo argumento un valor indicativo de si el semáforo sincronizará hilos del mismo odiferente proceso. Por defecto se pone un valor 0 a dicho valor ya que sólo serán usados dentro del mismo proceso.Análogamente al semáforo binario, la función sem_destroy() puede obviarse.

Veamos cómo se implementa un sistema productor-consumidor con búfer circular utilizando hilos, semáforos POSIXbinarios y semáforos POSIX genéricos:

#include <stdio.h> #include <string.h> #include <errno.h> #include <pthread.h> #include <semaphore.h>

#define TAMBUF 8 // Tamaño del búfer circular #define NUMDATOS 100 // Número de datos a enviar

// // El buffer circular y los correspondientes punteros int buffer[TAMBUF]; int bufin = 0; int bufout = 0;

// // Semaforo binario pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER;

// // Variable suma unsigned long sum = 0;

// // Semaforos generales sem_t hay_datos; sem_t hay_sitio;

// // Funciones de escritura y lectura del buffer circular void obten_dato(int *itemp) { pthread_mutex_lock(&buffer_lock); *itemp = buffer[bufout]; bufout = (bufout + 1) % TAMBUF; pthread_mutex_unlock(&buffer_lock);

return;

}

Page 8: Semaforos y Memoria Compartida en UNIX

}

void pon_dato(int item) { pthread_mutex_lock(&buffer_lock); buffer[bufin] = item; bufin = (bufin + 1) % TAMBUF; pthread_mutex_unlock(&buffer_lock); return; }

// // Funciones productor-consumidor void *productor(void *arg1) { int i;

for (i = 1; i <= NUMDATOS; i++) { sem_wait(&hay_sitio); pon_dato(i*i); sem_post(&hay_datos); }

pthread_exit( NULL ); }

void *consumidor(void *arg2) { int i, midato;

for (i = 1; i<= NUMDATOS; i++) { sem_wait(&hay_datos); obten_dato(&midato); sem_post(&hay_sitio); sum += midato; }

pthread_exit( NULL ); }

// // Funcion principal main() {

pthread_t tidprod, tidcons; unsigned long i, total;

total = 0;

for (i = 1; i <= NUMDATOS; i++) total += i*i;

printf("El resultado deberia ser %u\n", total);

// // Inicializacion de semaforos

sem_init(&hay_datos, 0, 0);

Page 9: Semaforos y Memoria Compartida en UNIX

sem_init(&hay_datos, 0, 0); sem_init(&hay_sitio, 0, TAMBUF);

// // Se crean los hilos pthread_create(&tidprod, NULL, productor, NULL); pthread_create(&tidcons, NULL, consumidor, NULL);

// // Se espera a que los hilos terminen pthread_join(tidprod, NULL); pthread_join(tidcons, NULL);

printf("Los hilos produjeron el valor %u\n", sum);

}

Descargar el codigo fuente de este programa.

Para compilar programas que utilizan hilos, semáforos binarios y semáforos genéricos en Solaris se debe utilizar la siguientelínea:

gcc -o buffer-circular-hilos buffer-circular-hilos.c -lpthread -lrt

Si se desea compilar en Linux (siempre que soporte hilos POSIX):

gcc -o buffer-circular-hilos buffer-circular-hilos.c -lpthread

Memoria compartida.

Al crear un proceso nuevo con fork() se crea una copia exacta de la imagen del proceso, es decir, de su bloque de control, deltexto, de los datos y de la pila. En concreto al copiarse la zona de datos y de pila se copian tanto las variables locales oautomáticas como las externas. Sin embargo dichas variables, aún teniendo el mismo nombre, son locales a cada proceso yno se pueden compartir. Para compartir memoria entre procesos hay que solicitarlo al sistema operativo.

Para utilizar la memoria compartida se siguen 3 pasos:

1. Obtención de un fragmento de memoria compartida con su identificador en el sistema (entero). Se hace con la funciónshmget().

2. Localizar dicha zona a través de un puntero a un determinado tipo de datos. Se usa la función shmat().3. Eliminación de la memoria compartida. Se usan las funciones shmdt() y shmctl().

La obtención y eliminación de la memoria se hará una sola vez. Apuntar a esa zona tienen que hacerlo todos los procesos quevayan a acceder a ella. Para conseguir esto, hay varios modos: pasar el identificador del fragmento de memoria obtenido, o bienpasar directamente el puntero (dirección de memoria donde comienza el fragmento compartido).

Para las llamadas anteriores hay que incluir los ficheros de cabecera:

#include <sys/types> #include <sys/ipc.h> #include <sys/shm.h>

Ejemplo de uso de memoria compartida

Dos procesos que incrementan una zona de memoria común (entero).

Page 10: Semaforos y Memoria Compartida en UNIX

Dos procesos que incrementan una zona de memoria común (entero).

#include <stdio.h> #include <sys/time.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <errno.h>

#define ESPERA 1000 // Son los microsegundos de espera usados para asegurar // la finalización del quantum. #define NINCR 400 // Es el número de veces que cada proceso incrementa // la variable compartida.

/********************************************************************** * * PROBAR ESTE PROGRAMA CON Y SIN LAS OPERACIONES P Y V * **********************************************************************/

main() { int pid; int memoria; int *dato; /*para acceder*/ int mutex;

// Inicialización del semáforo binario mutex = inicia(1);

// Solicitamos memoria al sistema operativo if ((memoria=shmget(IPC_PRIVATE, sizeof(int) * 1, 0660)) ==-1) { perror ("Error en acceso a memoria compartida de sistema."); exit(-1); } // Obtenemos el puntero a dicha memoria dato = (int*) shmat(memoria, (char*) 0, 0);

// Inicializamos la posición compartida *dato = 0; printf("memoria:%d &dato:%x dato:%d\n",memoria,dato,*dato);

// Creación de procesos concurrentes if (0 == (pid=fork())) proceso_hijo(dato,mutex); else proceso_padre(dato,mutex);

// A este punto se llega después de que hijo y padre hayan acabado shmdt((char *)dato); shmctl(memoria, IPC_RMID, 0); /* destruye la memoria */ borra_s(mutex); /* destruye el semaforo */ }

proceso_hijo(dato,mutex)

Page 11: Semaforos y Memoria Compartida en UNIX

proceso_hijo(dato,mutex) int *dato; int mutex; { int i, copia;

// Impresión de algunos datos antes de empezar printf("HIJO:&dato:%x dato:%d\n",dato,*dato);

// Bucle de acceso a la variable compartida for (i=0; i< NINCR; i++) { P(mutex); // Comienzo sección crítica

copia = *dato; copia +=1; // Esperamos un tiempo para forzar la finalización del quantum retardo(); // Actualizamos la memoria compartida *dato = copia;

// Fin sección crítica V(mutex); }

exit(); }

proceso_padre(dato, mutex) int *dato; int mutex; { int i, copia;

// Impresión de algunos datos antes de empezar printf("PADRE:&dato:%x dato:%d\n",dato,*dato);

// Bucle de acceso a la variable compartida for (i=0; i< NINCR; i++) { P(mutex); // Comienzo sección crítica

copia = *dato; copia +=1; // Esperamos un tiempo para forzar la finalización del quantum retardo(); // Actualizamos la memoria compartida *dato = copia;

// Fin sección crítica V(mutex); }

wait(0); /* espera finalizacion de hijo */

printf("Proceso padre. El hijo ya terminó. Valor final %d.\n", *dato); printf("Debería valer %d.\n", 2*NINCR);

}

/**********************************************************************

Page 12: Semaforos y Memoria Compartida en UNIX

/********************************************************************** * * * * Provocamos la espera durante ESPERA microsegundos * * ***********************************************************************/ retardo() {

struct timeval tiempo; struct timezone tz; unsigned long inicio, ahora;

gettimeofday(&tiempo, &tz); ahora = inicio = tiempo.tv_sec * 1000000 + tiempo.tv_usec;

// ESPERA microsegs while (ahora < inicio + ESPERA) { gettimeofday(&tiempo, &tz); ahora = tiempo.tv_sec * 1000000 + tiempo.tv_usec; }

}

Descargar el código de este programa (memoria.c). (están incluidas todas las funciones de uso de semáforos, es decir, está listo para compilar con "gcc -o memoria memoria.c")

Problema 5

Dos procesos concurrentes P1 y P2 se comunican por medio de un buffer circular con capacidad para 10 datos. El proceso P1prodduce números enteros positivos del 1 al 10000 que va depositando en el buffer (si tiene espacio para ello). El proceso P2lee los números (si los hay), los cambia de signo y los presenta por salida estándar, eliminándolos del buffer. Implantar estesistema productor-consumidor en un programa C para UNIX.

Recursos del Sistema Operativo

Tanto la memoria compartida como los semáforos (System V) son recursos limitados gestionados por el sistema operativo;éste les asigna un identificador en su creación. Puede ver la lista de los semáforos y segmentos de memoria compartidaexistentes mediante el comando ipcs.

No olvide programar sus procesos de forma queliberen todos los recursos solicitados yconcedidos cuando ya no los necesite.

En caso de que los procesos fracasen en dicha liberación, utilice el comando ipcrm.

Cuando el número de recursos utilizados excede un cierto límite, el sistema operativo deniega todas las peticiones posterioressobre dichos recursos, provocando funcionamientos erróneos de procesos correctos.

Page 13: Semaforos y Memoria Compartida en UNIX