Publi

Creando un mutex con semáforos entre procesos hijos en C [fork()]

Hemos estado viendo cómo compartir variables entre procesos hijos, y dejamos un poco en el tintero la implementación de mutex en ese caso, esta vez, para implementar el mutex vamos a utilizar semáforos. Estos semáforos también tienen que ser variables compartidas para funcionar correctamente.

Los semáforos tendrá un funcionamiento peculiar. En principio, pensemos en ellos con los valores 1 y 0. Por tanto, si el semáforo vale 1, el semáforo está abierto y lo haremos 0, pero si vale 0, nos esperaremos hasta que valga 1 (al contrario que un while (semaforo==0); utilizando semáforos, será el sistema operativo el que active nuestro proceso cuando el semáforo vale 1 por lo que podemos utilizar los recursos del sistema para otra cosa mientras).
Aunque podemos ir más allá, el valor del semáforo (numérico) podrá ser cualquiera, pero siempre que éste sea positivo, el proceso no se bloqueará, pero sí se decrementará el valor; aunque si el valor es negativo, o cero, el valor se decrementa, pero el proceso se queda esperando un valor positivo.
Al fin y al cabo un mutex es un semáforo que sólo vale 1 y 0 (aunque situado estratégicamente para proteger una sección crítica).

Para utilizar los semáforos tenemos que tener en cuenta tres funciones básicas (hay algunas más):

  • sem_init(semaforo, pshared, value): Inicializamos el semáforo, y le damos el valor value. pshared deberá ser 0 si queremos el semáforo entre threads, u otro valor si queremos el semáforo compartido entre procesos. En este caso, será 1
  • sem_post(semaforo): Incrementa el semáforo, es lo que utilizamos cuando se libera el recurso
  • sem_wait(semaforo): Decrementa el semáforo, y si su valor es menor que cero, bloquea el proceso hasta tener un valor mayor o igual a cero. Lo usamos para comprobar si el recurso está ocupado.

En el siguiente ejemplo incrementaremos un número, aunque para complicarlo un poco, este número vendrá dado por una cadena de caracteres, cada incremento debe hacerlo un proceso hijo diferente por lo que tienen que estar todos perfectamente sincronizados para hacerlo bien. El valor final de x debe ser 20. Por otro lado, se han introducido esperas aleatorias para simular un proceso pesado que tarda un tiempo variable, y así provocar la condición de carrera.

Podemos modificar el valor de la constante SEMAPHORES De 1 a 0 para ver cómo se comporta el código en cada caso.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <string.h>

#define SEMAPHORES 1

int main()
{
  char *x = mmap(NULL, sizeof(char)*10, PROT_READ | PROT_WRITE,
               MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  strcpy(x, "0");

  int i;
  int child;
  sem_t *semaforo = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE,
             MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  int temp;
  sem_init (semaforo, 1, 1);
  for (i=0; i<10; ++i)
    {
      child = fork();
      if (child==0)
    {
      usleep(rand()%20000);

      if (SEMAPHORES)
        sem_wait(semaforo);
      printf("[%d] Trying to access the resource\n", getpid());
      temp=atoi(x);
      printf("[%d] Using the resource\n", getpid());
      temp++;
      sprintf(x, "%d", temp);

      if (SEMAPHORES)
        sem_post(semaforo);
      printf("[%d] Just used the resource\n", getpid());
      usleep(rand()%20000);

      if (SEMAPHORES)
        sem_wait(semaforo);
      printf("[%d] Trying to access the resource\n", getpid());
      temp=atoi(x);
      printf("[%d] Using the resource\n", getpid());
      temp++;
      sprintf(x, "%d", temp);

      if (SEMAPHORES)
        sem_post(semaforo);
      printf("[%d] Just used the resource\n", getpid());
      printf("[%d] EXITING\n", getpid());
      exit(1);
    }
    }

   while (wait(NULL)>=0);

  printf("x is: %s\n", x);
  munmap(x, sizeof(int));
  munmap(semaforo, sizeof(sem_t));

  return 0;
}

Cada vez que un proceso vaya a acceder a nuestra sección crítica, lo pondrá en pantalla junto con su pid, por lo que podemos ver claramente si un proceso está utilizando el recurso y otro proceso también entra a utilizarlo. Finalmente se dirá el valor de x, que debe ser 20 y que en el ejemplo sin semáforos no siempre vale 20. (Puede que alguna vez lo haga bien, pero será casualidad.

Nota: Debemos compilar con soporte para pthread:

$ gcc -o example example.c -lpthread

Foto: Paul Albertella (Flickr) CC-by

También podría interesarte...

Only 1 comment left Ir a comentario

  1. Pingback: BlogESfera.com /

Leave a Reply