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) {
arg.val = valor;
return(id);
|
Las operaciones elementales sobre semáforos (esperar y señalar)
se pueden implantar con el siguiente código:
| /*Rutina P */
P (semaforo)
/*Rutina V */ V (semaforo)
semcall (semaforo, operacion)
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 una posibilidad
para hacer esto es:
| borra_s (semaforo)
int semaforo; { if ( semctl(semaforo, 0, IPC_RMID,
0) == -1) {
} |
| #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
/**********************************************************************
main()
mutex=inicia(1); if (0==(pid=fork()))
borra_s(mutex);
/**********************************************************************
proceso_hijo(critica)
for (i=0;i< 30; i++) {
exit();
proceso_padre(critica)
for (i=0;i< 30; i++) {
/**********************************************************************
struct timeval tiempo;
gettimeofday(&tiempo, &tz);
// ESPERA microsegs
} /***********************************************************************
inicia(valor)
if ((id=semget(IPC_PRIVATE, 1, (IPC_CREAT|0666))) == -1) {
arg.val = valor;
/*Rutina P */ P(semaforo)
/*Rutina V */ V(semaforo)
semcall(semaforo, operacion)
return ( semop(semaforo, &sb, 1) ); /*devuelve -1 si error
*/
borra_s(semaforo)
if ( semctl(semaforo, 0, IPC_RMID, 0) == -1) {
} |
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
a que el sistema operativo lo haga cuando él estime oportuno. Es
una función muy útil para la depuración de programas.
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 de sección crítica.
Completar el problema 2 sincronizando los 4 procesos mediante
semáforos de manera que escriban, en estricto turno, una línea
cada 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 ... |
| pthread_mutex_lock() | Operación P |
| pthread_mutex_destroy() | Libera el semáforo |
| pthread_mutex_unlock() | Operación V |
| pthread_mutex_init() | Inicialización |
Si son declarados como variables externas, se pueden inicializar de
forma estática 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 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 de memoria y en la mayor parte de los sistemas esta primitiva no hace nada. Puede obviarse.
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 o diferente 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 POSIX
binarios 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
//
//
//
//
//
return;
void pon_dato(int item)
//
for (i = 1; i <= NUMDATOS; i++) {
pthread_exit( NULL );
void *consumidor(void *arg2)
for (i = 1; i<= NUMDATOS; i++) {
pthread_exit( NULL );
//
pthread_t tidprod, tidcons;
total = 0; for (i = 1; i <= NUMDATOS; i++)
printf("El resultado deberia ser %u\n", total); //
//
//
printf("Los hilos produjeron
el valor %u\n", sum);
} |
Para compilar programas que utilizan hilos, semáforos binarios
y semáforos genéricos en Solaris se debe utilizar la siguiente
lí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 |
Para utilizar la memoria compartida se siguen 3 pasos:
La obtención y eliminación 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 (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> |
| #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
/**********************************************************************
main()
// Inicialización del
semáforo binario
// Solicitamos memoria al
sistema operativo
// Inicializamos la posición
compartida
// Creación de procesos
concurrentes
// A este punto se llega después
de que hijo y padre hayan acabado
proceso_hijo(dato,mutex)
// Impresión de algunos
datos antes de empezar
// Bucle de acceso a la variable
compartida
copia = *dato;
// Fin sección crítica
exit();
proceso_padre(dato, mutex)
// Impresión de algunos
datos antes de empezar
// Bucle de acceso a la variable
compartida
copia = *dato;
// Fin sección
crítica
wait(0); /* espera finalizacion de hijo */ printf("Proceso padre. El
hijo ya terminó. Valor final %d.\n", *dato);
}
/**********************************************************************
struct timeval tiempo;
gettimeofday(&tiempo,
&tz);
// ESPERA microsegs
}
Descargar el código de este programa (memoria.c).
|
Dos procesos concurrentes P1 y P2 se comunican por medio de un buffer circular con capacidad para 10 datos. El proceso P1 prodduce números enteros positivos del 1 al 10000 que va depositando en el buffer (si tiene espacio para ello). El proceso P2 lee los números (si los hay), los cambia de signo y los presenta por salida estándar, eliminándolos del buffer. Implantar este sistema productor-consumidor en un programa C para UNIX.
|
|
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 posteriores
sobre dichos recursos, provocando funcionamientos erróneos de procesos
correctos.