Système d'Exploitation - Travaux Dirigés n°8
«Gestion des IPC dans UNIX : les segments de mémoire partagée»
Notions vues dans ce TD : les segments de mémoire partagée, les signaux.
Prochain TD : C'est le dernier ! ....
PS : Les parties correspondant à du travail à faire sont toutes en italiques ; le restant étant du complément au cours.
Rappel sur les IPCs dans UNIX
Les IPCs sont des outils de communication et de synchronisation entre des processus qui s'exécute sur une même machine. Les sémaphores qui ont été vu dans le TD précédent, permettent de gérer des mécanismes d'exclusion mutuelle (section critique) sur du code entre différents processus. Les files de message, dont un exemple complet (code) a été donné en cours, permettent une communication asynchrone par échange de messages entre des processus ; elles permettent également le multiplexage de messages entre différents processus lecteurs. Enfin les segments de mémoire partagée, qui font l'objet de ce TD, permettent le partage d'une zone de la mémoire centrale par différents processus, chacun voyant cette zone comme une extension de son espace d'adressage.
Ces objets sont indépendants du système de fichiers d'Unix et de la gestion des processus. A chaque type d'objet correspond en mémoire une table consultable (commande "ipcs") depuis n'importe quel processus. La manipulation de ces objets partagés nécessite un système de nommage indépendant de celui des fichiers (i-noeud) et des processus (pid). Pour accéder à un objet IPC l'un des processus concernés devra créer cet objet (fonction "shmget") en lui attribuant un nom (clé externe) grâce à la fonction "ftok". Pour construire ce nom cette méthode prend deux chaînes de caractères en paramètre, la première étant issus du système de fichier (par exemple "/tmp") alors que la deuxième sera quelconque. Les processus désirant accéder à cet objet IPC venant d'être créé, devront fabriquer cette même clé externe en appelant la méthode "ftok" avec les deux mêmes chaînes que celles utilisées par le premier processus (celui qui a créé l'objet).
Après avoir créer un segment de mémoire, le processus courant doit rattacher ce dernier à son propre espace d'adressage virtuel (fonction "shmat"), soit en précisant l'adresse de rattachement, soit en laissant le système trouver cette adresse. Cette dernière méthode conseillée (la plus sûre) sera appliquée en prenant NULL (cf. l'exemple donné) comme deuxième paramètre de l'appel à "shmat". Tous les autres processus voulant partager ce segment devront aussi effectuer cette opération de rattachement. Cette fonction de rattachement "shmat" renvoie un pointeur (*) sur n'importe quel type de donnée (void). Cela dépendra de ce que l'on a mis dans le segment.
Lorsqu'un processus n'a plus besoin d'utiliser un segment de mémoire il peut le détacher de son espace d'adressage virtuel (fonction "shmdt" avec l'adresse du segment en paramètre).
Enfin lorsqu'un segment n'est plus utilisé par les processus concernés, il peut être détruit soit par le dernier processus qui l'utilise (fonction "shmctl"), soit à partir d'un shell (commande "ipcrm [ shm | msg | sem ] id").
key_t cle ;
int taille ; // taille du segment de mémoire créé
struct donnees * commun ; // structure définissant le type des données manipulées dans le segment partagé
...
cle = ftok ("/tmp", 'A') ; // création de la clé externe - nom partageable par les processus
taille = sizeof (struct donnees) ;
id = shmget ( cle , taille, IPC_CREAT | IPC_EXCL | 0666 ) ; // création de l'objet lui-même
// attachement en lecture/écriture du segment au processus courant à l'adresse définie par "commun"
commun = (struc donnees *) shmat (id, NULL, SHM_R | SHM_W ) ;
...
shmdt ( (char *) commun ) ; // détachement du segment de l'espace d'adressage virtuel du processus courant
shmctl (id, IPC_RMID, NULL) ; // destruction de l'objet IPC d'identité "id"
Problème
Ecrire deux programmes indépendants "cons.c" et "prod.c" qui communiqueront via un segment de mémoire partagée. Ils seront lancés simultanément dans deux shell différents.
Le premier processus ("prod.c") va créer le segment de mémoire partagée. Il boucle sur la lecture au clavier d'un nombre qu'il additionne dans une variable "somme". Cette variable est initialisée avec la première valeur lue ; elle représente donc à chaque étape de la boucle, la somme de toutes les valeurs entrées au clavier depuis le lancement du programme. La variable "somme" ainsi qu'une variable entière "nb" précisant le nombre de saisies au clavier réalisées depuis le lancement du programme, seront mises dans une structure (struct donnees) déclarée dans le segment partagé. Ainsi ces informations pourront être récupérées par le deuxième processus. Il faudra prévoir un mécanisme pour sortir le processus de sa boucle de saisie au clavier/écriture dans le segment de mémoire partagée. Soit on utilisera le signal SIGINT (Ctrl C) depuis le shell de lancement, soit on sortira sur une valeur particulière entrée au clavier (n'importe quel caractère alphabétique). Pour cette dernière solution on pourra utiliser le code retour de la fonction "scanf" :
while (1)
{
if (scanf (" %d ", & reponse) != 1 ) break ; // si le scanf n'a pas marché correctement il retourne une valeur autre que "1".
}
Après réception du signal (Ctrl C) ou lecture d'un caractère alphabétique et avant de se terminer, le processus devra détacher le segment de mémoire de son espace d'adressage virtuel et détruire le segment de mémoire.
Le deuxième processus ("cons.c") va afficher le contenu de la mémoire partagée ; prévoir une boucle active dans laquelle il imprime à l'écran le contenu de la mémoire partagée suivi d'un sleep (2) pour temporiser l'impression. Ce processus sortira de sa boucle active sur réception du signal SIGINT (Ctrl C) envoyé depuis le shell de lancement. Après réception de ce signal le processus devra détacher le segment de mémoire avant de se terminer.
Vous vérifierez lorsque vos deux processus ce seront arrêtés que le segment de mémoire partagée n'existe plus en lançant la commande "ipcs" dans l'un des deux shell utilisés.