Portail informatique 2019-2020

Systèmes d'exploitation

Pour commencer, récupérez la base du TP d'aujourd'hui:

  • git clone -b tp12_base https://stark2.int-evry.fr/asr3/csc4508_facebook/ csc4508_tp12
  • ou git checkout tp12_base

Le but du TP d'aujourd'hui est d'implémenter un outil analysant des fichiers de trace générés par l'outil lock analyzer développé lors du TP précédent.

Le programme analyze_trace.c parcoure une trace er collecte plusieurs statistiques:

Pour tester le programme, vous pouvez utiliser une des traces que vous avez générées. Vous pouvez également générer une trace en lançant la commande make run depuis le répertoire test/leveldb. Enfin, vous pouvez utiliser une de ces traces:

Actuellement, les différents locks détectés dans la trace sont stockés dans un tableau locks de taille fixe. Cela risque de poser des problèmes pour les traces contenant un grand nombre de verrous.

Modifiez le programme pour que le tableau soit alloué dynamiquement et agrandi (en utilisant realloc) à chaque fois qu'un nouveau lock est détecté.

Au lieu de lire le fichier de trace événement par événement, on souhaite voir la trace comme un grand tableau d'événements accessible via leur indice.

Modifiez le programme pour projeter le fichier en mémoire (en utilisant mmap).

L'étape suivante consiste à paralléliser l'analyse de la trace. Pour cela, nous mettons en place une architecture maître/esclave: le thread principal parcoure la trace et distribue les événements à des threads worker qui traitent les événements. Chaque worker traite les événements d'un thread de la trace. Ainsi, le thread principal sait à quel worker attribuer chaque événement.

Dans un premier temps, chargeons nous sur les éventuels problèmes d'accès concurrents à deux structures de données: le tableau locks, et le tableau threads.

Le but de cette question est de rendre les accès au tableau locks thread-safe. Modifiez la fonction update_lock_stats afin de la rendre thread-safe tout en limitant l'impact de la modification sur les performances.

Les modifications appliquées à un lock peuvent être faites de manière atomique. Du fait de l'utilisation de realloc (qui peut déplacer le buffer), il est nécessaire de s'assurer qu'aucun thread n'est en train d'aggrandir le tableau pendant qu'on traite un lock. Pour cela, vous pouvez utiliser un rwlock.

Nous nous attaquons maintenant à la parallélisation de l'application et à la gestion du tableau threads. Cette parallélisation repose sur une architecture maître esclave : le thread principal parcoure la trace et distribue les événements aux threads worker chargés de traiter les événements. Chaque worker traite les événements d'un thread de la trace. Ainsi, le thread principal sait à quel worker attribuer chaque événement. De plus, il n'est pas nécessaire de protéger l'accès au tableauthreads puisqu'aucun accès concurrent n'y est fait.

Modifiez l'application de la façon suivante:

  • dans la structure thread_info, ajouter un ou des champs permettant d'implémenter un schéma de type producteur/consommateur. Vous pouvez par exemple utiliser un tube (voir man pipe). Il est également nécessaire d'ajouter deux champs min_timestamp et max_timestamp correspondant aux timestamps minimum et maximum détectés pour ce thread.
  • dans get_thread_info, lorsqu'un nouveau thread est détecté dans la trace, il faut créer un thread worker qui sera chargé de traiter les événements de ce thread.
  • chaque thread worker doit attendre que le thread principal lui envoie un int correspondant à l'indice d'un événement dans le tableau des événements. Une fois l'indice reçu, le worker traite l'événement (en appelant process_event) puis attend la prochaine notification. Le thread se termine lorsqu'il reçoit un indice négatif.
  • lorsqu'il lit un nouvel événement, le thread principal doit réveiller le thread worker en charge de cet événement et lui indiquer l'indice de l'évenement à traiter.
Le corrigé de cet exercice est disponible dans la branch tp12_corrige du dépôt git.