Vous êtes sur la page 1sur 13

Semforos y Memoria Compartida en UNIX.

Semforos UNIX System V.


Los semforos que proporciona Unix son ms potentes que los formulados originalmente por Dijkstra. Como inconveniente, tienen su ms compleja manipulacin. Con una nica llamada al sistema se pueden crear varios semforos (un array) con un nico identificador, y con otra funcin se ejecutan primitivas sobre todos los semforos con un mismo identificador. Los semforos con un mismo identificador se distinguen entre s por el ndice (posicin). En todo programa C para UNIX que haga uso de operaciones con semforos debe incluirse los siguientes ficheros de cabecera:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> La creacin e inicializacin de un semforo se puede llevar a cabo con la siguiente funcin:

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 semforo."); return(-1); } arg.val = valor; if (semctl(id, 0, SETVAL, arg) == -1) { perror("Error al inicializar el semforo."); return (-1); /*error en inicializacion*/ } return(id); } Las operaciones elementales sobre semforos (esperar y sealar) se pueden implantar con el siguiente cdigo:

/*Rutina P */ P (semaforo) int semaforo; { if ( semcall(semaforo, -1) == -1 ) perror("Error en operacin P."); }

/*Rutina V */

V (semaforo) int semaforo; { if ( semcall(semaforo, 1) == -1 ) perror("Error en operacin 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 informtico que ya no se va a necesitar ha de ser liberado. Si se trata de semforos UNIX una posibilidad para hacer esto es:

borra_s (semaforo) int semaforo; { if ( semctl(semaforo, 0, IPC_RMID, 0) == -1) { perror("Error al eliminar el semforo."); return(-1); } }

Ejemplo de utilizacin de semforos para exclusin mutua.


En este ejemplo, dos procesos concurrentes compiten por el uso del recurso 'stdout'. Cada proceso escribe cadenas de 80 caracteres, uno de '+', y el otro de '-'. Si el acceso al recurso no es ordenado, las salidas se entremezclarn debido a que el cuantum de los procesos se agotar de forma no determinista. Provocamos de forma deliberada el agotamiento de este quantum utilizando la funcin retardo(). El cdigo para la funcin main(), la funcin retardo() y los dos procesos concurrentes es el indicado a continuacin:

#include #include #include #include #include #include #include

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

#define ESPERA 1000 // Son los microsegundos de espera usados para asegurar // la finalizacin del quantum. /********************************************************************** * * PROBAR EL SISTEMA CON Y SIN LAS OPERACIONES P Y V * **********************************************************************/

**********************************************************************/ 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 finalizacin 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 finalizacin del quantum de tiempo retardo (); } printf("\n");

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 semforos * ***********************************************************************/ 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 semforo."); return(-1); } arg.val = valor; if (semctl(id, 0, SETVAL, arg) == -1) { perror("Error al inicializar el semforo."); return (-1); /*error en inicializacion*/ } return(id); } /*Rutina P */ P(semaforo)

P(semaforo) int semaforo; { if ( semcall(semaforo, -1) == -1 ) perror("Error en operacin P."); } /*Rutina V */ V(semaforo) int semaforo; { if ( semcall(semaforo, 1) == -1 ) perror("Error en operacin 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 semforo."); return(-1); } } Descargar el cdigo fuente de este ejemplo. Para compilar el anterior programa:

gcc -o critica critica.c Preste atencin a la funcin fflush (). Esta funcin obliga a escribir los datos en los ficheros abiertos sin necesidad de esperar a que el sistema operativo lo haga cuando l estime oportuno. Es una funcin muy til para la depuracin de programas.

Problema 3
Crear una biblioteca de funciones llamada libsem.a que contenga todas las funciones relacionadas con semforos que se han descrito ms arriba. Codificar el ejemplo anterior y comprobar su comportamiento segn se utilicen o no las primitivas de entrada y salida de seccin crtica.

Problema 4
Completar el problema 2 sincronizando los 4 procesos mediante semforos de manera que escriban, en estricto turno, una lnea cada uno. Asegurar que cada proceso escribe la lnea completa, o no escribe nada. Un ejemplo de la salida es:

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

Semforos POSIX
Utilizaremos dos tipos de semforos POSIX: los semforos binarios y los semforos generales.

Semforos POSIX binarios


Los semforos binarios slo pueden estar en uno de dos estados posibles. Se declaran como variables de tipo pthread_mutex_t y las funciones que los manejan son: pthread_mutex_lock() Operacin P pthread_mutex_destroy()Libera el semforo pthread_mutex_unlock() Operacin V pthread_mutex_init() Inicializacin Si son declarados como variables externas, se pueden inicializar de forma esttica sin necesidad de invocar a pthread_mutex_init(). Por ejemplo:

// // Semaforo binario pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER; Como las funciones lock () y unlock () modifican el semforo recibirn como argumento de entrada su direccin; por ejemplo:

pthread_mutex_lock(&buffer_lock); *itemp = buffer[bufout]; bufout = (bufout + 1) % TAMBUF; pthread_mutex_unlock(&buffer_lock); La funcin pthread_mutex_destroy() libera la memoria asociada a dicho semforo. Normalmente no existe tal estructura de memoria y en la mayor parte de los sistemas esta primitiva no hace nada. Puede obviarse.

Semforos POSIX genricos


Se trata de semforos del mismo tipo que los explicados para System V, aunque diseados para sincronizar hilos. El estndar

Se trata de semforos del mismo tipo que los explicados para System V, aunque diseados para sincronizar hilos. El estndar tiene en cuenta la posibilidad de su utilizacin para la sincronizacin de procesos (adems de hilos) pero esta posibilidad no est soportada en todas las implementaciones y por ello, en esta asignatura, slo sern utilizados entre hilos de un mismo proceso. Se declaran como variables de tipo sem_t. Se manejan con las siguientes funciones:

int int int int

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

La funcin de inicializacin recibe como segundo argumento un valor indicativo de si el semforo sincronizar hilos del mismo o diferente proceso. Por defecto se pone un valor 0 a dicho valor ya que slo sern usados dentro del mismo proceso. Anlogamente al semforo binario, la funcin sem_destroy() puede obviarse. Veamos cmo se implementa un sistema productor-consumidor con bfer circular utilizando hilos, semforos POSIX binarios y semforos POSIX genricos:

#include #include #include #include #include

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

#define TAMBUF 8 // Tamao del bfer circular #define NUMDATOS 100 // Nmero 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; }

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

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, semforos binarios y semforos genricos en Solaris se debe utilizar la siguiente lnea:

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, del texto, de los datos y de la pila. En concreto al copiarse la zona de datos y de pila se copian tanto las variables locales o automticas como las externas. Sin embargo dichas variables, an teniendo el mismo nombre, son locales a cada proceso y no se pueden compartir. Para compartir memoria entre procesos hay que solicitarlo al sistema operativo. Para utilizar la memoria compartida se siguen 3 pasos: 1. Obtencin de un fragmento de memoria compartida con su identificador en el sistema (entero). Se hace con la funcin shmget(). 2. Localizar dicha zona a travs de un puntero a un determinado tipo de datos. Se usa la funcin shmat(). 3. Eliminacin de la memoria compartida. Se usan las funciones shmdt() y shmctl(). La obtencin y eliminacin de la memoria se har una sola vez. Apuntar a esa zona tienen que hacerlo todos los procesos que vayan a acceder a ella. Para conseguir esto, hay varios modos: pasar el identificador del fragmento de memoria obtenido, o bien pasar directamente el puntero (direccin 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 comn (entero).

Dos procesos que incrementan una zona de memoria comn (entero).

#include #include #include #include #include #include #include #include

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

#define ESPERA 1000 // Son los microsegundos de espera usados para asegurar // la finalizacin del quantum. #define NINCR 400 // Es el nmero 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; // Inicializacin del semforo 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 posicin compartida *dato = 0; printf("memoria:%d &dato:%x dato:%d\n",memoria,dato,*dato); // Creacin de procesos concurrentes if (0 == (pid=fork())) proceso_hijo(dato,mutex); else proceso_padre(dato,mutex);

// A este punto se llega despus 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)

proceso_hijo(dato,mutex) int *dato; int mutex; { int i, copia; // Impresin 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 seccin crtica copia = *dato; copia +=1; // Esperamos un tiempo para forzar la finalizacin del quantum retardo(); // Actualizamos la memoria compartida *dato = copia; // Fin seccin crtica V(mutex); } exit(); } proceso_padre(dato, mutex) int *dato; int mutex; { int i, copia; // Impresin 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 seccin crtica copia = *dato; copia +=1; // Esperamos un tiempo para forzar la finalizacin del quantum retardo(); // Actualizamos la memoria compartida *dato = copia; // Fin seccin crtica V(mutex); } wait(0); /* espera finalizacion de hijo */ printf("Proceso padre. El hijo ya termin. Valor final %d.\n", *dato); printf("Debera valer %d.\n", 2*NINCR); } /**********************************************************************

/********************************************************************** * * * * 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 cdigo de este programa (memoria.c). (estn incluidas todas las funciones de uso de semforos, 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 P1 prodduce nmeros enteros positivos del 1 al 10000 que va depositando en el buffer (si tiene espacio para ello). El proceso P2 lee los nmeros (si los hay), los cambia de signo y los presenta por salida estndar, eliminndolos del buffer. Implantar este sistema productor-consumidor en un programa C para UNIX.

Recursos del Sistema Operativo


Tanto la memoria compartida como los semforos (System V) son recursos limitados gestionados por el sistema operativo; ste les asigna un identificador en su creacin. Puede ver la lista de los semforos y segmentos de memoria compartida existentes mediante el comando ipcs.

No olvide programar sus procesos de forma que liberen todos los recursos solicitados y concedidos cuando ya no los necesite.
En caso de que los procesos fracasen en dicha liberacin, utilice el comando ipcrm. Cuando el nmero de recursos utilizados excede un cierto lmite, el sistema operativo deniega todas las peticiones posteriores sobre dichos recursos, provocando funcionamientos errneos de procesos correctos.

Vous aimerez peut-être aussi