Práctica de programación: Semáforos

Como ya sabemos, los semáforos son un mecanismo de sincronización potente y fácil de emplear. En esta práctica se propone una implementación de semáforo tomando como base el interface de aplicación de semáforo de System V.

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:

  1. Problema del buffer limitado.
  2. Problema de los filosofos comensales.
  3. Problema de los lectores y escritores.

Nota importante

A pesar de que las rutinas están pensadas para que no haya problemas de semaforos colgados en el sistema, siempre es posible que esto ocurra. Para resolver el problema se puede utilizar una solución similar a la que se describe en la práctica anterior. A pesar de ello, el alumno inteligente habrá pensado que UNIX debería disponer ya de algún modo de eliminar ipcs no deseados... En efecto "ipcrm" es la solución al problema; consulta la pagina del manual currespondiente!

Referencias: W. Richard Stevens "Advanced Programming in the Unix Environment" Addison-Wesley 1992


Implementación de funciones de manejo de semáforos en UNIX

/*
* 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, "< BIGCOUNT>>\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");
}

(cargar fuente) volver
Apriete aqui para volver a la página raíz