|
|
Institut National des
Télécommunications
Télécom INT 2è
année
TP Noté CSC4508/M2 du 22/06/07
(2è session)
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().
|