Con este interface de aplicación se trata de evitar alguno de los problemas de que adolecen el nivel de aplicación, tal y como lo presenta el sistema operativo. Las funciones correspondientes se encuentran en el fichero fuente semaph.c. En el siguiente ejemplo se muestra su utilización sobre una variable definida en memoria compartida (para ver el empleo de memoria compartida véase la práctica anterior: Algoritmos con espera activa).
#include "rshmem.h" #include <sys/sem.h> incrementa(int *mem, int k) { int i; i=*mem; TP i=i+k; TP *mem=i; } int main() { key_t claveMutex; int mutex; /* semáforo */ int *recurso; char *marcaFin; /* obtener una clave cualquiera para el recurso ipc */ if ((key_t) -1 == (claveMutex = ftok("ej2", 's'))) { fprintf(stderr, "main: Error al crear la clave con ftok()\n"); exit(1); } /* crear del semaforo */ if (-1 == (mutex = semCreate(claveMutex, 1))){ fprintf(stderr, "main: No pude crear el semaforo\n"); exit(1); } /* crear zona de memoria compartida */ if (!crearMemoria()) fprintf(stderr, "error de crearMemoria\n"); recurso = (int *) memoria ; /* variable s.c. */ marcaFin = memoria + sizeof (int) ; *recurso = 0 ; *marcaFin = 'p' ; if (0!=fork()) { /* proceso padre */ int i; for (i=0; i< 1000; i++) { semWait(mutex); incrementa(recurso, -5); semSignal(mutex); } while (*marcaFin != 'x') ; /* espera al hijo */ printf("El recurso valia 0 y ahora vale %d\n", *recurso); if (!eliminarMemoria()) /* eliminar memoria compartida */ fprintf(stderr, "error de eliminarMemoria\n"); semClose(mutex); exit(0); } else { /* proceso hijo */ int i; if (-1 == (mutex = semOpen(claveMutex))) fprintf(stderr, "No tengo el cualificador de mutex\n"); for (i=0; i< 1000; i++) { semWait(mutex); incrementa(recurso, 5); semSignal(mutex); } /* termina */ semClose(mutex); *marcaFin = 'x'; exit(0); } } }(cargar fuente)
Para compilar el programa debemos escribir:
cc -o ejemplo.c rshmem.c semaph.c
A partir de este programa de ejemplo se puede implementar soluciones a diversos problemas de acceso concurrente, de los cuales algunos interesantes son:
Referencias: W. Richard Stevens "Advanced Programming in the Unix Environment" Addison-Wesley 1992
/* * Provee un interface mas sencillo de entender que las llamadas a sistema * de semaforos de System V. Hay 7 rutinas disponibles: * * id = semCreate(key, initval); # Crear con un valor inicial o abrir. * id = semOpen(key); # Abrir (debe existir ya) * semWait(id); # espera = P = down en 1 * semSignal(id); # senal = V = up en 1 * semOp(id, cantidad); # espera si (cantidad < 0) * # senal si (cantidad > 0) * semClose(id); # cierra * semRm(id); # destruye (borra) * * Se disegna un semaforo soportado por un conjunto de tres, dos de ellos * auxiliares. (Los semaforos se crean por arrays) * - El primer miembro, [0], es el valor real del semaforo. * - El segundo miembro, [1], es un contador utilizado para conocer * si todos los procesos han acabado con el semaforo. El contador * se inicializa con un numero grande (BIGCOUNT) y se decrementa cada * vez que se crea o abre, y se incrementa en cada cierre. * * De esta forma se puede "ajustar" la caracteristica de System V * de forma que se tenga en cuenta cualquier proceso que salga * sin llamar a semClose(). A pesar de ello, no ahuda mucho si el * ultimo proceso sale sin cerrar el semaforo, ya que no hay forma * de destruir el semaforo, pero puede ayudar si acaba (intencional * o no intencionalmente) cualquier otro proceso diferente del ultimo. * - El tercer miembro, [2], del conjunto de semaforos se utiliza para * bloquear las secciones criticas en semCreate() y semClose(). */ #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <errno.h> void semOp(int, int); int semCreate(key_t, int); int semOpen(key_t); void semRm(int); void semClose(int); void semWait(int); void semSignal(int); #define BIGCOUNT 10000 /* Valor inicial para el contador de procesos */ /* Define los arrays de operaciones del semaforo para llamadas a * semop(). */ static struct sembuf op_lock[2] = { 2, 0, 0, /* espera para [2] (bloqueo) sea igual 0 * despues incrementa [2] en 1 - esto lo bloquea */ 2, 1, SEM_UNDO /* UNDO para liberar el bloqueo si el proceso sale * antes de desbloquear explicitamente */ }; static struct sembuf op_endcreate[2] = { 1, -1, SEM_UNDO, /* decrementa [1] (contador de procesos) con undo en * caso de finalizar */ /* UNDO para ajustar el contador de procesos en caso de * acabar antes de llamar explicitamente a semClose() */ 2, -1, SEM_UNDO /* despues decrementa [2] (bloqueo) de vuelta a 0 */ }; static struct sembuf op_open[1] = { 1, -1, SEM_UNDO /* decrementa [1] (contador de proceso) con undo en * caso de finalizar */ }; static struct sembuf op_close[3] = { 2, 0, 0, /* espera hasta que [2] (bloqueo) sea igual a 0 */ 2, 1, SEM_UNDO, /* despues incrementa [2] en 1 - esto lo bloquea */ 1, 1, SEM_UNDO /* despues incrementa [1] (contador de procesos) */ }; static struct sembuf op_unlock[1] = { 2, -1, SEM_UNDO /* decrementa [2] (bloqueo) de vuelta a 0 */ }; static struct sembuf op_op[1] = { 0, 99, SEM_UNDO /* decrementa o incrementa [0] con undo en caso de * finalizar */ /* El 99 se substituye con la cantidad real que hay * que substraer (positiva o negativa) */ }; /**************************************************************************** * Crea un semaforo con un valor inicial especificado. * Si el semaforo existe, no se inicializa (por su puesto). * Se devuelve la identidad del semaforo si todo va bien, si no -1. */ int semCreate(key_t key, int initval) { register int id, semval; union semun { int val; struct semid_ds *buf; ushort *array; } semctl_arg; if (key == IPC_PRIVATE) return(-1); /* no utilizable para semaforos privados */ else if (key == (key_t) -1) return(-1); /* probablemente una llamada erronea anterior a ftok() */ deNuevo: if ( (id = semget(key, 3, 0666 | IPC_CREAT)) < 0) return(-1); /* problemas de permisos o tablas llenas */ /* Cuando se crea el semaforo, sabemos que el valor de todos los * miembros es 0. * * Bloquear el semaforo esperando a que [2] sea 0, e incrementarlo. * * Hay una condicion de carrera: Cabe la posibilidad de que entre el * semget() de arriba y el semop() de abajo, otro proceso pueda llamar * a semClose() que puede borrar el semaforo si el ultimo lo esta * usando. * * Ademas, se maneja la condicion de error sobre el identificador. * Si esto ocurre, se vuelve atras y se intenta crear de nuevo. */ if (semop(id, &op_lock[0], 2) < 0) { if (errno == EINVAL) goto deNuevo; fprintf(stderr, "semCreate: no puedo bloquear\n"); } /* Obtener el valor del contador de procesos. Si es igual a 0, * entonces ninguno ha inicializado el semaforo aun. */ if ( (semval = semctl(id, 1, GETVAL, 0)) < 0) fprintf(stderr, "semCreate: no puedo realizar GETVAL\n"); if (semval == 0) { /* Podriamos inicializar mediante SETALL, pero podria borrar el * ajuste del valor que se realizo cuando se bloqueo el semaforo antes. * En su lugar, se hacen dos llamadas al sistema para inicializar * [0] y [1]. */ semctl_arg.val = initval; if (semctl(id, 0, SETVAL, semctl_arg) < 0) fprintf(stderr, "semCreate: puedo SETVAL[0]\n"); semctl_arg.val = BIGCOUNT; if (semctl(id, 1, SETVAL, semctl_arg) < 0) fprintf(stderr, "semCreate: puedo SETVAL[1]\n"); } /* Decrementar el contador de procesos y desbloquear. */ if (semop(id, &op_endcreate[0], 2) < 0) fprintf(stderr, "semCreate: no puedo acabar semCreate()\n"); return(id); } /**************************************************************************** * Abre un semaforo que debe existir ya. * Esta funcion deberia de usarse, en vez de semCreate(), si en la llamada * se sabe que el semaforo deberia ya existir. Por ejemplo un cliente * de un par cliente-servidor podria utilizarla, si es responsabilidad del * servidor crear el semaforo. * Se vuelve la identidad del semaforo si va bien, si no -1. */ int semOpen(key_t key) { register int id; if (key == IPC_PRIVATE) return(-1); /* no utilizable para semaforos privados */ else if (key == (key_t) -1) return(-1); /* probablemente una llamada erronea anterior a ftok() */ if ( (id = semget(key, 3, 0)) < 0) return(-1); /* no existe o las tablas estan llenas */ /* Decrementa el contador de procesos. No necesitamos un bloqueo * para hacer esto. */ if (semop(id, &op_open[0], 1) < 0) fprintf(stderr, "semOpen: no puedo abrir\n"); return(id); } /**************************************************************************** * Borrar un semaforo. * Se supone que esta llamada se realiza desde un servidor en operaciones como * apagarServidor ... No importa si los otros procesos estan usandolo o no. * El resto de los procesos deberian emplear semClose(). */ void semRm(int id) { if (semctl(id, 0, IPC_RMID, 0) < 0) fprintf(stderr, "semRm: no puedo borrar semaforo (IPC_RMID)\n"); } /**************************************************************************** * Cerrar el semaforo. * Funcion por proceso que decrementa el numero de procesos activos en el * semaforo. Se emplea al salir. Si el proceso es el ultimo destruye el * semaforo. */ void semClose(int id) { register int semval; /* En primer lugar bloquear el recurso semaforo e incrementar el contador * de procesos [1]. */ if (semop(id, &op_close[0], 3) < 0) fprintf(stderr, "semClose: no puedo bloquer en semClosep\n"); /* Comprobar si el valor leido es la ultima referencia al semaforo. */ if ( (semval = semctl(id, 1, GETVAL, 0)) < 0) fprintf(stderr, "semClose: no puedo realizar GETVAL\n"); if (semval > BIGCOUNT) fprintf(stderr, "<(cargar fuente) volverBIGCOUNT>>\n "); else if (semval == BIGCOUNT) semRm(id); else if (semop(id, &op_unlock[0], 1) < 0) fprintf(stderr, "semClose: no puedo desbloquear\n"); /* desbloqueo */ } /**************************************************************************** * Espera hasta que el valor del semaforo sea mayor que 0, entonces * decrementa en 1 y vuelve. Operador wait, DOWN (Tanenbaum) o P (Dijkstra). */ void semWait(int id) { semOp(id, -1); } /**************************************************************************** * Incrementar el semaforo en 1. Operador segnal, UP (Tanenbaum) o * V (Dijkstra). */ void semSignal(int id) { semOp(id, 1); } /**************************************************************************** * Operacion generica de semaforo: * incrementar o decrementar cierta cantidad positiva o negativa, distinta * de cero. */ void semOp(int id, int value) { if ( (op_op[0].sem_op = value) == 0) (void) fprintf(stderr, "semOp: 'valor' no puede ser 0\n"); if (semop(id, &op_op[0], 1) < 0) (void) fprintf(stderr, "semOp: error\n"); }