Portail informatique 2019-2020

Systèmes d'exploitation

Durée : 3 heures

Tous documents autorisés.

Les exercices 2, 3 et 4 sont indépendants les uns 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érents exercices.

cd votreRepertoireDeTravailPourCSC4508 wget http://www-inf.telecom-sudparis.eu/COURS/CSC4508/Supports/CF/2018-2019/cf1/TPNote2019.tgz tar xvfz TPNote2019.tgz mv TPNote2019 ${USER}_TPNote2019 cd ${USER}_TPNote2019

La « livraison » de votre travail en fin de TP noté se fera par remontée sous Moodle (rubrique « TP noté de 3 heures ») du fichier d'extension tgz constitué de la manière suivante :

cd votreRepertoireDeTravailPourCSC4508 tar cvfz ${USER}.tgz ${USER}_TPNote2019

Le programme exercice2/imprime.c simule une application d'impression qui attend de la part de l'utilisateur des noms de fichier à imprimer et les envoie à l'imprimante (l'impression étant simulée par un sleep de quelques secondes).

Telle qu'elle est écrite, l'utilisateur doit attendre qu'un fichier soit imprimé pour pouvoir donner le nom d'un autre fichier.

Modifier l'application exercice2/q1/imprime.c de sorte qu'à chaque fois que l'utilisateur a entré un nom de fichier, un thread est créé pour s'occuper de l'impression de ce fichier.

imprime.c
Copier le programme précédent dans le répertoire exercice2/q2/, puis le modifier pour que les threads ne mélangent pas leurs impressions à l'imprimante. imprime.c

On ne crée plus un thread à chaque demande d'impressions. On crée au démarrage de l'application un ensemble de threads de taille MAXNBTHREADS = 3 threads. Chaque thread se met en attente d'une impression, imprime puis se met en attente d'une nouvelle impression. De plus, une zone tampon circulaire de taille MAXNBIMPRESSIONS = 5 est utilisée pour stocker la file d'impressions. L'accès à l'imprimante reste exclusif.

Copier le programme précédent dans le répertoire exercice2/q3/, puis le modifier pour utiliser un pool de threads.

imprime.c

Afin de corriger de multiples problèmes dans un système d'information poussif, il est décidé de réécrire complètement le système de gestion des notes des étudiants. Pour cela, 3 fichiers sont utilisés: etudiants.dat contient la liste des étudiants, cours.dat contient la liste des cours, et notes.dat contient la liste des notes.

Chacun de ces fichiers est constituté d'une suite de structures sous cette forme:

struct etudiant { int id; char nom[STRING_MAX]; }; struct cours { int id; char nom[STRING_MAX]; }; struct note { int etudiant_id; int cours_id; int note; };

Afin de permettre un accès rapide à un étudiant à partir de son identifiant, l'emplacement d'un étudiant dans le fichier est déterminé par son identifiant. Ainsi, l'étudiant dont id vaut 0 est stocké au début du fichier, celui dont l'id vaut 1 est stocké à sizeof(struct etudiant) octets du début du fichier, etc.

Les cours sont stockés de la même façon : le cours d'id 0 est placé au début, le cours d'id 1 est placé à sizeof(struct cours) du début du fichier, etc.

Ce programme est chargé d'éditer les bulletins de notes. Le but est d'obtenir un affichage comme celui-ci:

0 - Yvan_le_hareng Système d'exploitation 18/20 Programmation système 16/20 Java 13/20 Traitement du signal 8/20 1 - Théophile_la_drosophile Système d'exploitation 11/20 Programmation système 13/20 Java 16/20 Anglais 15/20 2 - Hector_le_castor Programmation système 9/20 Java 14/20 Anglais 11/20 Pour cela, plusieurs fonctions parcourent les fichiers *.dat:
  • void print_bulletins() parcoure le fichier etudiants.dat, affiche le nom de chaque étudiant et appelle la fonction print_bulletin ;
  • void print_bulletin(struct etudiant *e) parcoure le fichier notes.dat et, pour chaque note attribuée à l'étudiant e, appelle la fonction print_note ;
  • void print_note(struct etudiant *e, struct note *n) recherche (dans cours.dat) le cours correspondant à la note n, et affiche le nom du cours et la note obtenue.
La position d'un cours dans le fichier cours.dat est déterminée par son identifiant. Il est donc possible d'y accèder en temps constant.

Complétez le programme exercice3/q1/notes.c pour implémenter ces fonctions.

notes.c

On envisage maintenant de paralléliser l'édition de bulletins de notes en lançant plusieurs threads s'exécutant en parallèle. Chaque thread serait chargé d'éditer le bulletin d'un étudiant, et les fonctions print_note et print_bulletin seraient donc exécutées de manière concurrente.

Le code des fonctions print_note et print_bulletin que vous avez produit à la question précédente risque-t-il de poser problème dans ce cas ?

Répondez en justifiant votre réponse dans le fichier exercice3/reponses.txt

accès concurrents en lecture donc ça ne devrait pas poser de problème, sauf si on récupère l'intitulé du cours avec un lseek+read. Dans ce cas, il faut s'assurer que le lseek+read est fait de manière atomique (en utilisant pread par ex).

Recopiez le programme exercice3/q1/notes.c dans le répertoire exercice3/q3/notes.c, puis modifiez le pour mmaper le fichier cours.dat en mémoire au démarrage du programme. Modifiez également la fonction print_note pour qu'elle accède au fichier via la zone mmapée.

A la fin du programme, pensez à "déprojeter" le fichier de la mémoire. notes.c

Commencez par donner le code de xv6 que vous avez réalisé lors du TP9 (implémentation d'une mémoire partagée). Pour cela, exécutez les commandes suivantes en les adaptant :

cd repertoire_xv6_TP9 make clean cd votreRepertoireDeTravailPourCSC4508 cp -r repertoire_xv6_TP9 exercice3/xv6_code_etudiant

Si vous n'avez pas fait le TP9 ou que le code produit n'est pas utilisable, vous pouvez vous baser sur le corrigé en exécutant les commandes suivantes à la place:

git clone -b tp9_corrige https://stark2.int-evry.fr/csc4508_xv6/ exercice3/xv6_code_etudiant

Dans xv6, ajoutez l'appel système int getppid() qui retourne le PID du processus parent. Pour cela, vous pouvez utiliser les deux champs pid et parent de la structure struct proc.

Dans xv6, ajoutez le programme utilisateur test_getppid.c qui affiche le PPID du processus.

Dans le fichier exercice4/reponses.txt, présentez l'implémentation de la fonction sys_shm_attach en vous appuyant sur le code que vous avez rendu à la question 4.a. Cette présentation doit expliquer l'algorithme principal implémenté ainsi que les mécanismes évitant les problèmes (détection d'erreur, verrouillage, etc.)

La fonction sys_shm_attach mappe un objet shm à une adresse dans l'espace d'adressage d'un processus. La fonction commence par récupérer les paramètres id (l'identifiant du shm), et addr (l'adresse où mapper l'objet) en vérifiant qu'ils sont valides. Ensuite, le verrou shms.lock est pris afin de s'assurer que la table shm n'est pas modifiée par un autre processus. La fonction recherche ensuite dans le processus courant un slot libre dans la table association afin de noter que le segment id est attaché à l'adresse addr. Si aucun slot n'est libre, on relache le verrou et on retourne une erreur. Ensuite, pour chaque page à mapper, on calcule l'adresse virtuelle (addr+i*PGSIZE), ainsi que l'adresse physique. L'adresse physique est obtenue à partir de l'adresse virtuelle de la page dans la mémoire du noyau. Lorsque toutes les pages ont été mappées, on incrémente le compteur de références nused, on relache le verrou et on retourne 0 pour indiquer que l'appel système s'est bien passé.
On considère que le programme utilisateur suivant s'exécute seul (c'est à dire qu'aucun autre processus n'accède au segment de mémoire partagée): int main() { int *addr = 0x10000000; int fd = shm_create(4096); shm_attach(fd, addr); if(*addr != 42) { printf("bonjour\n"); } *addr = 42; shm_detach(fd); int *addr2 = 0x20000000; shm_attach(fd, addr2); if(*addr2 != 42) { printf("au revoir\n"); } exit(); return 0; } Pour chacun des messages bonjour et au revoir, expliquer dans le fichier exercice4/reponses.txt :
  • si le message peut s'afficher dans certains cas, donnez les conditions dans lesquelles le message s'affiche
  • si le message s'affiche toujours, expliquez le mécanisme qui le garanti
  • si le message ne s'affiche jamais, expliquez le mécanisme qui le garanti
  • Le message bonjour s'affiche toujours car lorsqu'un segment de mémoire partagée est crée, shm_create initialise les pages à 0 grace à un appel à memset. La page est donc remplie de 0, donc *addr != 42.
  • Le message au revoir ne s'affiche jamais. L'instruction *addr = 42 écrit la valeur 42 au début de la première page du segment de mémoire partagée. Le segment est ensuite détaché, puis attaché à une autre addresse, et addr2 pointe alors sur le début de la première page du segment de mémoire partagée. Donc *addr2 == 42, et le message "au revoir" ne s'affiche pas.

Lorsqu'un processus est dupliqué (avec l'appel système fork), il serait souhaitable que les pages mémoires partagées par le processus père soient également accessible par le processus fils.

Expliquez dans le fichier exercice4/reponses.txt comment implémenter un tel mécanisme.

Dans la fonction fork (dans proc.c), il faut recopier le tableau attached du processus père dans le processus fils. Pour chaque entrée valide, il faut faire l'équivalent d'un shm_attach: on mappe les pages (en fait, c'est inutile car l'appel à copyuvm dans fork s'en charge), puis on incrémente le compteur de référence nused.