miércoles, 13 de junio de 2012

Métodos para la resolución de condiciones de competencia entre procesos

Método de los semáforos

Un semáforo es una variable entera sobre la que tenemos dos operaciones diferentes:

Bajar: (en inglés, wait). Lo que hace es comprobar el valor del semáforo. Si es mayor que cero, decrementa el valor y continúa. Si es cero, se pone a dormir. Esta es una operación atómica: comprobar el valor del semáforo, y decrementar o dormir, van a hacerse en un solo paso (debe garantizarlo el sistema operativo).

Subir: (en inglés, signal). Puede que haya procesos esperando en el semáforo. Si hay procesos esperando, despierta a uno de ellos. El planificador decide cuál de ellos se va a despertar y el semáforo conserva su valor. Si no existen procesos esperando se incrementa el valor del semáforo. Si hay un proceso que encontró el semáforo a cero y se puso a dormir, entonces subir no incrementa el semáforo. También ha de ser una operación atómica.

La operación bajar puede bloquear un proceso, pero la operación subir nunca bloquea a nadie.

Para conseguir que haya operaciones atómicas, el sistema operativo puede, por ejemplo, prohibir las interrupciones en ellas. Esto no era bueno en procesos de usuario, pero sí para el sistema operativo. Sin embargo, prohibir las interrupciones no sirve si existen varios procesadores en paralelo en el sistema, en ese caso, deberá haber un variable cerrojo, para cuyo acceso deberá usarse la instrucción tsl. La instrucción tsl implica espera activa, pero en este caso no hace perder mucho tiempo, pues incrementar o decrementar el semáforo tarda muy poco.



#define N 100

typedef int semaforo;

semaforo mutex=1;

semaforo vacio=N;

semaforo lleno=0;

productor()

{

while(TRUE)

{

producir_elemento (&elemento);

bajar (&vacio);

bajar (&mutex);

dejar_elemento (elemento);

subir (&mutex);

subir (&lleno);

}

}

consumidor()

{

int elemento;

while (TRUE)

{

bajar (&lleno);

bajar (&mutex);

retirar_elemento (&elemento);

subir (&mutex);

subir (&vacio);

consumidor_elemento (elemento);

}

}




Método del contador de eventos
Sobre un contador de eventos vamos a tener tres operaciones diferentes (al contador de eventos lo llamamos E):

Leer: Devuelve el valor de E.
Avanzar: Incrementa E. Se realiza en una operación atómica.
Esperar: Se espera que E valga V o mayor que V. Si E es menor que V, se bloquea el proceso; si es mayor o igual que V, se pasa.

Se implementan como llamadas al sistema. Los contadores de eventos nunca se decrementan.
Hay que tener en cuenta que debe cumplirse que el número de entradas ha de ser mayor o igual al número de salidas y que, además, la diferencia entre el número de entradas y el de salidas ha de ser menor o igual que el tamaño (ya que entradas es el número de elementos que hemos metido en el buffer y que salidas es el número de elementos que hemos sacado; tamaño es el ídem del buffer).

#define N 100

typedef int contador_de_eventos;

contador_de_eventos ent =0;

contador_de_eventos sal=0;

productor();

{

int elemento, secuencia=0;

while (TRUE)

{

producir_elemento (&elemento);

secuencia=secuencia+1;

esperar (sal, secuencia-N);

dejar_elemento (elemento);

avanzar (&ent);

}

}

consumidor ()

{

int elemento, secuencia=0;

while (TRUE)

{

secuencia=secuencia+1;

esperar (ent,secuencia);

retirar_elemento(&elemento);

avanzar (&sal);

consumir_elemento (elemento);

}

}