1. Présentation du modèle lecteurs/écrivains
Nous allons ici utiliser des sémaphores pour synchroniser des processus en suivant la sémantique du modèle lecteurs/écrivains. Pour plus de détails, on trouvera ici le support de cours sur les sémaphores (cf. pages 100 à 112) et plus particulièrement l’implémentation du modèle lecteurs/écrivains (pages 106 à 108).
1.1 Rappel du modèle
Le fonctionnement du modèle lecteurs/écrivains est le suivant :
- Un écrivain en cours de modification interdit tout autre accès à la ressource partagée, que ce soit en lecture ou en écriture,
- Un lecteur en consultation interdit les accès en écriture, mais autorise d’autres consultations simultanées de la ressource partagée.
1.2 Sémaphores Unix
Dans le cours, vous avez vu les opérations P, V et Init sur les sémaphores. Les fonction C proposées par le système UNIX pour effectuer les opération P, V, et Init sont décrites dans le tableau ci-dessous :
Opération | API POSIX C |
---|---|
Init(sem, compteur) |
sem_t * sem = sem_open(Nom, O_CREAT, 0644, compteur) Signification des paramètres:
|
P(sem) |
sem_wait(sem); Notez ici que le paramètre sem est celui retourné par la fonction sem_open; il est du type sem_t*. |
V(sem) |
sem_post(sem); Notez ici que le paramètre sem est celui retourné par la fonction sem_open; il est du type sem_t*. |
2. Synchronisation des seuls lecteurs
Un canevas de programme, à compléter, vous est fourni dans le fichier source TP_SELC/tp11&12/material/readers_writers_exercice.c. Vous allez compléter ce programme pas à pas: d’abord en synchronisant les lecteurs entre eux, puis en synchronisant les lecteurs et les écrivains (en répondant à la question suivante).
Dans cette étape, l’objectif est simplement de s’assurer que la synchronisation entre les lecteurs ne produit pas de situations d’interblocage.
Remarques :
- Pour implanter le mécanisme de synchronisation de type lecteur/écrivain, il faut partager l’information suivante entre les processus: le nombre de lecteurs ayant accès à la ressource partagée. Cette information sera encodée dans le fichier readers.dat. L’accès à l’information est déjà codé dans le fichier source fourni grâce aux fonctions fopen, lseek, fprintf, et fscanf.
- Création et initialisation des sémaphores: le fichier semaph.h est utilisé pour associer des noms (définis via des macro) aux sémaphores Unix, il contient :
semaph.h /* The content of this file aims at defining user-friendly macros * to refer to Unix semaphore names. */ #define LOCK_FOR_SHARED_VARIABLES_NAME "/sem55" #define LOCK_FOR_READERS_WRITERS_NAME "/sem66" #define LOCK_AGAINST_STARVATION_NAME "/sem77"
- L’édition de liens doit se faire avec l’option -lpthread pour avoir accès à la bibliothèque contenant les sémaphores. Par exemple :
$gcc -Wall readers_writers_exercice.c -o readers_writers -lpthread
- En cas de problème, vous allez interrompre l’exécution du programme en faisant Ctrl+c. Dans ce cas, vous laissez les sémaphores dans un état qui ne correspond plus à l’état dans lequel ils étaient avant l’exécution du programme… Et il faut les nettoyer. Le fichier cleanup.c contient le code qui permet de réaliser cette action. Compilez le (en utilisant l’option -lpthread) et exécutez le à chaque fois que vous devez ré-initialiser les sémaphores.
- Un sémaphore à qui on donne le nom "/nom" est implanté dans le répertoire /dev/shm sous le nom : sem.nom Pour le vérifier on peut faire : ls -l /dev/shm. On peut donc supprimer les sémaphores en utilisant la commande rm.
2.1 Exercice
- Compiler et exécuter le programme pour vérifier que la synchronisation n’est pas assurée (i.e. les lecteurs et écrivains ont accès simultanément à la ressource partagée), par exemple :
./readers_writers 0 2
./readers_writers 2 1 - Compléter la partie lecteur seulement de ce programme et vérifier son bon fonctionnement. Pour ce faire, si l’exécutable s’appelle readers_writers, on doit obtenir ce type d’affichage (qui montre qu’il n’y a pas d’interblocage, c’est à dire que le programme se termine) :
$ ./readers_writers 0 0 main : ./readers_writers: this is the end... $ ./readers_writers 4 0 In is_first, number of readers : 1 ========================= Reader 0 is the first one entering the critical section (i.e. accessing the shared resource) In is_first, number of readers : 2 In is_first, number of readers : 3 ========================= Reader 0 enters the critical section -- Date : 44:03 ========================= Reader 1 enters the critical section -- Date : 44:03 In is_first, number of readers : 4 ========================= Reader 2 enters the critical section -- Date : 44:03 ========================= Reader 3 enters the critical section -- Date : 44:03 ========================= Reader 0 about to exit the critical section -- Date : 44:13 In is_last, number of readers: 3 main : end of child process, number 0000 (state in hexa) ========================= Reader 1 about to exit the critical section -- Date : 44:14 In is_last, number of readers: 2 main : end of child process, number 0100 (state in hexa) ========================= Reader 2 about to exit the critical section -- Date : 44:15 In is_last, number of readers: 1 main : end of child process, number 0200 (state in hexa) ========================= Reader 3 about to exit the critical section -- Date : 44:16 In is_last, number of readers: 0 ========================= Reader 3 is last main : end of child process, number 0300 (state in hexa) main : ./readers_writers : this is the end...
3. Synchronistion des lecteurs et des écrivains
3.1 Exercice
Complétez maintenant la partie écrivain de ce programme. Pour le moment on ne traite pas le problème de la famine pour les écrivains.
Les tests à effectuer :
- On vérifie d’abord que l’exclusion mutuelle est assurée pour les écrivains :
$ ./readers_writers 0 2 ******************** Writer 1 starts -- Date: 47:05 ******************** Writer 1 enters the critical section -- Date : 47:05 (iteration : 0) ******************** Writer 0 starts -- Date: 47:05 ******************** Writer 1 about to exit the critical section -- Date : 47:09 (iteration : 0) ******************** Writer 0 enters the critical section -- Date : 47:09 (iteration : 0) ******************** Writer 0 about to exit the critical section -- Date : 47:12 (iteration : 0) ******************** Writer 1 enters the critical section -- Date : 47:12 (iteration : 1) ******************** Writer 1 about to exit the critical section -- Date : 47:16 (iteration : 1) ******************** Writer 0 enters the critical section -- Date : 47:16 (iteration : 1) ******************** Writer 0 about to exit the critical section -- Date : 47:19 (iteration : 1) ******************** Writer 1 enters the critical section -- Date : 47:19 (iteration : 2) ******************** Writer 1 about to exit the critical section -- Date : 47:23 (iteration : 2) ******************** Writer 0 enters the critical section -- Date : 47:23 (iteration : 2) ******************** Writer 0 about to exit the critical section -- Date : 47:26 (iteration : 2) Writer 1 about to finish main : end of child process, number 0100 (state in hexa) Writer 0 about to finish main : end of child process, number 0000 (state in hexa) main : ./readers_writers : this is the end...
2. On vérifie ensuite le bon fonctionnement global du modèle, par exemple :
$ ./readers_writers 6 2 In is_first, number of readers : 1 ========================= Reader 0 is the first one entering the critical section (i.e. accessing the shared resource) In is_first, number of readers : 2 ******************** Writer 1 starts -- Date: 50:58 ******************** Writer 0 starts -- Date: 50:58 ========================= Reader 0 enters the critical section -- Date : 50:58 ========================= Reader 1 enters the critical section -- Date : 50:58 ========================= Reader 0 about to exit the critical section -- Date : 51:08 In is_last, number of readers: 1 main : end of child process, number 0000 (state in hexa) ========================= Reader 1 about to exit the critical section -- Date : 51:09 In is_last, number of readers: 0 ========================= Reader 1 is last ******************** Writer 1 enters the critical section -- Date : 51:09 (iteration : 0) main : end of child process, number 0100 (state in hexa) ******************** Writer 1 about to exit the critical section -- Date : 51:13 (iteration : 0) ******************** Writer 0 enters the critical section -- Date : 51:13 (iteration : 0) In is_first, number of readers : 1 ========================= Reader 2 is the first one entering the critical section (i.e. accessing the shared resource) ******************** Writer 0 about to exit the critical section -- Date : 51:16 (iteration : 0) In is_first, number of readers : 2 In is_first, number of readers : 3 In is_first, number of readers : 4 ========================= Reader 2 enters the critical section -- Date : 51:16 ========================= Reader 3 enters the critical section -- Date : 51:16 ========================= Reader 4 enters the critical section -- Date : 51:16 ========================= Reader 5 enters the critical section -- Date : 51:16 ========================= Reader 4 about to exit the critical section -- Date : 51:26 In is_last, number of readers: 3 main : end of child process, number 0400 (state in hexa) ========================= Reader 5 about to exit the critical section -- Date : 51:27 In is_last, number of readers: 2 main : end of child process, number 0500 (state in hexa) ========================= Reader 2 about to exit the critical section -- Date : 51:28 In is_last, number of readers: 1 main : end of child process, number 0200 (state in hexa) ========================= Reader 3 about to exit the critical section -- Date : 51:29 In is_last, number of readers: 0 ========================= Reader 3 is last ******************** Writer 1 enters the critical section -- Date : 51:29 (iteration : 1) main : end of child process, number 0300 (state in hexa) ******************** Writer 1 about to exit the critical section -- Date : 51:33 (iteration : 1) ******************** Writer 0 enters the critical section -- Date : 51:33 (iteration : 1) ******************** Writer 0 about to exit the critical section -- Date : 51:36 (iteration : 1) ******************** Writer 1 enters the critical section -- Date : 51:36 (iteration : 2) ******************** Writer 1 about to exit the critical section -- Date : 51:40 (iteration : 2) ******************** Writer 0 enters the critical section -- Date : 51:40 (iteration : 2) ******************** Writer 0 about to exit the critical section -- Date : 51:43 (iteration : 2) Writer 1 about to finish main : end of child process, number 0100 (state in hexa) Writer 0 about to finish main : end of child process, number 0000 (state in hexa) main : ./readers_writers : this is the end...
4. Eviter la famine pour les écrivains
Pour éviter la famine pour les écrivains ajoutez un sémaphore et vérifiez le fonctionnement du programme: le flot de lecteurs doit être interrompu lorsqu’un écrivain souhaite accéder à la ressource partagé.
5. Gestion d’une barrièrre
On veut réaliser une barrière selon le principe suivant : tant que N participants ne sont pas arrivés devant la barrière, il faut que chacun d’eux attende qu’elle se lève. Lorsque le participant numéro N arrive, il lève la barrière et débloque ainsi tous ceux qui attendaient.
Nous considérons chaque participant comme un processus, que nous appellerons un acteur.
Un canevas de programme, à compléter se trouve dans le fichier TP_SELC/TP11&12/material/barrier_exercice.c
Le nom de deux sémaphores sont proposés dans le fichier semaph_barrier.h. Libre à vous d’en ajouter si vous pensez que c’est nécessaire.
Constatez le mauvais fonctionnement en l’exécutant: les acteurs passent la barrière alors qu’elle ne devraient pas être levée.
Complétez ce programme (cf. commentaires : TO BE COMPLETED) et vérifier son bon fonctionnement.
En cas de problème, le code proposé dans le fichier source barrier_cleanup.c permettra de nettoyer le contenu des sémaphores que nous vous proposons d’utiliser. Complétez ce code si vous décidez d’utiliser d’autres sémaphores.
Si l’exécutable s’appelle barrier, on doit obtenir ce type d’affichage :
$ ./barrier 3 ******************** Actor 0 starts at 18:10 ******************** Actor 1 starts at 18:10 ******************** Actor 2 starts at 18:10 ========================= Actor 0 in SC1 ========================= Actor 0 is about to exit SC1 ************ Actor 0 waits at the barrier ========================= Actor 1 in SC1 ========================= Actor 1 is about to exit SC1 ************ Actor 1 waits at the barrier ========================= Actor 2 in SC1 ========================= ALL THE ACTORS ARRIVED ========================= Actor 2 is about to release another actor (one of 2 blocked) ========================= Actor 2 is about to release another actor (one of 1 blocked) ========================= Actor 2 is about to exit SC1 ************ Actor 0 passed the barrier ! 18:18 ******************** Actor 2 : end 18:18 ************ Actor 1 passed the barrier ! 18:18 main - end of child process with state: 0200 (hexa) ******************** Actor 0 : end 18:19 main - end of child process with state: 0000 (hexa) ******************** Actor 1 : end 18:20 main - end of child process with state: 0100 (hexa) main - ./barrier : this is the end ...
Fin du TP.