Synchronisation multicouche avec tampons partagés (INF3600/2610)
Ce document présente un ensemble d'exercices sur la synchronisation de processus concurrents à l'aide de sémaphores et de moniteurs, dans le cadre d'un examen ou d'un devoir en informatique.
D'après le document Synchronisation multicouche avec tampons partagés (INF3600/2610)
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.
Document source
Programming, Math, Synchronization · PDF · 22 pages · 2006
Afficher l'aperçu du document
Ce document présente un ensemble d'exercices sur la synchronisation de processus concurrents à l'aide de sémaphores et de moniteurs, dans le cadre d'un examen ou d'un devoir en informatique. Il teste la compréhension des mécanismes de synchronisation, la gestion des accès aux ressources partagées, la prévention des interblocages, et la capacité à écrire des pseudocodes corrects pour des problèmes classiques de synchronisation.
Exercice 1
On considère un système multicouche avec trois processus P0, P1 et P2, communiquant via deux tampons partagés T0 et T1 de taille N. Il s'agit de synchroniser ces processus pour éviter les conflits d'accès et interblocages.
a) Utilisation des sémaphores pour contrôler l'accès aux tampons
Chaque tampon partagé doit être protégé par un mécanisme d'exclusion mutuelle pour éviter que deux processus n'y accèdent simultanément. On utilise un sémaphore mutex pour chaque tampon, initialisé à 1, garantissant l'exclusion mutuelle.
De plus, pour gérer la production et la consommation des messages dans chaque tampon, on utilise deux sémaphores par tampon :
- Vide : nombre de cases libres dans le tampon, initialisé à N.
- Plein : nombre de cases occupées dans le tampon, initialisé à 0.
Ces sémaphores permettent de contrôler que le producteur ne dépose pas dans un tampon plein et que le consommateur ne retire pas d'un tampon vide.
Pour éviter l'interblocage, il faut que les processus respectent un ordre strict d'acquisition des sémaphores et que les opérations P (wait) et V (signal) soient bien placées.
b) Pseudocodes des processus
On note :
- mutexT0, pleinT0, videT0 pour le tampon T0
- mutexT1, pleinT1, videT1 pour le tampon T1
Processus P0 {
while (true) {
Message m = Lire(); // lire message du clavier
P(videT0);
P(mutexT0);
Deposer(m, T0);
V(mutexT0);
V(pleinT0);
}
}
Processus P1 {
while (true) {
P(pleinT0);
P(mutexT0);
Message m = Retirer(T0);
V(mutexT0);
V(videT0);
Message mEnc = Encrypter(m);
P(videT1);
P(mutexT1);
Deposer(mEnc, T1);
V(mutexT1);
V(pleinT1);
}
}
Processus P2 {
while (true) {
P(pleinT1);
P(mutexT1);
Message m = Retirer(T1);
V(mutexT1);
V(videT1);
Envoyer(m);
}
}
Réponse : Les sémaphores mutex, plein et vide permettent de garantir l'exclusion mutuelle et la synchronisation correcte entre les producteurs et consommateurs sur les tampons T0 et T1.
Exercice 2
1) Effet d'une file LIFO pour un sémaphore
Si la file d'attente d'un sémaphore est gérée selon la discipline LIFO (last in first out), le dernier processus à attendre sera servi en premier. Cela peut entraîner une inversion de priorité et potentiellement une famine pour les processus plus anciens, car les nouveaux entrants peuvent toujours passer avant eux.
2) Avantage des moniteurs sur les sémaphores
Les moniteurs encapsulent la synchronisation et la gestion des variables partagées dans une structure plus sûre et plus simple à utiliser que les sémaphores, qui sont des primitives plus basses. Les moniteurs évitent les erreurs de programmation liées à un mauvais usage des opérations P et V, et facilitent la maintenance et la compréhension du code.
3) Complétion du producteur/consommateur avec sémaphores
Le producteur produit M caractères à la fois (1 ≤ M ≤ N), tandis que le consommateur consomme un caractère à la fois. Les sémaphores Plein, Vide et Mutex sont utilisés pour gérer le tampon circulaire T de taille N.
Producteur {
int ip = 0, M;
char ch[N];
while (true) {
M = Lire(ch, N); // lit une chaîne de longueur M
for (int i = 0; i < M; i++) {
P(Vide); // attendre qu'il y ait de la place
}
P(Mutex);
Deposer(ch, M, ip);
ip = (ip + M) % N;
V(Mutex);
for (int i = 0; i < M; i++) {
V(Plein); // signaler la disponibilité de M caractères
}
}
}
Consommateur {
int ic = 0;
char c;
while (true) {
P(Plein);
P(Mutex);
c = Retirer(ic);
ic = (ic + 1) % N;
V(Mutex);
V(Vide);
Traiter(c);
}
}
Réponse : Les sémaphores Plein, Vide et Mutex sont utilisés pour assurer la synchronisation correcte entre producteur et consommateur, même si le producteur produit plusieurs caractères à la fois.
Exercice 3
1) Synchronisation des moines Shaolins
Trois moines ont chacun un bol et une baguette, mais pour manger, ils ont besoin de deux baguettes. Pour éviter l'interblocage, on peut utiliser un seul sémaphore comptant le nombre total de baguettes disponibles (ici 3). Chaque moine attend que deux baguettes soient disponibles avant de manger. Ainsi, on utilise un sémaphore initialisé à 2 (nombre de baguettes nécessaires), et chaque moine effectue P(2) avant de manger et V(2) après.
2) Ordonnanceur pour deux processeurs avec files alternées
Le thread ordonnanceur doit remplir alternativement les deux files d'attente (buffers), chacune pouvant contenir 4 processus. On utilise les sémaphores sem_libre et sem_occupe pour contrôler les cases libres et occupées de chaque buffer.
void *ordonnanceur(void *inutilise) {
int i[NB_BUFF], buff;
for (buff = 0; buff < NB_BUFF; buff++) i[buff] = 0;
buff = 0;
while (1) {
P(&sem_libre[buff]); // attendre qu'il y ait une place libre dans le buffer courant
tab[buff][i[buff]] = prochainProc(); // ajouter un processus dans le buffer courant
V(&sem_occupe[buff]); // signaler qu'une case est occupée
i[buff] = (i[buff] + 1) % BUFF_SIZE; // avancer l'indice
buff = (buff + 1) % NB_BUFF; // passer au buffer suivant
}
}
Réponse : Le thread ordonnanceur remplit alternativement les deux buffers en respectant leur capacité, sans ajouter de sémaphores ou variables supplémentaires.
Exercice 4
1) Synchronisation des processus P1, P2 et P3
Les processus P1 et P2 doivent exécuter leurs cycles en concurrence, puis P3 doit exécuter un cycle après que P1 et P2 aient tous deux terminé leur cycle. Ensuite, P1 et P2 recommencent un nouveau cycle après que P3 ait terminé.
On peut utiliser trois sémaphores :
- S12 initialisé à 1, pour synchroniser P1 et P2 (exécution simultanée)
- S3 initialisé à 0, pour que P3 attende la fin de P1 et P2
- Un compteur partagé pour compter les terminaisons de P1 et P2
Variables:
Semaphore S12 = 1; // contrôle l'accès aux cycles de P1 et P2
Semaphore S3 = 0; // P3 attend que P1 et P2 terminent
int compteur = 0; // nombre de processus P1/P2 terminés
Processus P1 {
while (true) {
P(S12);
printf("cycle %d de 1", n++);
P(mutex_compteur);
compteur++;
if (compteur == 2) V(S3);
V(mutex_compteur);
V(S12);
// attendre que P3 termine
P(S3);
V(S3);
}
}
Processus P2 identique à P1, avec printf("cycle %d de 2", n++)
Processus P3 {
while (true) {
P(S3);
printf("cycle %d de 3", n++);
compteur = 0;
V(S3);
}
}
Réponse : Les sémaphores permettent de synchroniser les cycles selon les contraintes données.
2) Modélisation de l'intersection routière
Le système modélise une intersection avec 3 voies nord-sud et 2 voies est-ouest, avec des règles spécifiques de feu vert et de flèches. La synchronisation classique utilisée est un exclusion mutuelle stricte entre les phases de feu vert est-ouest, nord-sud et flèche.
On identifie :
- Un processus léger pour la gestion des feux (synchronisation des phases)
- Un processus par voie pour gérer les files d'attente des voitures
- Des sémaphores pour chaque phase de feu (est-ouest, nord-sud, flèche), initialisés à 1 ou 0 selon l'état initial
- Des mutex pour protéger les files d'attente (listes STL)
Réponse : Le système utilise un modèle de synchronisation par exclusion mutuelle entre phases de feu, avec des sémaphores et mutex pour gérer les files et l'alternance des feux.
Exercice 5
1) Synchronisation de la communication entre deux processus A et B avec un tampon unique
Le tampon T contient un seul message à la fois, utilisé alternativement par A et B. Il faut synchroniser les dépôts et récupérations pour éviter les conflits.
On définit deux sémaphores :
- sem_depot initialisé à 1 (autorise le dépôt)
- sem_recup initialisé à 0 (attente de la récupération)
Semaphore sem_depot = 1;
Semaphore sem_recup = 0;
char T[256];
Processus A {
while (true) {
lire(mess);
P(sem_depot); // attendre que le tampon soit libre pour déposer
depot(mess);
V(sem_recup); // signaler que le message est prêt à être récupéré
P(sem_depot); // attendre la réponse
recuperer(rep);
V(sem_depot); // autoriser un nouveau dépôt
}
}
Processus B {
while (true) {
P(sem_recup); // attendre un message à récupérer
recuperer(mess);
reponse(mess, rep);
depot(rep);
V(sem_depot); // signaler que la réponse est déposée
}
}
Réponse : Les sémaphores sem_depot et sem_recup assurent la synchronisation alternée entre A et B.
2) Ajout d'un troisième processus C communiquant avec B via le même tampon
Le processus B doit pouvoir recevoir des messages de A ou C, et répondre au bon expéditeur. Il faut donc distinguer les sémaphores et tampons pour chaque expéditeur, ou utiliser un mécanisme d'identification des messages.
Comme il n'y a qu'un seul tampon, on peut utiliser deux sémaphores sem_depot_A, sem_depot_C et sem_recup_A, sem_recup_C, chacun initialisé comme précédemment, pour gérer les échanges séparément. B attend sur les deux sémaphores sem_recup_A et sem_recup_C (par exemple en alternance ou avec un mécanisme de priorité).
Réponse : La synchronisation nécessite des sémaphores distincts par expéditeur pour éviter les conflits sur le tampon unique.
Exercice 6
Synchronisation de la fabrication de stylos à bille
Les opérations sont :
- RC : remplissage de la cartouche
- BO : assemblage du bouchon arrière et du corps
- AS : assemblage de la cartouche avec le corps et le capuchon (doit attendre RC et BO)
- EM : emballage (doit attendre AS)
RC et BO se font en parallèle. AS doit prélever les éléments sur mRC et mBO (opération GP()). EM attend le stock issu d'AS.
On utilise :
- Un tampon stock de taille N (FIFO) pour stocker les produits AS
- Des sémaphores Plein, Vide, Mutex pour gérer ce tampon
- Des barrières ou sémaphores pour synchroniser AS avec RC et BO
mRC() {
while (true) {
RC();
V(sem_RC); // signaler fin RC
}
}
mBO() {
while (true) {
BO();
V(sem_BO); // signaler fin BO
}
}
mAS() {
while (true) {
P(sem_RC); // attendre RC
P(sem_BO); // attendre BO
GP(); // prélever éléments
AS();
P(Vide);
P(Mutex);
DeposerProduit();
V(Mutex);
V(Plein);
}
}
mEM() {
while (true) {
P(Plein);
P(Mutex);
RetirerProduit();
V(Mutex);
V(Vide);
EM();
}
}
Réponse : Les sémaphores permettent de synchroniser les opérations selon leurs dépendances et de gérer le stock FIFO.
Exercice 7
Contrôle de déplacement de robots sur segments exclusifs
Les segments AB, BC et BD doivent être utilisés par un seul robot à la fois. Deux types de robots : RobotAC (A vers C) traverse AB puis BC, RobotDA (D vers A) traverse BD puis AB.
On utilise un sémaphore par segment, initialisé à 1 :
- semAB = 1
- semBC = 1
- semBD = 1
Processus RobotAC {
P(semAB); // 1 - traverser AB
TraverserSegAB();
V(semAB);
P(semBC); // 2 - traverser BC
TraverserSegBC();
V(semBC);
}
Processus RobotDA {
P(semBD); // 4 - traverser BD
TraverserSegBD();
V(semBD);
P(semAB); // 5 - traverser AB
TraverserSegAB();
V(semAB);
}
Réponse : L'utilisation de sémaphores par segment garantit l'exclusion mutuelle sur chaque segment, évitant collisions.
Problèmes potentiels : Pas d'interblocage si l'ordre d'acquisition est respecté (RobotAC acquiert AB puis BC, RobotDA acquiert BD puis AB). Cependant, il peut y avoir un risque d'interblocage si RobotAC attend BC et RobotDA attend AB simultanément. Une solution est d'imposer un ordre global d'acquisition des sémaphores.
Exercice 8
a) Compléter les procédures avec sémaphores
On modélise une intersection avec deux voies à sens unique, un feu vert alternant entre voie 1 et voie 2. Une seule voiture peut traverser à la fois.
On définit :
- Semaphore mutex = 1 pour l'exclusion mutuelle sur l'intersection
- Semaphore feu1 et feu2 pour indiquer quel feu est vert (initialement feu1 = 1, feu2 = 0)
void Traversee1() {
P(feu1); // attendre feu vert voie 1
P(mutex); // entrer intersection
circuler();
V(mutex); // libérer intersection
V(feu2); // passer feu vert à voie 2
}
void Traversee2() {
P(feu2);
P(mutex);
circuler();
V(mutex);
V(feu1);
}
b) Compléter le moniteur
Variables :
- Feu = 1 ou 2
- Nbw1, Nbw2 : nombre de voitures sur voie 1 et voie 2
- Variables de condition pour attendre le feu vert
Le moniteur contrôle les entrées et sorties des voitures, en bloquant celles dont le feu est rouge.
Réponse : Le moniteur utilise des variables de condition pour faire attendre les voitures au feu rouge et les réveiller lorsque le feu passe au vert, garantissant l'exclusion mutuelle sur l'intersection.
Exercice 9
Implémentation d'un compteur d'événements avec sémaphores
Un compteur d'événements E a :
- val : valeur actuelle
- Une file d'attente de processus en attente
On ajoute :
- Semaphore mutex pour exclusion mutuelle
- Semaphore sem pour bloquer les processus en attente
struct CompteurEvenement {
int val;
Semaphore mutex = 1;
Semaphore sem = 0;
int attente = 0; // nombre de processus en attente
};
void Await(CompteurEvenement *E, int Valeur) {
P(E->mutex);
if (E->val < Valeur) {
E->attente++;
V(E->mutex);
P(E->sem); // bloquer le processus
P(E->mutex);
E->attente--;
}
V(E->mutex);
}
void Advance(CompteurEvenement *E) {
P(E->mutex);
E->val++;
for (int i = 0; i < E->attente; i++) {
V(E->sem); // réveiller tous les processus en attente
}
V(E->mutex);
}
int Read(CompteurEvenement *E) {
int v;
P(E->mutex);
v = E->val;
V(E->mutex);
return v;
}
Réponse : Cette implémentation assure la synchronisation correcte des processus attendant une valeur donnée du compteur.
Exercice 10
Passage à niveau à voie unique
Le contrôleur ferme les barrières lorsqu'un train traverse, et les ouvre s'il n'y a aucun train en attente.
On utilise :
- Semaphore mutex pour exclusion mutuelle
- Semaphore train_attente pour compter les trains en attente
- Semaphore barriere_fermee pour indiquer que la barrière est fermée
Semaphore mutex = 1;
Semaphore train_attente = 0;
Semaphore barriere_fermee = 0;
Contrôleur() {
while (true) {
P(train_attente);
FermerBarrieres();
V(barriere_fermee);
// attendre que le train ait traversé
P(mutex);
if (PNB(train_attente) == 0) {
OuvrirBarrieres();
}
V(mutex);
}
}
Train() {
while (true) {
P(mutex);
V(train_attente); // signaler arrivée
V(mutex);
P(barriere_fermee); // attendre barrière fermée
Traverser();
V(barriere_fermee); // signaler fin traversée
}
}
Réponse : Les sémaphores assurent la fermeture des barrières avant la traversée et l'ouverture après, en évitant les collisions.
Exercice 11
Vérification de l'implémentation d'échange de cours
Le code verrouille d'abord cours1, désinscrit l'utilisateur, puis teste si cours2 n'est pas plein. Si non plein, il verrouille cours2, inscrit l'utilisateur, puis déverrouille cours2. Enfin, il déverrouille cours1.
Problèmes :
- Il y a un risque d'interblocage si deux étudiants veulent échanger simultanément entre les mêmes cours dans l'ordre inverse (cours1 puis cours2 et inversement).
- Le verrouillage n'est pas ordonné, ce qui peut provoquer un deadlock.
- Si cours2 est plein, l'utilisateur est désinscrit de cours1 sans être inscrit ailleurs, ce qui est incorrect.
Solution :
- Verrouiller toujours les cours dans un ordre global (par exemple, ordre croissant des identifiants de cours) pour éviter les interblocages.
- Vérifier la disponibilité de cours2 avant de désinscrire de cours1.
- Effectuer l'échange de façon atomique, ou abandonner si l'échange n'est pas possible.
Réponse : L'implémentation est incorrecte. Il faut ordonner les verrous et vérifier la disponibilité avant désinscription pour éviter perte de cours et interblocage.
Exercice 12
a) Implémentation des barrières avec sémaphores
Une barrière synchronise n processus, bloquant chacun jusqu'à ce que tous atteignent la barrière.
class Barrier_t {
int count = 0;
int n; // nombre de processus
Semaphore mutex = 1;
Semaphore turnstile = 0;
Barrier_t(int nb) {
n = nb;
}
void Barriere() {
P(mutex);
count++;
if (count == n) {
for (int i = 0; i < n; i++) {
V(turnstile);
}
count = 0;
}
V(mutex);
P(turnstile);
}
}
b) Synchronisation de la chaîne de production de stylos
On utilise deux barrières :
- barriere1 pour synchroniser la fin des opérations RC et BO
- barriere2 pour synchroniser avant AS et EM
mRC() {
while (true) {
RC();
barriere1.Barriere();
}
}
mBO() {
while (true) {
BO();
barriere1.Barriere();
}
}
mAS() {
while (true) {
barriere1.Barriere();
GP();
AS();
barriere2.Barriere();
}
}
mEM() {
while (true) {
barriere2.Barriere();
GP();
EM();
}
}
c) Risque d'interblocage avec mauvaise utilisation des barrières
Une mauvaise utilisation des barrières, par exemple un nombre incorrect de processus attendus ou un ordre d'appel incohérent, peut bloquer tous les processus à la barrière, provoquant un interblocage.
Réponse : Oui, un usage incorrect des barrières peut entraîner un interblocage.
Exercice 13
1) Analyse du code avec mutex1 et mutex2
Le processus p1 acquiert mutex1 puis mutex2, modifie n et out, puis libère mutex2 puis mutex1.
Le processus p2 acquiert mutex2, modifie out, puis libère mutex2.
Le processus p3 acquiert mutex1, modifie n, puis libère mutex1.
Problème :
- Il y a un risque d'interblocage car p1 attend mutex1 puis mutex2, tandis que p2 attend mutex2 seul. Si p2 détient mutex2 pendant que p1 détient mutex1, p1 attend mutex2 et p2 attend mutex1 indirectement, causant un deadlock.
- La condition d'exclusion mutuelle n'est pas garantie car p3 modifie n sans coordination avec p1.
Solution :
- Utiliser un seul mutex pour protéger n et out.
- Ou imposer un ordre strict d'acquisition des mutex.
2) Produit de matrices en parallèle
On crée m threads, chacun calculant un groupe de lignes de la matrice résultat R.
Semaphore mutex = 1; // pour protéger l'écriture dans R si nécessaire
void CalculLignes(int debut, int fin) {
for (int i = debut; i < fin; i++) {
for (int j = 0; j < n; j++) {
R[i][j] = 0;
for (int k = 0; k < n; k++) {
R[i][j] += A[i][k] * B[k][j];
}
}
}
}
Réponse : Chaque thread calcule ses lignes sans conflit, donc mutex n'est pas nécessaire si R est segmenté par lignes.
Exercice 14
1) Modèle correspondant
Le problème des trains sur une voie unique correspond au modèle des philosophes ou lecteurs/rédacteurs, où l'accès exclusif est requis pour la voie, et les trains doivent circuler dans un seul sens à la fois.
2) Code avec sémaphores pour accès et sortie
Semaphore mutex = 1;
Semaphore voieLibre = 1;
int direction = 0; // 0 = libre, 1 = A vers B, 2 = B vers A
int nbTrains = 0;
void DemandeAccesAversB() {
P(mutex);
while (direction == 2) {
V(mutex);
P(voieLibre);
P(mutex);
}
direction = 1;
nbTrains++;
V(mutex);
}
void SortieAversB() {
P(mutex);
nbTrains--;
if (nbTrains == 0) {
direction = 0;
V(voieLibre);
}
V(mutex);
}
void DemandeAccesBversA() {
P(mutex);
while (direction == 1) {
V(mutex);
P(voieLibre);
P(mutex);
}
direction = 2;
nbTrains++;
V(mutex);
}
void SortieBversA() {
P(mutex);
nbTrains--;
if (nbTrains == 0) {
direction = 0;
V(voieLibre);
}
V(mutex);
}
Réponse : Ce code garantit qu'à un instant donné, la voie est utilisée par des trains dans un seul sens, évitant collisions.
Exercice 15
1) Producteurs et consommateurs multiples avec un tampon unique
Pour n producteurs, n consommateurs et un tampon de taille Max :
Semaphore Mutex = 1, Vide = Max, Plein = 0;
Message tampon[Max];
int ip = 0, ic = 0;
Producteur() {
while (true) {
Message m = creermessage();
P(Vide);
P(Mutex);
tampon[ip] = m;
ip = (ip + 1) % Max;
V(Mutex);
V(Plein);
}
}
Consommateur() {
while (true) {
P(Plein);
P(Mutex);
Message m = tampon[ic];
ic = (ic + 1) % Max;
V(Mutex);
V(Vide);
}
}
2) Un producteur, n consommateurs et n tampons
Le producteur dépose chaque message dans tous les tampons en commençant par le premier. Chaque consommateur i récupère dans le tampon i.
On utilise un tableau de sémaphores Mutex[i], Vide[i], Plein[i] pour chaque tampon i.
for (int i = 0; i < n; i++) {
Semaphore Mutex[i] = 1, Vide[i] = Max, Plein[i] = 0;
Message tampon[i][Max];
int ip[i] = 0, ic[i] = 0;
}
Producteur() {
while (true) {
Message m = creermessage();
for (int i = 0; i < n; i++) {
P(Vide[i]);
P(Mutex[i]);
tampon[i][ip[i]] = m;
ip[i] = (ip[i] + 1) % Max;
V(Mutex[i]);
V(Plein[i]);
}
}
}
Consommateur(int i) {
while (true) {
P(Plein[i]);
P(Mutex[i]);
Message m = tampon[i][ic[i]];
ic[i] = (ic[i] + 1) % Max;
V(Mutex[i]);
V(Vide[i]);
}
}
Réponse : Cette adaptation permet au producteur de déposer dans tous les tampons et aux consommateurs de récupérer dans leur tampon respectif.
Méthode
Ce devoir récompense une bonne compréhension des mécanismes fondamentaux de synchronisation : sémaphores, exclusion mutuelle, files d'attente, et moniteurs. Il est essentiel de :
- Respecter les conventions et notations données dans l'énoncé.
- Présenter clairement les sémaphores utilisés, leurs valeurs initiales et leur rôle.
- Montrer les étapes de raisonnement, notamment l'ordre d'acquisition des sémaphores pour éviter interblocages.
- Éviter les erreurs classiques comme la famine, l'interblocage, ou la mauvaise gestion des files d'attente.
- Utiliser des pseudocodes précis, avec les opérations P (wait) et V (signal) bien placées.
- Ne pas inventer d'informations absentes du sujet, et signaler les questions impossibles à traiter.
Les erreurs pénalisées sont notamment :
- Oublier l'exclusion mutuelle sur les ressources partagées.
- Ne pas gérer correctement les conditions de synchronisation (tampons pleins/vides).
- Introduire des variables ou sémaphores non autorisés.
- Ignorer les risques d'interblocage ou de famine.
- Ne pas justifier les choix de synchronisation.
Une bonne réponse montre un équilibre entre rigueur formelle, clarté du raisonnement et respect des contraintes imposées.
Commentaires
Aucun commentaire pour le moment. Posez la première question.