Utilisation d'un segment de mémoire partagé
Introduction
Dans Linux, chaque processus se voit attribué un espace mémoire virtuel de 4Go.
Bien sur, c'est virtuel, seuls les segments mémoires réellement utilisés sont reliés (mappés) par le système à la mémoire réelle.
Le système gère totalement la mémoire réelle, nous n'y avons pas accès. Nous n'avons accès qu'à la mémoire virtuelle de chaque processus.
Puisque chaque processus est confiné dans un segment mémoire, le système met à disposition une ressource permettant de mettre en commun une zone mémoire : le segment de mémoire partagé.
Qt met à disposition la classe QSharedMemory
.
Attention : Ressource commune
En programmation, utiliser une ressource commune à plusieurs processus est toujours délicat car il faut contrôler l'accès à la ressource de manière protéger les données.
Comme vous le savez maintenant, un processus peut à tout moment être interrompu par l'ordonnanceur du système pour que le processeur exécute le code d'un autre processus.
Imaginez qu'une procédure d'écriture soit en cours sur la mémoire partagée, mais non terminée. Imaginez aussi que le processus suivant écrive dans ce segment de mémoire partagé. Les données pourraient être corrompues, invalides ou incohérentes.
C'est la raison pour laquelle il faut absolument protéger le segment de mémoire partagé dès lors que plusieurs processus lecteur/écrivain y accèdent.
La classe QSharedMemory
L'objet s'utilise de la manière suivante :
Instanciation de l'objet
QSharedMemory
.Création (la première fois) du segment de mémoire partagé (
create()
).Attachement du segment au processus en cours (
attach()
).Utilisation protégée de la mémoire (pointeur).
Détachement du segment du processus en cours (
detach()
).Destruction de l'objet.
En plus de gérer un segment de mémoire partagé, la classe encapsule un objet sémaphore de protection accessible par les méthodes lock()
et unlock()
.
Rappel : Le sémaphore
Un sémaphore est une ressource mise à disposition par le système Linux pour permettre entre autre l'unicité d'accès à une ressource.
Chaque processus utilisant cette ressource doit utiliser le même sémaphore afin de s'assurer de l'unicité d'accès à la ressource.
En interne, un sémaphore est constitué d'un compteur et d'une file d'attente de processus.
Lorsqu'un processus "prends le jeton" (lock()
) du sémaphore, le compteur décroît.
Lorsque le compteur atteint 0, les processus suivant qui "prendront le jeton" se verront placés en file d'attente jusqu'à ce que le compteur ait une valeur positive.
Lorsqu'un processus "libère le jeton"(unlock()
), le compteur est incrémenté, les processus en attente sont réveillés.
Exemple : Création d'un segment de mémoire partagé
Il faut commencer par créer une structure de données contenant les informations qu'on souhaite partager entre les processus ou threads. Ci-dessous un exemple issu d'un projet.
Cette structure se déclare dans un fichier d'interface (.h).
// exemple
typedef struct {
int noMes; // numéro de la mesure
int adrCapteur; // Adresse ou fichier d'accès au capteur en hexa sans particule
int posL; // N° ligne affichage incrustation
int posC; // N° colonne affichage incrustation
char nomClasse[AFFMAX]; // nom de la classe de gestion du capteur
char nomMes[AFFMAX]; // nom de la mesure
char symbUnit[AFFMAX]; // Symbole de l'unité de la mesure
char textUnit[AFFMAX]; // Texte de l'unité
char valMes[AFFMAX]; // Valeur instantannée de la mesure
} T_Mes;
Il faut ensuite créer l'objet QSharedMemory
et le segment de mémoire partagé :
QSharedMemory *mShm; // segment de mémoire partagé
T_Mes *mData; // pointeur du segment de mémoire partagé
mShm = new QSharedMemory(KEY, this);
mShm->attach(); // tentative de s'attacher
if (!mShm->isAttached()) { // si existe pas alors création
res = mShm->create(9*sizeof(T_Mes)); // on réserve pour 9 capteurs max
if (res == false)
qDebug(mShm->errorString().toStdString().c_str());
} // if isattached
mShm->attach(); // tentative de s'attacher
mData = (T_Mes *)mShm->data(); // obtient le pointeur sur la mémoire
// utilisation de la mémoire partagée
mShm->detach();
La constante symbolique
KEY
est une chaîne de caractères représentant le chemin et nom d'un fichier existant. Le système se sert de ce fichier existant pour calculer une clef unique désignant le segment de mémoire partagé parmi d'autres.La mémoire partagée n'est pas forcément créée à l'instanciation de l'objet. Si l'attachement au processus n'est pas possible (cas du segment non créé) il faut donc créer le segment par la méthode
create()
.Dans cet exemple, le segment est créé d'une dimension pouvant contenir 9 structures de données de type
T_Mes
.La méthode
data()
permet de récupérer l'adresse de base (dans l'espace virtuel du processus) du segment de mémoire partagé. Avec ce pointeur, il est possible de lire/écrire dans la mémoire.Ne pas oublier de détacher le segment une fois son utilisation terminée. La destruction du segment est automatique dès que le dernier processus détache le segment.