Objectif
La mémoire partagée System V permet à plusieurs processus d'accéder aux mêmes données sans recopier les données via un pipe ou une file de messages à chaque échange.
C'est un mécanisme d'IPC très rapide.
1. Principe
Code: Select all
Processus A Processus B
adresse virtuelle A adresse virtuelle B
| |
+------------+-----------------+
|
v
mêmes pages mémoire
Même mémoire partagée ne signifie pas même adresse virtuelle.
2. En-têtes
Code: Select all
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
Code: Select all
shmget()
|
v
shmid
|
v
shmat()
|
v
adresse virtuelle
|
v
lecture / écriture
|
v
shmdt()
|
v
shmctl(... IPC_RMID ...)
Prototype :
Code: Select all
int shmget(key_t key,
size_t size,
int shmflg);
Code: Select all
key_t key = ftok("/tmp/shm.key", 'S');
int shmid = shmget(key,
4096,
IPC_CREAT | 0600);
if (shmid == -1) {
perror("shmget");
}
size représente la taille demandée du segment.
Le kernel travaille en pages mémoire et la taille réelle allouée est gérée conformément aux contraintes du système.
Pour ouvrir un segment existant, la taille demandée doit respecter les règles de l'API.
6. IPC_CREAT et IPC_EXCL
Création si nécessaire :
Code: Select all
shmget(key, 4096, IPC_CREAT | 0600);
Code: Select all
shmget(key,
4096,
IPC_CREAT | IPC_EXCL | 0600);
7. Huge pages : SHM_HUGETLB
Linux propose des extensions comme :
Code: Select all
SHM_HUGETLB
C'est une fonctionnalité avancée, soumise aux capacités et à la configuration du système.
Elle n'est pas nécessaire pour utiliser la mémoire partagée classique.
8. Attacher : shmat()
Prototype :
Code: Select all
void *shmat(int shmid,
const void *shmaddr,
int shmflg);
Code: Select all
void *ptr = shmat(shmid, NULL, 0);
if (ptr == (void *)-1) {
perror("shmat");
}
C'est généralement le comportement à préférer.
9. Valeur de retour de shmat()
En cas de succès :
Code: Select all
adresse virtuelle du segment
Code: Select all
(void *) -1
Test correct :
Code: Select all
if (ptr == (void *)-1)
Flag :
Code: Select all
SHM_RDONLY
Code: Select all
void *ptr = shmat(shmid,
NULL,
SHM_RDONLY);
11. SHM_RND
SHM_RND intervient lorsque le programme demande une adresse d'attachement particulière.
Il permet d'arrondir l'adresse selon les contraintes définies par le système.
Dans la majorité des programmes :
Code: Select all
shmaddr = NULL
12. Lire et écrire
Après shmat(), la zone se manipule comme de la mémoire normale.
Exemple :
Code: Select all
char *mem = shmat(shmid, NULL, 0);
strcpy(mem, "bonjour");
Code: Select all
char *mem = shmat(shmid, NULL, SHM_RDONLY);
printf("%s\n", mem);
Exemple :
Code: Select all
struct shared_data {
int counter;
char text[256];
};
Code: Select all
struct shared_data *data;
data = shmat(shmid, NULL, 0);
data->counter++;
strcpy(data->text, "test");
La mémoire partagée ne fournit pas de synchronisation automatique.
Exemple dangereux :
Code: Select all
Processus A : counter++
Processus B : counter++
Résultat possible :
Code: Select all
race condition
- System V semaphore ;
- POSIX semaphore ;
- mutex process-shared ;
- autre protocole adapté.
Code: Select all
lock semaphore
modifier shared memory
unlock semaphore
Code: Select all
reserveSem(semid);
data->counter++;
releaseSem(semid);
Prototype :
Code: Select all
int shmdt(const void *shmaddr);
Code: Select all
if (shmdt(ptr) == -1) {
perror("shmdt");
}
shmdt() ne signifie pas forcément supprimer le segment System V.
17. Suppression : shmctl()
Prototype :
Code: Select all
int shmctl(int shmid,
int cmd,
struct shmid_ds *buf);
Code: Select all
IPC_RMID
Code: Select all
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID");
}
Lorsqu'un segment encore attaché est marqué avec IPC_RMID, sa destruction complète est liée à la fin des attachements restant selon les règles du système.
Conceptuellement :
Code: Select all
IPC_RMID
|
v
segment marqué pour suppression
|
v
dernier processus se détache
|
v
destruction finale
Exemple :
Code: Select all
struct shmid_ds ds;
if (shmctl(shmid, IPC_STAT, &ds) == -1) {
perror("shmctl IPC_STAT");
}
- permissions ;
- taille du segment ;
- PID du créateur ;
- PID de la dernière opération ;
- nombre de processus attachés ;
- dates importantes.
IPC_SET permet de modifier certains champs autorisés de la structure de contrôle.
Cela concerne principalement des métadonnées administratives et permissions.
21. Adresses virtuelles différentes
Exemple :
Code: Select all
Processus A :
base = 0x7f100000
Processus B :
base = 0x6a300000
Conséquence :
un pointeur absolu stocké dans le segment peut être invalide dans un autre processus.
22. Mauvais exemple : pointeur absolu
Code: Select all
struct data {
char *ptr;
};
Code: Select all
ptr = 0x7f100100
23. Bonne solution : offsets
Stocker un déplacement depuis le début du segment :
Code: Select all
struct data {
size_t offset;
};
Code: Select all
char *base = shared_base;
char *ptr = base + data->offset;
24. Structures contenant des pointeurs
Règle pratique :
Dans une structure réellement partagée entre processus, préférer :
- offsets ;
- indices ;
- tailles ;
- identifiants ;
plutôt que des pointeurs virtuels absolus.
25. Emplacement dans l'espace virtuel
Après shmat(), le segment apparaît comme une région du processus.
Sous Linux, on peut inspecter :
Code: Select all
/proc/<pid>/maps
26. Comparaison avec mmap()
System V shared memory :
Code: Select all
shmget()
shmat()
shmdt()
shmctl()
Code: Select all
mmap()
munmap()
27. Verrouillage mémoire
Certaines commandes ou extensions permettent de verrouiller des pages en mémoire afin d'éviter qu'elles soient évincées.
Ce sont des fonctions avancées qui dépendent des permissions et de la configuration du système.
Elles ne sont pas nécessaires pour la majorité des usages de shared memory.
28. Exemple simple : producteur / consommateur
Structure :
Code: Select all
struct shared_data {
int ready;
char buffer[1024];
};
Code: Select all
lock();
strcpy(shared->buffer, "message");
shared->ready = 1;
unlock();
Code: Select all
lock();
if (shared->ready) {
printf("%s\n", shared->buffer);
shared->ready = 0;
}
unlock();
29. Exemple de création
Code: Select all
key_t key = ftok("/tmp/shm.key", 'S');
if (key == (key_t)-1) {
perror("ftok");
exit(EXIT_FAILURE);
}
int shmid = shmget(key,
sizeof(struct shared_data),
IPC_CREAT | 0600);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
Code: Select all
struct shared_data *shared;
shared = shmat(shmid, NULL, 0);
if (shared == (void *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
Code: Select all
if (shmdt(shared) == -1) {
perror("shmdt");
}
Code: Select all
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
}
Le système impose diverses limites :
- nombre de segments ;
- taille minimale ;
- taille maximale ;
- quantité totale de mémoire partagée ;
- nombre de segments attachés selon l'implémentation.
Code: Select all
SHMMNI
SHMMIN
SHMMAX
SHMALL
34. Inspection
Afficher les segments :
Code: Select all
ipcs -m
Code: Select all
cat /proc/sysvipc/shm
Code: Select all
ipcrm -m <shmid>
- EEXIST : segment déjà présent avec IPC_EXCL.
- EACCES : permissions insuffisantes.
- EINVAL : taille, identifiant ou paramètres invalides.
- ENOMEM : mémoire / espace virtuel insuffisant.
- EIDRM : segment marqué/supprimé dans certains scénarios.
- très rapide ;
- pas de copie de message via le kernel à chaque échange ;
- adaptée aux gros volumes de données ;
- accès naturel sous forme de mémoire.
- synchronisation à gérer séparément ;
- risques de race conditions ;
- pointeurs absolus problématiques ;
- gestion du cycle de vie plus complexe ;
- objets System V persistants ;
- API historique.
Code: Select all
shmget() // créer / ouvrir un segment
shmat() // attacher dans l'espace virtuel
shmdt() // détacher
shmctl() // contrôler / supprimer
Code: Select all
key
|
v
shmget()
|
v
shmid
|
v
shmat()
|
v
adresse virtuelle
|
+-- lire
+-- écrire
|
v
shmdt()
administration :
shmctl()
|
+-- IPC_STAT
+-- IPC_SET
+-- IPC_RMID
- plusieurs processus voient les mêmes données.
- les adresses virtuelles peuvent être différentes.
- shmat() retourne (void *)-1 en cas d'erreur.
- shared memory ne synchronise rien.
- protéger les données avec un mécanisme adapté.
- ne pas stocker naïvement des pointeurs absolus.
- préférer offsets et indices.
- shmdt() détache.
- IPC_RMID supprime l'objet.
- shmget / shmat / shmdt / shmctl constituent le cœur de l'API.
