Comme nous l’avons vu dans l’épisode 2, les processus sont exécutés en mémoire sur des pages virtuelles, ce qui garantit une étanchéité totale entre eux. Ceci oblige à passer par les mécanismes offerts par le noyau pour faire transiter des informations d’un processus à l’autre. Ces mécanismes ont été survolés lors de l’épisode 5.
Parmi ces mécanismes, on trouve les segments de mémoire partagée.

Le noyau peut en effet allouer des pages de mémoire virtuelle partagées que les processus peuvent monter dans leur espace mémoire. Ces pages, appelés segments de mémoire partagés, peuvent être montés par plusieurs processus, créant ainsi une zone d’échange de données permettant de traiter de (relativement) gros volumes de données.
Mais attention, l’accès concurrent à des données partagées pose des problèmes de stabilité et de fiabilité de ces données qui sont insoupçonnables tant qu’on n’a pas expérimenté ce type de programmation concurrente. C’est un sujet qu’on étudie lorsqu’on fait ses études d’informatique. La solution est simple pourtant : aucun processus ne doit accéder à ces données en même temps qu’un autre processus, ce qui oblige à considérer les sections d’accès aux données comme du code critique, dont l’accès doit être protégé par un sémaphore.
Nous avons vu comment mettre en oeuvre les sémaphores dans l’épisode 10.
Travaux pratiques sur les segments de mémoire partagée
Nous allons repartir du code source de l’exercice sur les sémaphores, en ajoutant un segment de mémoire partagée de 100 octets.
Après avoir créé le segment et l’avoir rattaché à la variable data, on va créer deux processus fils (qui vont bénéficier du rattachement au segment automatiquement, mais on pourrait monter le segment dans n’importe quel autre processus).
Le premier processus fils va réclamer le jeton (sémaphore) qui protège l’accès concurrent au segment, puis va y écrire une phrase.
Le deuxième processus fils va lui commencer par dormir 2 secondes pour laisser le temps au fils 1 de travailler. Puis il va luis aussi réclamer le jeton (sémaphore) qui protège l’accès concurrent au segment, puis va y lire la phrase (qu’il ne connait pas a priori) laissée dans le segment par fils 1. Il affichera ensuite cette phrase à l’écran, démontrant ainsi que les deux processus ont réussi à s’échanger des données via le segment de mémoire partagée.
Comme nous l’avions vu dans le TP sur les sémaphores, la commande ipcs permet de voir les files de messages, les sémaphores et les segments de mémoire partagée actifs, et la commande ipcrm permet de les supprimer.
A la fin de notre exercice nous supprimerons donc à la main (ipcrm shm num_segment) le segment partagé créé par le programme. Sachez qu’on pourrait aussi coder directement dans le programme de détruire le segment de mémoire partagée une fois qu’on n’en a plus besoin (appel système shmctl utilisant le flag IPC_RMID).
Voici le code source commenté :
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
union type_argument
{
int valeur;
struct semid_ds *tampon;
unsigned short *tab_valeur;
} argument;
int sem;
char *data;
int init_semaphore()
{
if((sem=semget(ftok("/home",1),1,IPC_CREAT | 0644))==-1) return(-1); // On tente de créer une grappe de sémaphores. Ramène -1 si le sémaphore existe déjà
argument.valeur=1;
semctl(sem,0,SETVAL,argument); // On initialise le sémaphore 0 de la grappe de sémaphore à la valeur 1 (mutex)
}
int get_semaphore()
{
if((sem=semget(ftok("/home",1),1,0644))==-1) return(-1); // On tente d'accéder à la grappe de sémaphores, qui doit déjà exister, sinon ramène -1
}
void prendre_jeton()
{
struct sembuf s;
s.sem_num=0;
s.sem_op=-1;
s.sem_flg=0;
semop(sem,&s,1); // Demande le jeton du sémaphore 0 de la grappe de sémaphores sem
}
void rendre_jeton()
{
struct sembuf s;
s.sem_num=0;
s.sem_op=1;
s.sem_flg=0;
semop(sem,&s,1); // Rend le jeton du sémaphore 0 de la grappe de sémaphores sem
}
void section_critique_ecrire_chaine(char *chaine)
{
strcpy(data,chaine);
}
void section_critique_lire_chaine(char *chaine)
{
strcpy(chaine,data);
}
int init_segment_memoire_partagee()
{
int seg;
// On essaie d'abord de récupérer l'accès au segment mémoire partagée de taille 100 octets (pour l'exemple) en considérant que celui-ci existe déjà
if((seg=shmget(ftok("/root",1),100, 0644))==-1) // l'accès au segment de mémoire partagée n'a pas pu être trouvé car ce dernier n'existe pas
{
// Dans ce cas on tente de le créer
if((seg=shmget(ftok("/root",1),100, IPC_CREAT | 0644))==-1) return(-1); // echec
}
if((data=(char *)shmat(seg,NULL,0))==(void *)-1) return(-1); // on tente de monter le segment sur la variable data
return(0); // succès. A partir de là, tout ce qu'on lit ou écrit dans data[0 .. 99] est écrit en mémoire dans le segment de mémoire partagée, donc accessible aussi aux autres processus ayant monté ce segment
}
int main()
{
int status;
char buffer[200];
if(init_semaphore()==-1)
{
if(get_semaphore()==-1)
{
printf("Impossible d'obtenir le sémaphore\n");
exit(1);
}
}
if(init_segment_memoire_partagee()==-1)
{
printf("Impossible d'obtenir le segment de mémoire partagée\n");
exit(1);
}
if(fork()==0)
{
// code du processus fils 1
printf("Ici le processus fils 1. Je réclame le jeton\n");
prendre_jeton(); // On réclame le jeton
printf("Ici le processus fils 1. J'ai obtenu le jeton\n");
section_critique_ecrire_chaine("Vive les saucisses !!!");
printf("Ici le processus fils 1. Je rends le jeton et je meurs\n");
rendre_jeton();
exit(0);
}
if(fork()==0)
{
// code du processus fils 2
printf("Ici le processus fils 2 : je dors 2 secondes pour laisser le temps au fils 1 de mettre quelque chose dans le segment de mémoire partagée\n");
sleep(2);
printf("Ici le processus fils 2. Je réclame le jeton\n");
prendre_jeton(); // On réclame le jeton
printf("Ici le processus fils 2. J'ai obtenu le jeton\n");
section_critique_lire_chaine(buffer);
printf("Ici le processus fils 2. Je rends le jeton\n");
rendre_jeton();
printf("Ici le processus fils 2, voici la phrase que j'ai trouvée dans le segment de mémoire partagée : %s\n",buffer);
exit(0);
}
wait(&status);
wait(&status);
}
Voici maintenant la magie du sorcier 🧙 qui s’opère à l’exécution :


Ce site présente des informations vraiment de très haut niveau. J’apprends beaucoup avec vos articles.
Un grand MERCI !!!
Continuez !
Merci. On essaie. A bientôt