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


    Évaluation



Institut National des Télécommunications
Télécom INT 2è année

TP Noté CSC4508/M2 du 22/06/07

(2è session)

Corrigés

Modalités

Durée : 1 heure 30

Tous documents autorisés.

Les questions 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 1 heure 30 ») du fichier d'extension tgz constitué de la manière suivante :
cd votreRepertoireDeTravailPourCSC4508M2
tar cvfz $USER.tgz TPNote2007Session2

Préparation

cd votreRepertoireDeTravailPourCSC4508M2
cp ~silber/Cours/CSC4508/tPNote2007Session2.tgz .
tar xvfz tPNote2007Session2.tgz
cd TPNote2007Session2

Question 1 : Verrou pour la banque (4 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 » ).

Pour l'exercice hors présentiel Entrées-Sorties #7 (Gestion d'une banque), on souhaite écrire une procédure qui se charge de mettre à jour le solde d'un compte.

Trois versions sont envisagées :
 








void majCompteVersion1(int fd, int numCompte, typSolde nouveauSolde){
verrou.l_type = F_WRLCK;
verrou.l_whence = SEEK_SET;
verrou.l_start = numCompte*sizeof(typSolde);
verrou.l_len = sizeof(typSolde);
assert(fcntl(fd, F_SETLKW, &verrou) >= 0);

assert( lseek(fd, numCompte*sizeof(typSolde), SEEK_SET) >= 0);

assert(write(fd, &solde, sizeof(typSolde)) == sizeof(typSolde));

verrou.l_type = F_UNLCK;
assert(fcntl(fd, F_SETLK, &verrou) >= 0);
 }
 
void majCompteVersion2(int fd, int numCompte, typSolde nouveauSolde){
verrou.l_type = F_WRLCK;
verrou.l_whence = SEEK_SET;
verrou.l_start = 0;
verrou.l_len = 0; /* Ce 0 signifie "jusqu'a la fin du fichier" */
assert(fcntl(fd, F_SETLKW, &verrou) >= 0);

assert( lseek(fd, numCompte*sizeof(typSolde), SEEK_SET) >= 0);

assert(write(fd, &solde, sizeof(typSolde)) == sizeof(typSolde));

verrou.l_type = F_UNLCK;
assert(fcntl(fd, F_SETLK, &verrou) >= 0);
 }
 
void majCompteVersion3(int fd, int numCompte, typSolde nouveauSolde){
assert( lseek(fd, numCompte*sizeof(typSolde), SEEK_SET) >= 0);

verrou.l_type = F_WRLCK;
verrou.l_whence = SEEK_SET;
verrou.l_start = numCompte*sizeof(typSolde);
verrou.l_len = sizeof(typSolde);
assert(fcntl(fd, F_SETLKW, &verrou) >= 0);
);


assert(write(fd, &solde, sizeof(typSolde)) == sizeof(typSolde));

verrou.l_type = F_UNLCK;
assert(fcntl(fd, F_SETLK, &verrou) >= 0);
 }

Quelle version de procédure permet d'avoir le plus de mise-à-jour simultanées sur le fichier des comptes ? Justifier votre réponse.
Quelle version de procédure réduit le plus le nombre de mise-à-jour simultanées sur le fichier des comptes ? Justifier votre réponse.






Question 2 : Lecteur/rédacteur et impression, le retour (7 points)

Pour chacune des questions ci-dessous, 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 »).

On considère l'exercice hors présentiel Communication inter-processus/Synchronisation entre processus #6 (Lecteur/rédacteur et impression). Pour cette application, le corrigé propose que :
  • le serveur d'impression et ses clients soient considérés comme des rédacteurs de la zone de mémoire partagée où sont stockés les noms de fichier à imprimer (puisqu'ils modifient le contenu de cette zone) ;
  • les différentes instances de l'application de consultation de la liste des fichiers à imprimer soient considérées comme des lecteurs de la zone de mémoire partagée où sont stockés les noms de fichier à imprimer (puisqu'elles ne font que lire le contenu de cette zone).
De ce fait, les algorihtmes suivants sont proposés :

Variables globales
==================
fichiersAImprimer Tableau[0..NBMAXNOMFIC - 1] de chaîne de caractères
iExtrait Entier = 0
iDepot Entier = 0
// Données liées à la synchronisation Producteurs/Consommateur
Sémaphore placeDispo initialisé à
NBMAXNOMFIC
Sémaphore infoPrete initialisé à 0
Sémaphore mutexP initialisé à 1
// Données liées à la synchronisation Lecteurs/Rédacteurs
nbLec Entier = 0
Sémaphore mutexL initialisé à 1
Sémaphore fifo initialisé à 1
Sémaphore mutexG initialisé à 1

Tâche serveur

=============
  faire
    P(fifo);
    P(mutexG);
    V(fifo);

    P(infoPrete);
    impression(fichiersAImprimer[shm->iExtrait]);
    V(placeDispo);
    iExtrait = (iExtrait+1) % NBMAXNOMFIC;

    V(mutexG);

  tant que VRAI
 













Tâche client

============
  faire
    afficherALEcran("Nom du fichier a imprimer (taper '0' pour terminer) ? ");
    lireAuClavier(nomFic);
    si (nomFic est égal à la chaîne "0") alors
      arrêter la tâche
    finsi
    P(fifo);
    P(mutexG);
    V(fifo);

    P(placeDispo);
    P(mutexP);
    fichiersAImprimer[shm->iDepot] = nomFic;
    iDepot = (iDepot + 1) % NBMAXNOMFIC;
    V(mutexP);
    V(infoPrete);

    V(mutexG);
  tant que VRAI

Tâche consultant
================
  P(fifo);
  P(mutexL);
  nbLec += 1;
  if (nbLec == 1) {
    P(mutexG);
  }
  V(mutexL);
  V(fifo);
  affichageTravauxEnAttente();
  P(mutexL);
  nbLec -= 1;
  if (nbLec == 0) {
    V(mutexG);
  }
  V(mutexL);

Question 2.1 : Où on se rend compte que le corrigé n'est pas très astucieux (2 points)

Les algorithmes ci-dessus ne permettent pas au serveur de retirer un nom de fichier de fichiersAImprimer pendant que simultanément des clients ajoutent des noms de fichiers. Expliquez pourquoi.

Question 2.2 : Vers un corrigé plus astucieux (5 points)

Modifiez les algorithmes ci-dessus pour faire évoluer l'implémentation du lecteur/rédacteur de sorte que le serveur puisse s'exécuter en parallèle de clients quand il n'y a aucune application de consultation en train de s'exécuter.

Question 3 : Somme des n premiers entiers (9 points)

Pour chacune des questions ci-dessous, répondez sur votre copie ou bien dans le fichier Q3/reponse3.txt (dans ce dernier cas, indiquez sur votre copie « Réponse dans Q3/reponse3.txt »).

Le code du fichier sommeEntier.c calcule la somme des n premiers entiers, n étant fourni en argument du programme.


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct {
  int donnee;
  int resultat;
} typSomme;


void *calculSomme(void *arg)
{
  if (((typSomme *)arg)->donnee == 1){
    ((typSomme *)arg)->resultat = 1;
  }else{
    typSomme ts;
    pthread_t thread;
    int rc;

    ts.donnee = ((typSomme *)arg)->donnee - 1;
    rc = pthread_create(&thread, NULL, calculSomme, &ts);
    if (rc){
    perror("pthread_create");
    fprintf(stderr, "((typSomme *)arg)->donnee valait %d\n",
               ((typSomme *)arg)->donnee);
    exit(EXIT_FAILURE);
    }
    rc = pthread_join(thread, NULL);
    ((typSomme *)arg)->resultat = ((typSomme *)arg)->donnee + ts.resultat;   
  }

  pthread_exit(NULL);
}

int main(int argc, char **argv)
{
  int n;
  typSomme ts;
  pthread_t thread;
  int rc;
 
  if (argc < 2)
    {
      fprintf(stderr, "Usage: %s entierStrictementPositif\n", argv[0]);
      exit(EXIT_FAILURE);
    }
 
  n = atoi(argv[1]);
  if (n <= 0){
    fprintf(stderr, "Usage: %s entierStrictementPositif\n", argv[0]);
    exit(EXIT_FAILURE);
  }
 
  ts.donnee = n;
  rc = pthread_create(&thread, NULL, calculSomme, &ts);
  if (rc){
    perror("pthread_create");
    exit(EXIT_FAILURE);
  }
  rc = pthread_join(thread, NULL);

  printf("Somme des %d premiers entiers = %d\n", n, ts.resultat);
  return EXIT_SUCCESS;
}

Question 3.1 : Fonctionnement du programme (3 points)

Expliquez le fonctionnement de ce programme.

Question 3.2 : Limite du programme (2 points)

Lorsqu'on exécute sommeEntier 2000 sur une machine de la salle B07-03, le programme affiche l'erreur suivante :

pthread_create: Cannot allocate memory
((typSomme *)arg)->donnee valait 1697

Expliquez ce message d'erreur.
Déduire de cet affichage le plus grand entier pour lequel sommeEntier fonctionne.

Question 3.3 : Repousser les limites (2 points)

On se propose de repousser les limites observées précédemment en utilisant les primitives pthread_attr_init() et pthread_attr_setstacksize() pour créer des threads avec une pile de 16 Ko (Cette valeur est la taille minimale de pile de thread sur un Linux en environnement PC).
  • Expliquez comment ces primitives et cette valeur de 16 Ko peuvent aider à repousser les limites.
  • Indiquez pourquoi on n'a pas de risque à créer des threads avec de si petites piles.
  • Modifiez le code du fichier sommeEntierQ3.3.c pour que :
    • il affiche (à l'aide de pthread_attr_setstacksize()) la quantité de pile utilisée par défaut pour la création d'un thread ;
    • il crée des threads avec une pile de 16 Ko
  • L'exécution sommeEntierQ3.3 16229 affiche le résultat "Somme des 16229 premiers entiers = 131698335", mais sommeEntierQ3.3 16230 donne un affichage semblable à ce qui suit :
Taille de la pile (par défaut) = 10485760
Taille de la pile (après setstacksize) = 16384
pthread_create: Resource temporarily unavailable
Erreur de segmentation


Commentez cet affichage (en expliquant notamment quel aurait dû être le plus grand entier pour lequel sommeEntier fonctionne, compte tenu de cette taille de pile de 16 Ko).

Question 3.4 : Une autre manière de communiquer son résultat (2 points)

Dans le code utilisé actuellement, un thread communique son résultat au thread qui l'a créé en utilisant le champ resultat de la structure typSomme. Modifiez le fichier sommeEntierQ3.3.c pour que le thread communique son résultat via la fonction pthread_exit().



Page mise à jour le 18 juin 2007