|
|
TÉLÉCOM SudParis 2ème
année
TP Noté CSC4508/M2 du 20/05/14
Modalités
Durée : 3 heures
Tous documents autorisés.
Les questions 1, 2 et 3 sont indépendantes les unes des autres. Aussi,
n'hésitez pas à lire tout le sujet avant de commencer pour déterminer
l'ordre dans lequel vous souhaitez traiter les questions.
Le barème est donné à titre indicatif des poids entre les différentes
questions.
La « livraison » de votre travail en fin de TP noté se fera par
remontée sous Moodle (rubrique « TP noté de 3 heures ») du fichier
d'extension tgz constitué de la manière suivante :
cd votreRepertoireDeTravailPourCSC4508M2 tar cvfz ${USER}.tgz ${USER}_TPNote2014
NB: Si vous êtes plus à l'aise en écrivant vos réponses sur une copie
plutôt qu'en les tapant sur ordinateur, vous pouvez rendre également
une copie papier à l'enseignant qui vous surveille. En fin d'épreuve,
même si vous ne rendez pas de copie, passez voir l'enseignant qui vous
surveille pour lui signaler explicitement que vous ne rendez pas de
copie.
Préparation
cd votreRepertoireDeTravailPourCSC4508M2
wget http://www-inf.it-sudparis.eu/COURS/CSC4508/Current/Documents/ExoCoursSys/TPNote2014/WWW/tPNote2014.tgz
tar xvfz tPNote2014.tgz
mv TPNote2014 ${USER}_TPNote2014
cd ${USER}_TPNote2014
Question 1 : Questions de cours (4 points)
On considère le programme Q1/my_strtok.c qui
s'appuie sur une implémentation de la
fonction strtok_r pour décomposer des chaînes de
caractères séparées par le caractère ':' . Les
paramètres passés au programme sont distribués à plusieurs
threads et chaque thread décompose sa chaîne de caractère
en tokens .
Ainsi, lorsque l'on invoque le programme avec les paramètres
"ab:c:d" et "e:f", un thread décompose la chaîne "ab:c:d" en 3
jetons ("ab", "c" et "d") pendant qu'un autre décompose la
chaîne "e:f" en "e" et "f" :
$ ./my_strtok ab:c:d e:f
[0] 'ab'
[0] 'c'
[0] 'd'
[1] 'e'
[1] 'f'
Question 1.1 (2 points)
Le programme semble fonctionner lorsque l'on décompose une
chaîne de caractère, mais se mélange les pinceaux lorsqu'il
faut en traiter plusieurs.
Expliquez dans le fichier Q1/reponses.txt
pourquoi le programme ne donne pas le résultat attendu
lorsqu'il faut traiter plusieurs chaînes de caractères.
Question 1.2 (1 point)
Proposez (sans l'implémenter) dans le fichier
Q1/reponses.txt une solution permettant de
corriger la fonction my_strtok_r . Dans cette
solution, seule la fonction my_strtok_r doit
être modifiée.
Question 1.3 (1 point)
Complétez dans le répertoire Q1/Q1.3 le
fichier my_strtok.c afin d'implémenter la
solution que vous proposez.
Question 2 : Question de cours (suite) (2 points)
On considère le programme Q2/mystere.c
Compilez ce programme. Puis, tapez la commande : ./mystere mystere.c mystere2.c
Le programme plante.
Corrigez mystere.c pour que ./mystere ne plante plus.
Puis, expliquez dans le commentaire en tête du fichier mystere.c ce que fait ce programme ./mystere .
Question 3 : "Jeu de trônes" (6 points)
Dans le royaume de Westeros, les relations entre la famille
Stark et la famille Lannister sont très tendues depuis
quelques temps.
Les Stark reprochent aux Lannister d'avoir tranché la tête
de l'un des leurs.
Les Lannisters quant à eux en veulent aux Starks d'avoir
lancé des rumeurs de relations incestieuses entre
Lannisters.
Ces relations tendues ayant déjà causé la mort de plusieurs
membres des deux familles, le Septon (prêtre) responsable
d'un septuaire (lieu de prière) fréquenté par les deux
familles a du prendre des mesures drastiques pour empêcher
de futurs bains de sang : le septuaire ne peut être
fréquenté que par une famille à la fois.
Si un Stark est occupé à prier, il peut être rejoint par
d'autres Stark, mais les Lannisters souhaitant se recueillir
doivent attendre que tous les Starks soient partis.
La même règle s'applique quand les Lannisters occupent le
lieu de prière : ils peuvent être rejoint par d'autres
Lannisters, mais les Starks doivent attendre leur tour.
L'objectif de cet exercice est de simuler la circulation
des membres des deux familles dans le septuaire. Pour cela,
chaque Stark ou Lannister de l'histoire est représenté par
un thread.
Lorsqu'un thread (Stark ou Lannister) entre dans
le lieu de pière, il appelle la
fonction prier . Tant qu'un thread est en train
de prier, les threads du même type peuvent appeler la
fonction prier . Les threads du type opposé
doivent attendre que le lieu de prière soit libre avant
d'appeler la fonction prier .
Pour chacune des questions ci-dessous :
- Répondez dans le fichier
Q3/reponses3.txt ou bien sur
votre copie (dans ce dernier cas, indiquez dans le fichier
Q3/reponses3.txt « Réponse sur copie »).
- Le langage algorithmique utilisé n'a pas besoin de respecter
strictement la syntaxe apprise en première année. L'essentiel est
qu'il soit lisible par le correcteur et sans ambiguïté.
- Les questions 3.1 et 3.2 sont indépendantes.
Question 3.1 (4 points)
Chaque Stark est modélisé par un thread exécutant
l'algorithme codeStark() . Chaque Lannister est
modélisé par un thread exécutant un
algorithme codeLannister() similaire.
procédure
codeStark()
// à compléter, si besoin, avec du code d'initialisation
et de synchronisation
prier( );
// à compléter, si besoin, avec du code de synchronisation
Fin Procédure
À l'aide de sémaphores (dont vous indiquerez les valeurs initiales),
d'éventuelles variables additionnelles (dont vous indiquerez les
valeurs initiales), et d'opérations P() et V(), complétez l'algorithme de
codeStark().
Question 3.2 (2 points)
La solution que vous proposez induit-elle un risque de
"famine" ? Justifiez votre réponse.
Question 4 : Liste chaînée (8 points)Cet
exercice s'intéresse à l'implantation, en environnement multithreadé, d'une liste
chaînée destinée à stocker des entiers de
manière ordonnée.
Le fichier d'interface Q4/Q4.1/LinkedList.h définit deux structures de données :
- une structure Element
correspondant à un maillon de la liste chaînée. Ce
maillon contient l'entier stocké à cet endroit de la
liste chaînée et un pointeur vers le maillon suivant de la
liste chaînée (NULL si ce maillon est le dernier de la liste) ;
- une structure LinkedList contenant un pointeur vers le premier maillon de la liste chaînée.
Ce fichier d'interface contient également les
déclarations et descriptions doxygen des différentes
opérations sur une liste chaînée.
Pour simplifier les algorithmes de notre implantation, une liste
chaînée contient systématiquement au moins 2
maillons : le premier stocke la valeur entière INT_MIN (la valeur minimum des entiers), alors que le dernier stocke la valeur entière INT_MAX (la valeur maximum des entiers). Ainsi, une liste chaînée vide contient toujours 2 maillons (l'un contenant INT_MIN et l'autre INT_MAX).
La figure suivante représente une liste chaînée contenant les valeurs 0 et 3 :
Nous vous invitons à étudier le code des fonctions addLL et removeLL dans Q4/Q4.1/LinkedList.c pour bien comprendre le fonctionnement de cette implantation.
Pour chacune des questions ci-dessous :
- répondez dans le fichier
Q4/reponses4.txt ou bien sur
votre copie (dans ce dernier cas, indiquez dans le fichier ;
Q4/reponses4.txt
« Réponse sur copie ») pour les questions
portant sur la compréhension des problèmes de
synchronisation (NB : ces questions comptent pour la moitié des
points du barême).
- les questions 4.1, 4.2 et 4.3 sont indépendantes.
Question 4.1 (4 points)
Le code semble fonctionner parfaitement. En effet, en allant dans le répertoire Q4.1, en tapant make, puis ./monothreadTestLinkedList (qui est un test d'intégration de la liste), nous obtenons, à raison, le message "Test OK (la liste finale est vide)". Toutefois, l'introduction de multithreading peut apporter des dysfonctionnements. Dans Q4/reponses4.txt (ou sur votre copie), montrer que :
- si la liste chaînée contient les valeurs 0 et 3 (en plus des valeurs INT_MIN et INT_MAX) et que si un thread thread1 exécute addLL(liste, 1) tandis qu'un thread thread2 exécute simultanément addLL(liste,2), l'ajout de 1 ou 2 peut être perdu ;
- si la liste chaînée contient les valeurs 4, 5, 6 et 7 (en plus des valeurs INT_MIN et INT_MAX) et que si un thread thread5 exécute removeLL(liste, 5) tandis qu'un thread thread7 exécute simultanément removeLL(liste,7), pour thread7, la variable locale e de removeLL peut se retrouver avec la valeur NULL dans le test de la boucle while.
Pour vérifier l'un de ces dysfonctionnements, complétez Q4/Q4.1/multithreadTestLinkedList.c pour que :
- votre fonction main() initialise une barrière pthread (cf. man pthread_barrier_init) ;
- votre fonction main() crée NBTHREAD threads (NBTHREAD étant une constante déjà définie dans Q4/Q4.1/multithreadTestLinkedList.c) qui exécutent la fonction addRemove() décrite ci-dessous ;
- votre fonction main() attend ensuite que les NBTHREADS aient terminé leur exécution ;
- votre fonction main() libère ensuite les différentes ressources ;
- chaque thread exécute la fonction addRemove() qui :
- commence par attendre sur la barrière pthread (cf. man pthread_barrier_wait),
- fait NBVAL appels à addLL() identiques à ceux que fait la fonction main() dans monothreadTestLinkedList.c , chaque appel à addLL() étant suivi par un appel à sched_yield(),
- et enfin effectue NBVAL appels à removeLL() identiques à ceux que fait la fonction main() dans monothreadTestLinkedList.c , chaque appel à removeLL() étant suivi par un appel à sched_yield().
Tapez make, puis ./multithreadTestLinkedList. Sur la machine du rédacteur de cet énoncé, nous obtenons une Erreur de segmentation (NB : du fait que vous n'avez aucun contrôle sur l'ordonnancement des threads sur votre système d'exploitation, il se peut aussi que votre test renvoie un résultat OK). L'analyse sous debugger (non demandée dans le cadre de ce TP noté) montre que la variable locale e de removeLL() se retrouve avec la valeur NULL dans le test de la boucle while : l'instruction e->next->val cause donc une erreur de segmentation.
Question 4.2 (2 points)
Pour éviter les problèmes identifiés
à la question Q4.1, nous allons considérer tout
accès à la liste chaînée comme une section
critique (à l'aide d'un mutex garantissant que seul un thread peut accéder à la liste chaînée à un instant donné).
Dans Q4/reponses4.txt (ou sur votre copie), expliquez les inconvénients de cette méthode.
Dans Q4.2/LinkedList.h, ajoutez un champ de type pthread_mutex_t au niveau de la structure LinkedList.
Ensuite, modifiez Q4.2/LinkedList.c pour utiliser ce mutex de la manière qui vous semble adéquate dans les différentes fonctions de ce module.
Tapez make, puis ./multithreadTestLinkedList (ou bien ./monothreadTestLinkedList si vous n'avez pas terminé l'implantation de la question 4.1). Vous devez obtenir, à raison, le message "Test OK (la liste finale est vide)".
Question 4.3 (2 points)
NB : cette question étant difficile, abordez-la seulement quand vous avez terminé les autres questions.
Pour pallier l'inconvénient identifié à la question 4.2, nous vous proposons d'avoir un mutex au niveau de *chaque* élément de la liste chaînée.
Dans removeLL(), un thread progresse en prenant le mutex sur l'élément ei sur lequel il se trouve et son successeur ei+1. Lorsqu'il "avance" (e prend la valeur de ei+1), il prend le mutex sur ei+2 et relâche le mutex sur ei. Il progresse ainsi jusqu'à ce que ei+1 corresponde à l'élément à supprimer.
Dans addLL(), un thread progresse de la même manière que pour removeLL(). Il progresse ainsi jusqu'à ce que ei et ei+1 correspondent aux éléments entre lesquels le thread va insérer le nouvel élément.
Dans Q4/reponses4.txt (ou sur votre copie), expliquez pourquoi un thread qui exécute removeLL()
ne peut pas se contenter de prendre le mutex de l'élément
qui précède l'élément à supprimer
(donc, sans prendre le mutex sur l'élément à
supprimer).
Dans Q4.3/LinkedList.h, ajoutez un champ de type pthread_mutex_t au niveau de la structure Element.
Ensuite, modifiez Q4.3/LinkedList.c pour utiliser ce mutex comme décrit précédemment.
Tapez make, puis ./multithreadTestLinkedList (ou bien
./monothreadTestLinkedList si vous n'avez pas terminé l'implantation de
la question 4.1). Vous devez obtenir, à raison, le message "Test OK (la
liste finale est vide)".
|
|