Département INFormatique 
  CSC4508/M2 : Concepts des systèmes d'exploitation et mise en œuvre sous Unix


    Évaluation



TELECOM & Management SudParis
TELECOM SudParis 2ème année

TP Noté CSC4508/M2 du 16/05/08

Corrigés

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.ccommon.cmain.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 »).



Page mise à jour le 15 avril 2009