|
|
TELECOM & Management SudParis
TELECOM SudParis 2ème
année
TP Noté CSC4508/M2 du 16/05/08
Modalités
Durée : 3 heures
Tous documents autorisés.
Les questions 1, 2, 3 et 4 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 remise de votre copie à
l'enseignant et
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 TPNote2008
Préparation
cd votreRepertoireDeTravailPourCSC4508M2 cp ~simatic/Cours/CSC4508/tPNote2008.tgz . tar xvfz tPNote2008.tgz cd TPNote2008
Question 1 : Bugs non reproductibles ? (5
points)
Pour chacune des questions ci-dessous, répondez sur votre
copie
ou
bien dans le fichier Q1/reponse1.txt (dans ce
dernier
cas, indiquez sur votre
copie « Réponse dans Q1/reponse1.txt »
).
Question 1-1 : Premier programme (3 points)
Expliquez brièvement ce que fait le programme correspondant
au
fichier Q1/prog1.c
/***********/
/* prog1.c */
/***********/
#include
<stdlib.h>
#include
<stdio.h>
#include
<unistd.h>
#include
<sys/stat.h>
#include
<fcntl.h>
#include
<string.h>
#define NOMFIC
"/tmp/ficPourProg1"
#define
NBENREG 1000
#define
ENREGFORMATE "Enregistrement #1234567\n"
int main() {
int
i;
FILE *ficW;
int
ficR;
char buf[256];
ficW = fopen(NOMFIC, "w");
for
(i=0 ; i<NBENREG; i++){
fprintf(ficW, "Enregistrement #%7d\n", i);
}
fclose(ficW);
ficR = open(NOMFIC, O_RDONLY);
for
(i=0 ; i<NBENREG; i++){
read(ficR,
(void*)buf,
strlen(ENREGFORMATE));
buf[strlen(ENREGFORMATE)-1] = '\0';
printf("Chaine lue : \"%s\"\n", buf);
}
close(ficR);
return EXIT_SUCCESS;
}
Ce programme fonctionne parfaitement, sauf sur
la machine ayant pour hostname
« assous »
où il se met à répéter une
certaine ligne (la ligne répétée
changeant à
chaque nouvelle exécution du programme). Par exemple, lors
d'une
exécution, on peut avoir l'affichage :
Chaine lue : "Enregistrement
# 983"
Chaine lue :
"Enregistrement # 984"
Chaine lue :
"Enregistrement # 985"
Chaine lue :
"Enregistrement # 985"
Chaine lue :
"Enregistrement # 985"
Chaine lue :
"Enregistrement # 985"
Chaine lue :
"Enregistrement # 985"
cette ligne « Chaine
lue : "Enregistrement # 985" »
étant répétée 15 fois au
total.
Modifiez le fichier Q1/prog1.c pour
que toutes les éventuelles erreurs à
l'exécution puissent être
détectées (en cas d'erreur
détectée, on arrêtera
immédiatement
l'exécution du programme).
Cette nouvelle version du programme est exécutée
sur la machine « assous ».
On
obtient le message d'erreur suivant :
Erreur 'No space left on
device' lors ecriture fichier /tmp/ficPourProg1
Expliquez
cette erreur, puis décrivez une expérience
permettant de
reproduire sur n'importe quelle machine le problème
observé
sur la
machine « assous ».
NB : on ne demande pas de réaliser cette
expérience.
Question 1-2 : Deuxième programme (2 points)
Expliquez brièvement ce que fait le programme correspondant
au fichier Q1/prog2.c
/***********/
/* prog2.c */
/***********/
#include
<stdlib.h>
#include
<sys/ipc.h>
#include
<sys/shm.h>
#include
<string.h>
#define SHMKEY
30
#define TAILLE
1<<26
int main() {
char *shm_adr;
int
shm_id;
shm_id = shmget(SHMKEY, TAILLE, 0700|IPC_CREAT);
shm_adr = shmat(shm_id, 0, 0);
memset(shm_adr, 0, TAILLE);
return EXIT_SUCCESS;
}
Ce programme fonctionne parfaitement, sauf sur
la machine ayant pour hostname
« oiserie »
où il s'arrête avec le message suivant :
Erreur de segmentation (core
dumped)
Modifiez le fichier Q1/prog2.c pour
que toutes les éventuelles erreurs à
l'exécution puissent être
détectées (en cas d'erreur
détectée, on arrêtera
immédiatement
l'exécution du programme).
Cette nouvelle version du programme est exécutée
sur la machine « oiserie ».
On
obtient le message d'erreur suivant :
shmget: Invalid argument
Expliquez cette erreur (barème : 0,5 point), puis
décrivez une expérience permettant de
reproduire sur n'importe quelle machine le problème
observé
sur la
machine « oiserie »
(barème : 0,5 point).
NB : on ne demande pas de réaliser cette
expérience.
Question 2 : Rendez-vous conditionnel (5
points)
Le
commentaire du transparent 3.3.2 (rendez-vous entre deux processus) du
cours « Synchronisation entre
processus » présente un algorithme pour
un
rendez-vous entre N processus à l'aide de
sémaphores P et V.
Écrire, à l'aide des
opérations pthread_mutex_lock
et pthread_mutex_unlock d'objets mutex thread
et des opérations pthread_cond_wait
et
pthread_cond_broadcast (NB :
on n'utilisera pas l'opération pthread_cond_signal )
d'objets condition,
l'algorithme d'une procédure
rdv
permettant de programmer un rendez-vous entre N threads.
Rappelons que, quand un thread
appelle cette procédure rdv
:
- s'il est le 1er, le 2ème...
ou le N-1ème
à l'appeler, ce thread
se retrouve bloqué, en attente, dans cette
procédure ;
- s'il est le Nème
à l'appeler, ce
thread
débloque les N-1 autres threads
en attente et poursuit son exécution.
NB : Pour simplifier l'algorithme de rdv, on suppose
que chaque thread
n'appelle rdv
qu'une seule fois.
Pour cette question, répondez sur votre
copie
ou
bien dans le fichier Q2/reponse2.txt (dans ce
dernier
cas, indiquez sur votre
copie « Réponse dans Q2/reponse2.txt »).
Question 3 : Expériences autour d'un serveur
Web (7
points + 2 points de bonus)
Le
code présent dans le répertoire Q3-1
contient un serveur Web
très
simplifié : quand on l'interroge, ce serveur se contente de
renvoyer
une page Web contenant le nombre de fois qu'on l'a interrogé
(et donc
qu'il a répondu).
Avant de rentrer dans le vif du sujet, faites fonctionner ce serveur :
- dans
un terminal, positionnez-vous dans le répertoire Q3-1, tapez make pour
compiler, puis server
pour lancer l'exécution du serveur ;
NB :
- par
défaut, le serveur se met en attente sur le port 8080 ;
- si vous
souhaitez qu'il utilise un autre numéro de port, tapez la
commande :
server -p numeroPort
- si vous obtenez l'erreur « ./server: erreur 'Address
already in use' ==> vous devez relancer le serveur en
précisant un autre numéro de port avec la
commande 'server -p numeroDePort' »,
alors relancez le serveur en précisant un autre
numéro de
port que le numéro que vous venez d'utiliser ;
- dans un navigateur Web, tapez l'adresse
http://localhost:8080
(ou un autre numéro de port, si vous avez
démarré
votre serveur avec un autre numéro de port) ;
- le navigateur Web affiche : « compteurReponse = 1 »
(le serveur indique qu'il a été
interrogé une fois et donc qu'il a
répondu une fois) ;
- si
vous cliquez plusieurs fois sur le bouton
« Rafraîchir / Actualiser la
page courante » du navigateur, votre navigateur
interroge à chaque fois
le serveur qui renvoie donc successivement : « compteurReponse
= 2 », « compteurReponse
= 3 »... ;
- arrêtez votre serveur (à l'aide
des touches Control
et C).
Pour fonctionner, ce serveur utilise 4 modules : codeEtudiant.c,
common.c,
main.c
et server.c.
Mais, dans les questions suivantes, vous serez amené
à
modifier seulement codeEtudiant.c
(c'est pourquoi c'est le seul module sur lequel vous avez les droits en
écriture). En effet, ce module héberge 3
fonctions qui interagissent
avec le reste du code :
- la procédure init
réalise toutes les
initialisations de votre module avant que le serveur web ne se mette en
attente de requêtes Web. Ainsi, dans le code livré
dans le répertoire Q3-1,
cette
procédure initialise à 0
une variable appelée compteurReponse
;
- la
procédure gestion_connexion_entrante
est appelée lorsque le serveur web
reçoit une requête Web. Cette procédure
(qui est appelée avec un
paramètre connexion_fd)
est chargée de confier à un thread
la responsabilité d'appeler la
procédure
handle_connection
(définie dans un des autres modules que vous n'avez pas
à modifier)
avec la valeur de ce paramètre connexion_fd.
Ainsi,
dans le code livré dans le répertoire Q3-1, cette
procédure crée un
thread thread_gestion_connexion_entrante
(avec en paramètre la
valeur de connexion_fd).
Et ce thread
se charge, quand il
prend la main, d'appeler handle_connection
avec la valeur
de connexion_fd
;
- la fonction gestion_compteurReponse
est
chargée d'effectuer les traitements liés
à la variable compteurReponse
lorsque le serveur répond à une requête
HTTP. Elle modifie la valeur de
compteurReponse,
puis retourne la valeur
courante de compteurReponse.
Ainsi, dans le code livré dans le répertoire Q3-1, cette
fonction incrémente compteurReponse,
puis retourne sa valeur.
Les questions Q3-1 à Q3-4 sont destinées
à
améliorer le fonctionnement de ce serveur. Noter que seront
évalués :
- la clarté du code,
- le fait que le code compile (sans warning),
- le fait que le retour de chaque appel
système, s'il y en a un,
est testé avec appel à
perror() ,
puis exit(EXIT_FAILURE)
en cas de problème détecté,
- le fait que le code répond correctement
à la question posée.
Question 3-1 : Empêcher les pertes
d'incrémentation de compteurReponse
(2 points)
La
variable compteurReponse
peut être manipulée par plusieurs threads
travaillant en
parallèle. On peut donc potentiellement perdre des
incrémentations de
cette variable. Pour tenter de l'observer, utilisons ab, l'outil
de benchmark d'Apache,
qui permet d'envoyer un certain nombre de
requêtes vers un serveur, dans un laps de temps
très court. Effectuez les opérations
suivantes :
- dans un terminal, démarrez le serveur ;
- dans
un autre terminal, tapez la commande :
ab -n 1000 -c 6 http://localhost:8080/
(où 1000
désigne le nombre d'invocations au serveur et 6
désigne le
nombre de
connexions simultanées faites au serveur). ab
vous indique les performances de votre serveur ;
- dans un navigateur, tapez
l'adresse http://localhost:8080
: à cause de ces pertes d'incrémentation
potentielle, il est possible que le
navigateur affiche une valeur de compteurReponse
inférieure à la valeur attendue,
c'est-à-dire
1001 (1000
requêtes réalisées par ab
et 1 requête réalisée
par votre navigateur) ;
- arrêtez votre serveur.
Dans
le répertoire Q3-1,
modifiez codeEtudiant.c
pour garantir qu'il n'y
aura aucune perte d'incrémentation de compteurRequete.
Question 3-2 : Sauvegarde de compteurReponse
sur disque (2,5 points)
Jusqu'à
présent, lorsque l'on arrête le serveur, la valeur
courante de
compteurReponse
est perdue. On se propose donc de mémoriser sur disque sa
valeur dans un fichier sauvegardeCompteurReponse
:
- recopiez Q3-1/codeEtudiant.c
dans Q3-2
;
- modifiez Q3-2/codeEtudiant.c
de sorte que :
- au
démarrage du serveur :
- si le fichier sauvegardeCompteurReponse
n'existe pas, il est créé (avec les droits de
lecture-écriture pour vous-même, le groupe et les
autres,
avant modification par le umask)
et compteurReponse
est initialisé à 0 ;
- si le fichier sauvegardeCompteurReponse
existe, compteurReponse
est initialisé à la valeur
mémorisée dans ce fichier ;
- à chaque appel de gestion_compteurReponse,
on sauvegarde la valeur de compteurReponse
dans le fichier.
NB
:
- la valeur de compteurReponse
sera stockée dans le fichier sauvegardeCompteurReponse
sous forme
binaire
ou textuelle, selon votre préférence.
- le fichier sauvegardeCompteurReponse
sera ouvert avec ou sans l'option O_SYNC,
selon votre préférence. En revanche, vous
veillerez
à justifier (dans un commentaire au niveau de l'instruction
d'ouverture du fichier) votre décision par rapport
à
l'utilisation ou la non-utilisation de O_SYNC.
Question 3-3 : Améliorer les performances de
la sauvegarde sur disque (2,5 points)
En
utilisant ab,
on constate que le serveur réalisé à
la question 3-2 voit
ses performances chuter par rapport à celui de la question
3-1. C'est
pourquoi, dans cette question, on met en œuvre
une architecture
logicielle préservant les performances de la question 3-1
tout en
conservant la sauvegarde de compteurReponse
abordée à la question 3-2. Le principe de cette
architecture est de confier l'écriture sur
disque à un thread
spécifique qui
écrit, chaque seconde, la valeur courante de compteurReponse
dans le
fichier sauvegardeCompteurReponse.
Certes, si le serveur tombe, on perd le
décompte de requêtes effectuées depuis
la dernière seconde où ce thread
spécifique s'est activé, mais on
préserve les performances du serveur !
- recopiez Q3-2/codeEtudiant.c
dans Q3-3
;
- modifiez Q3-3/codeEtudiant.c
de sorte que :
- au
démarrage du serveur :
- si le fichier sauvegardeCompteurReponse
n'existe pas, il est créé (avec les droits de
lecture-écriture pour vous-même, le groupe et les
autres)
et compteurReponse
est initialisé à 0 ;
- si le fichier sauvegardeCompteurReponse
existe, compteurReponse
est initialisé à la valeur
mémorisée dans ce fichier ;
- on crée ce thread
spécifique ;
- ce thread
spécifique exécute une boucle sans fin dans
laquelle :
-
il écrit la valeur courante de compteurReponse
dans sauvegardeCompteurReponse
;
- il dort pendant une seconde ;
- à chaque appel de gestion_compteurReponse,
on se contente d'incrémenter compteurReponse
de 1 (sans faire aucune écriture dans le fichier sauvegardeCompteurReponse).
NB
: le fichier sauvegardeCompteurReponse
sera ouvert avec ou sans l'option O_SYNC,
selon votre préférence. En revanche, vous
veillerez à justifier (dans
un commentaire au niveau de l'instruction d'ouverture du fichier) votre
décision par rapport à l'utilisation ou la
non-utilisation de O_SYNC.
Question 3-4 (question bonus) : Utilisation d'un pool de threads (bonus de 2
points)
Dans
le code actuel, à chaque requête, on
crée un thread
dédié, ce
qui
prend du temps et limite donc les performances globales du serveur.
- recopiez Q3-3/codeEtudiant.c
dans Q3-4
;
- modifiez Q3-4/codeEtudiant.c
de sorte que :
- au
démarrage du serveur, outre les traitements mis en
œuvre à la question
Q3-3, on crée 20
threads
qui se mettent en attente de travail ;
- lors de l'appel à gestion_connexion_entrante,
on confie le traitement de cette connexion à l'un des threads inactifs de
ce pool ;
- lorsqu'un thread
a terminé son appel à handle_connection,
il se remet en attente de travail.
Question 4 : Priorité aux
rédacteurs
(3 points)
NB
: Cette question est difficile. Nous vous recommandons donc de ne
l'aborder que quand vous aurez terminé les autres questions.
Le
transparent 3.5.2 (Solution de base) du cours
« Synchronisation
entre
processus » présente une
implémentation du
paradigme lecteurs/rédacteurs
qui donne priorité aux lecteurs. Le transparent 3.5.4
(Solution avec
priorités égales) présente une
implémentation de ce paradigme qui met
lecteurs et rédacteurs au même niveau. Ainsi, si
on a la
séquence de demande de lecture/écriture suivante :
- demande de lecture Lecteur1,
- demande de lecture Lecteur2,
- demande d'écriture Rédacteur1,
- demande de lecture Lecteur3,
- demande d'écriture Rédacteur 2,
l'algorithme répond aux demandes de la manière
suivante :
- demande de lecture Lecteur1 accordée,
- demande de lecture Lecteur2 accordée,
- demande d'écriture Rédacteur1
accordée,
- demande de lecture Lecteur3 accordée,
- demande d'écriture Rédacteur2
accordée.
Proposez un algorithme (basé sur des appels aux fonctions P() et V()) pour que
le paradigme donne
systématiquement la priorité aux
écrivains. Ainsi,
dans le cas de la séquence de demande de
lecture/écriture évoquée
précédemment, cet algorithme répondra
aux demandes
de la manière suivante :
- demande de lecture Lecteur1 accordée,
- demande de lecture Lecteur2 accordée,
- demande d'écriture Rédacteur1
accordée,
- demande
d'écriture
Rédacteur2 accordée,
- demande de lecture
Lecteur3 accordée.
Pour cette question, répondez sur votre
copie
ou
bien dans le fichier Q4/reponse4.txt (dans ce
dernier
cas, indiquez sur votre
copie « Réponse dans Q4/reponse4.txt »).
|