Esperimenti con semafori e monitor in C

From Sistemi Operativi
Jump to navigation Jump to search

Ho messo a vostra disposizione alcuni sorgenti che implementano in C le astrazioni di semaforo e monitor come le abbiamo viste (o vedremo presto) a lezione.

Ho messo il materiale in questo file.

La directory comprende:

  • un Makefile
  • tlist.[ch] un modulo di implementazione di liste circolari di thread_id (usate per gestire i processi bloccati). Le liste possono essere usate come code o stack.
  • suspend.[ch] è il modulo che implementa la sospensione e riattivazione dei pthread (usando il segnale SIGUSR1).
  • semaphore.[ch] implementazione dei semafori fair
  • monitor.[ch] implementazione dei monitor (con semantica signal urgent per le variabili di condizione).
  • prod_cons.c esempio: produttore consumatore con i semafori
  • bounded_buf.c esempio: bounded_buffer con i semafori
  • bounded_buf_mon.c esempio: bounded buffer con i monitor
  • philo.c: cena dei filosofi (la soluzione è volutamente errata e può generare deadlock)

--FedericoB (talk) 11:14, 22 November 2016 (CET)
Ho aggiunto in questo file.

  • binsemaphore.[ch] semafori binari implementati senza semafori normali
  • dirtybinsemaphore.c semafori binari implementati con semafori normali
  • prod_consBin.c problema del produttore-consumatore risolto con solo un semaforo(binario). (warning contiene useless wait)
  • dirtysemaphore.c semafori ordinari implementati con semafori binari
  • readerWriters.c soluzione del problema lettori-scrittori che abbiamo discusso qui
  • semaphoreWithMonitors.c semafori ordinari implementati utilizzando i monitor

Nel makefile ho aggiunto:

  • il target prod_consBin e dirtyprod_consBin in cui cambia solo il tipo di semaforo binario linkato.
  • il target readerWriters e dirtyreaders_writers in cui cambia solo il tipo di semaforo ordinario linkato.
  • il target dirtyPhilo che linka al problema dei filosofi i semafori che utilizzano al loro interno i monitor.

Renzo (talk) 09:01, 15 November 2016 (CET)
attenzione: l'implementazione di FedericoB e' un ottimo punto di partenza per la discussione ma non e' corretta, Infatti non rispetta l'invariante dei semafori binari 0 <= nV + Init - nP <= 1. Poniamo infatti Init = 0, e sottoponiamo l'implementazione di Federico alla sequenza V V P V. La prima V porta il semaforo al valore 1, la seconda blocca il chiamante, la P successiva trova il semaforo a 1 quindi non e' bloccante e quindi (codice alla mano) decrementa il semaforo (s->value--) e risveglia il processo bloccato per la seconda V. Il semaforo ora vale zero. L'ultima V non e' quindi bloccante. quindi Init=0, nV=3, nP=1. (Nv + Init - Np) vale quindi 0 + 3 - 1 cioe' 2. Ahi! Dov'e' l'errore?


--FedericoB (talk) 10:54, 15 November 2016 (CET)
Ho sistemato creando prima i semafori binari implementati con i semafori normali.
Dopo sono riuscito a sistemare anche i semafori binari senza semafori normali. L'unico difetto è che usano 2 value invece che 1.


Renzo (talk) 12:03, 15 November 2016 (CET)
Ancora ottimi spunti di discussione, ma non ancora perfetti.... Prendiamo l'implementazione dei semafori binari con i semafori ordinari e il caso Init 1, V V P P. Per effetto dell'Init sem0 vale 1 e sem 1 vale 0. Il primo V porta sem0 a 2 e il chiamante si ferma su sem1. Il secondo V porta sem0 a 3 e ferma anch'esso il chiamante su sem1. La prima P supera la P su sem0 (che viene portato a 2) fa la V su sem1 (che ora vale 1) ed esce. La seconda P porta sem0 a 1, sem1 a 2 ed esce. Abbiamo lasciato I due processi fermi sulla P(sem1). Prima o poi si sbloccheranno.... ma in questo momento nP (il numero di processi che hanno superato la P) vale 2, nV (il numero di processi che hanno superato la V) e' zero, Init era 1. quindi nV + Init - nP = 0 + 1 - 2 = -1. Ahime'! Come si risolve?


--FedericoB (talk) 10:52, 18 November 2016 (CET)
Ho sistemato i semafori binari e ho aggiunto i semafori ordinari implementati con i binari.
Ho tenuto il problema del produttore consumatore con il solo semaforo binario. Funziona ma abbiamo visto che può far aspettare inutilmente il consumatore.


S.G (talk) 10:36, 18 November 2016 (CET)
Ho dei dubbi riguardo la seguente parte "La prima P supera la P su sem0 (che viene portato a 2) fa la V su sem1 (che ora vale 1) ed esce". La prima P viene superata, mentre la V su sem1 (verifica che la coda sia vuota, se lo è incrementa di uno il semaforo, altrimenti risveglia un processo) ha in coda alcuni processi, e quindi non dovrebbe incrementare sem1.


#include<stdio.h>
#include<pthread.h>
#include "binsemaphore.h"

binsemaphore binSemaphore;
volatile int buffer;

void *producer(void *arg) {
    short int firstTime = 1;
	while (1) {
		int value;
		usleep(random() % 200000);
		value = random() % 32768;
		printf("produced: %d\n",value);
		if (!firstTime) binsemaphore_V(binSemaphore);
        buffer = value;
		printf("sent: %d\n",value);
        binsemaphore_V(binSemaphore);
        firstTime=0;
	}
}

void *consumer(void *arg) {
	while (1) {
		int value;
		printf("\t\tconsumer ready\n");
        binsemaphore_P(binSemaphore);
		value = buffer;
		printf("\t\tconsume %d\n", value);
        binsemaphore_P(binSemaphore);
		usleep(random() % 200000);
	}
}

int main(int argc, char *argv[]) {
	pthread_t prod_t;
	pthread_t cons_t;
    binSemaphore=binsemaphore_create(0);
	srandom(time(NULL));
	pthread_create(&prod_t, NULL, producer, NULL);
	pthread_create(&cons_t, NULL, consumer, NULL);
	pthread_join(prod_t, NULL);
	pthread_join(cons_t, NULL);
}

In questa soluzione del prod_cons con un solo semaforo binario è presente race condition su buffer in quanto non c'è alcuna sicurezza di mutua esclusione nel producer e quindi sia producer che consumer possono accedere al buffer contemporaneamente... Si riesce a patchare oppure non è possibile risolverlo con un solo semaforo 0/1?

uso dei semafori

Se volete fare altri esperimenti con i semafori dovete includere il file semaphore.h nei vostri sorgenti C.

Un semaforo ha tipo semaphore e viene creato in questo modo

semaphore mutex;
mutex = mutex_create(1);

(ovviamente le variabili semaphore devono essere condivise e 1 è solo l'esempio di valore iniziale per un semaforo mutex)

Le operazioni P e V su mutex si scrivono così:

semaphore_P(mutex);
semaphore_V(mutex);


produttore e consumatore

Il file prod_cons.c implementa una soluzione del problema del produttore/consumatore.

Il buffer e i semafori sono definiti come variabili condivise.

semaphore full;
semaphore empty;
volatile int buffer;

Il main inizializza i semafori e crea i thread.

int main(int argc, char *argv[]) {
  pthread_t prod_t;
  pthread_t cons_t;
  full=semaphore_create(0);
  empty=semaphore_create(1);

  pthread_create(&prod_t, NULL, producer, NULL);
  pthread_create(&cons_t, NULL, consumer, NULL);
  pthread_join(prod_t, NULL);
  pthread_join(cons_t, NULL);
}

La sincronizzazione fra produttore e il consumatore viene realizzata come segue:

void *producer(void *arg) {
  while (1) {
    int value;
    // produce value
    semaphore_P(empty);
    buffer = value;
    semaphore_V(full);
  }
}

void *consumer(void *arg) {
  while (1) {
    int value;
    semaphore_P(full);
    value = buffer;
    semaphore_V(empty);
    // consume value
  }
}

Uso dei monitor

Un monitor viene creato con la funzione:

monitor mymon;
mymon = monitor_create()

Le variabili di condizione vengono create nel seguente modo:

condition oktoproceed;
oktoproceed = condition_create(mymon);

(NB: le variabili di condizione sono sempre riferite ad uno specifico monitor).

Le procedure entry sono normali funzioni C ma occorre mettere all'entrata e all'uscita le chiamate monitor_{enter,exit}':

int mymon_dosomething(/* params */) {
  rval int;
  monitor_enter(mymon);
  ....
  monitor_exit(mymon);
  return rval;
}

Le operazioni wait e signal sulle variabili di condizione si scrivono così:

condition_wait(oktoproceed);
condition_signal(oktoproceed);

Bounded buffer con i monitor

Vengono definite come variabili condivise il monitor e le variabili di condizione

monitor bb;
condition ok2write;
condition ok2read;

oltre al buffer e alle variabili per la gestione delle strutture dati, fra le quali il contatore buflen.

Il monitor viene creato da questa funzione:

void bb_create(void) {
  bb = monitor_create();
  ok2write = condition_create(bb);
  ok2read = condition_create(bb);
}

che viene richiamata dal main prima di far partire i thread:

int main(int argc, char *argv[]) {
  pthread_t prod_t;
  pthread_t cons_t;
  bb_create();

  pthread_create(&prod_t, NULL, producer, NULL);
  pthread_create(&cons_t, NULL, consumer, NULL);
  pthread_join(prod_t, NULL);
  pthread_join(cons_t, NULL);
}

Il monitor implementa due procedure entry put e get:

void bb_put(int value) {
  monitor_enter(bb);
  if (buflen >= SIZE)
    condition_wait(ok2write);
  // enqueue value in the buffer
  buflen++;
  condition_signal(ok2read);
  monitor_exit(bb);
}

int bb_get(void) {
  int rval;
  monitor_enter(bb);
  if (buflen <= 0)
    condition_wait(ok2read);
  // dequeue rval from the buffer
  buflen--;
  condition_signal(ok2write);
  monitor_exit(bb);
  return rval;
}

Il codice dei processi produttore e del consumatore usa le procedure entry del monitor bb in questo modo:

void *producer(void *arg) {
  while (1) {
    int value;
    // produce value
    bb_put(value);
  }
}

void *consumer(void *arg) {
  while (1) {
    int value;
    value = bb_get();
    // consume value
  }
}

Semafori implementati con monitor

#include "semaphore.h"
#include "monitor.h"

struct semaphore {
    volatile long value;
    monitor monit;
    condition okToP;
};

semaphore semaphore_create(long initval) {
    struct semaphore* s=malloc(sizeof(semaphore));
    if (s) {
        s->value = initval;
        s->monit = monitor_create();
        s->okToP = condition_create(s->monit);
    }
    return s;
}

void semaphore_destroy(semaphore s) {
    condition_destroy(s->okToP);
    monitor_destroy(s->monit);
    free(s);
}

void semaphore_P(semaphore s) {
    monitor_enter(s->monit);
    if (s->value <=0) condition_wait(s->okToP);
    s->value--;
    monitor_exit(s->monit);
}

void semaphore_V(semaphore s) {
    monitor_enter(s->monit);
    s->value++;
    condition_signal(s->okToP);
    monitor_exit(s->monit);
}

Monitor implementati con semafori

--FedericoB (talk) 09:59, 30 November 2016 (CET) In questo pacchetto trovate anche il file commentato, l'implementazione della lista di semafori e il problema bounded buffer risolto con i monitor per provare il funzionamento.

#include<stdio.h>
#include "semaphore.h"
#include "slist.h"

struct monitor {
    semaphore lock;
    slist urgent;
};

typedef struct monitor *monitor;

struct condition {
    monitor mon;
    slist waitQueue;
};

typedef struct condition *condition;

monitor monitor_create(void) {
    monitor m = malloc(sizeof(*m));
    if (m) {
        m->lock = semaphore_create(1);
        m->urgent = NULL;
    }
    return m;
}

void monitor_destroy(monitor m) {
    free(m);
}

void monitor_enter(monitor m) {
    semaphore_P(m->lock);
}

void monitor_exit(monitor m) {
    if (m->urgent != NULL)
        semaphore_V(slist_pop(&m->urgent));
    else
        semaphore_V(m->lock);
}

condition condition_create(monitor m) {
    condition c = malloc(sizeof(*c));
    if (c) {
        c->mon = m;
        c->waitQueue = NULL;
    }
    return c;
}

void condition_destroy(condition c) {
    free(c);
}

void condition_wait(condition c) {
    semaphore sem = semaphore_create(0);
    slist_enqueue(&c->waitQueue, sem);
    monitor_exit(c->mon);
    semaphore_P(sem);
    semaphore_destroy(sem);
}

void condition_signal(condition c) {
    if (!slist_empty(c->waitQueue)) {
        monitor mon = c->mon;
        semaphore processToWakeup = slist_dequeue(&c->waitQueue);
        semaphore ourSemaphore = semaphore_create(0);
        slist_push(&mon->urgent, ourSemaphore);
        semaphore_V(processToWakeup);
        semaphore_P(ourSemaphore);
        semaphore_destroy(ourSemaphore);
    }
}