Objectif
Les System V semaphores servent à synchroniser plusieurs processus.
Ils ne transportent pas de données.
Ils permettent notamment :
- exclusion mutuelle ;
- contrôle d'accès à une ressource ;
- comptage de ressources ;
- attente d'un état ;
- synchronisation entre processus indépendants.
Un sémaphore contient une valeur entière positive ou nulle.
Exemple de sémaphore binaire :
Code: Select all
valeur = 1
Code: Select all
-1
Code: Select all
1 -> 0
Comme la valeur ne peut pas devenir négative, B bloque jusqu'à ce qu'un autre processus ajoute 1.
2. En-têtes
Code: Select all
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
semget() crée ou ouvre un ensemble de sémaphores.
Prototype :
Code: Select all
int semget(key_t key, int nsems, int semflg);
Code: Select all
int semid = semget(key,
3,
IPC_CREAT | 0600);
Code: Select all
sem[0]
sem[1]
sem[2]
4. Création stricte
Code: Select all
int semid = semget(key,
1,
IPC_CREAT | IPC_EXCL | 0600);
5. Initialisation
Un nouveau sémaphore doit être correctement initialisé avant utilisation.
Pour un verrou binaire classique :
Code: Select all
valeur initiale = 1
6. semctl()
Prototype conceptuel :
Code: Select all
int semctl(int semid, int semnum, int cmd, ...);
Code: Select all
SETVAL
GETVAL
SETALL
GETALL
IPC_STAT
IPC_SET
IPC_RMID
GETPID
GETNCNT
GETZCNT
Selon les environnements, le programme doit parfois définir lui-même :
Code: Select all
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
8. SETVAL
Initialiser un sémaphore :
Code: Select all
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl SETVAL");
}
Code: Select all
semnum = 0
valeur = 1
Lire la valeur :
Code: Select all
int value = semctl(semid, 0, GETVAL);
if (value == -1) {
perror("semctl GETVAL");
}
SETALL initialise toutes les valeurs d'un set.
GETALL récupère toutes les valeurs.
Exemple conceptuel :
Code: Select all
unsigned short vals[3] = {1, 0, 5};
union semun arg;
arg.array = vals;
semctl(semid, 0, SETALL, arg);
Prototype :
Code: Select all
int semop(int semid,
struct sembuf *sops,
size_t nsops);
Code: Select all
struct sembuf {
unsigned short sem_num;
short sem_op;
short sem_flg;
};
sem_num désigne le sémaphore du set sur lequel effectuer l'opération.
Exemple :
Code: Select all
op.sem_num = 2;
13. sem_op > 0
Ajouter une valeur.
Exemple :
Code: Select all
op.sem_op = +1;
Code: Select all
0 -> 1
14. sem_op < 0
Soustraire une valeur.
Exemple :
Code: Select all
op.sem_op = -1;
Sinon, le processus bloque par défaut.
Exemple :
Code: Select all
valeur = 0
sem_op = -1
=> blocage
Attendre que le sémaphore devienne exactement 0.
Exemple :
Code: Select all
op.sem_op = 0;
16. Lock classique
Code: Select all
struct sembuf lock;
lock.sem_num = 0;
lock.sem_op = -1;
lock.sem_flg = 0;
if (semop(semid, &lock, 1) == -1) {
perror("semop lock");
}
Code: Select all
struct sembuf unlock;
unlock.sem_num = 0;
unlock.sem_op = +1;
unlock.sem_flg = 0;
if (semop(semid, &unlock, 1) == -1) {
perror("semop unlock");
}
Code: Select all
initialisation = 1
lock:
-1
1 -> 0
section critique
unlock:
+1
0 -> 1
Par défaut, semop() peut bloquer.
Pour éviter le blocage :
Code: Select all
op.sem_flg = IPC_NOWAIT;
19. SEM_UNDO
SEM_UNDO demande au kernel de mémoriser certains ajustements du processus.
Exemple :
Code: Select all
op.sem_flg = SEM_UNDO;
Code: Select all
sem = 1
Processus A :
-1
sem = 0
Processus A termine brutalement
kernel applique l'ajustement d'annulation
sem revient vers son état corrigé
SEM_UNDO n'est pas une solution magique à tous les problèmes de synchronisation ou de crash.
20. Plusieurs opérations atomiques
semop() peut appliquer plusieurs opérations en un seul appel.
Exemple :
Code: Select all
struct sembuf ops[2];
ops[0].sem_num = 0;
ops[0].sem_op = -1;
ops[0].sem_flg = 0;
ops[1].sem_num = 1;
ops[1].sem_op = +1;
ops[1].sem_flg = 0;
semop(semid, ops, 2);
- si toutes les opérations peuvent être réalisées, elles sont appliquées ;
- sinon aucune modification partielle n'est laissée et le processus peut attendre.
21. semtimedop()
Linux propose :
Code: Select all
int semtimedop(int semid,
struct sembuf *sops,
size_t nsops,
const struct timespec *timeout);
Utile pour éviter un blocage sans limite.
22. Interruption par signal
Un semop() bloqué peut être interrompu par un signal.
Dans ce cas, l'appel peut retourner :
Code: Select all
-1
errno = EINTR
23. Suppression : IPC_RMID
Code: Select all
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("semctl IPC_RMID");
}
24. IPC_STAT
Récupérer la structure de contrôle :
Code: Select all
struct semid_ds ds;
union semun arg;
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("semctl IPC_STAT");
}
- permissions ;
- nombre de sémaphores ;
- date de dernière opération ;
- date de dernier changement.
Permet d'obtenir le PID associé à la dernière opération concernant un sémaphore donné.
26. GETNCNT
Permet d'obtenir le nombre de processus attendant qu'une valeur permette une diminution.
27. GETZCNT
Permet d'obtenir le nombre de processus attendant que la valeur devienne zéro.
28. Race d'initialisation
Cas classique :
Code: Select all
Processus A crée le set
Processus B ouvre immédiatement le set
Processus B l'utilise
Processus A n'a pas encore fini SETVAL
Création et initialisation ne sont pas automatiquement une seule opération atomique.
Il faut donc concevoir le protocole d'initialisation avec attention.
IPC_CREAT | IPC_EXCL aide à savoir quel processus est le créateur.
29. Sémaphore binaire
Un sémaphore System V peut être utilisé comme verrou binaire.
Code: Select all
SETVAL 1
reserve:
sem_op = -1
release:
sem_op = +1
Code: Select all
reserveSem();
SECTION CRITIQUE
releaseSem();
Un sémaphore peut aussi représenter plusieurs ressources disponibles.
Exemple :
Code: Select all
valeur initiale = 5
Chaque utilisateur fait :
Code: Select all
-1
Code: Select all
+1
Usage très classique :
Code: Select all
System V shared memory
+
System V semaphore
Le sémaphore protège l'accès.
Exemple :
Code: Select all
lock semaphore
modifier mémoire partagée
unlock semaphore
Linux impose des limites sur :
- nombre de sets ;
- nombre de sémaphores ;
- nombre de sémaphores par set ;
- nombre d'opérations dans un semop() ;
- valeur maximale d'un sémaphore ;
- structures SEM_UNDO.
Code: Select all
SEMMNI
SEMMSL
SEMMNS
SEMOPM
SEMVMX
33. Inspection
Afficher les sémaphores :
Code: Select all
ipcs -s
Code: Select all
cat /proc/sysvipc/sem
Code: Select all
ipcrm -s <semid>
- EEXIST : set déjà présent avec IPC_EXCL.
- EACCES : permissions insuffisantes.
- EAGAIN : IPC_NOWAIT et opération impossible.
- EINTR : attente interrompue par un signal.
- EIDRM : set supprimé pendant une attente.
- EINVAL : identifiant ou paramètres invalides.
Code: Select all
semget() // créer / ouvrir un set
semctl() // initialiser / lire / supprimer
semop() // opérations de synchronisation
semtimedop() // semop avec timeout
Code: Select all
key
|
v
semget()
|
v
semid
|
+-- semctl()
| |
| +-- SETVAL / GETVAL
| +-- SETALL / GETALL
| +-- IPC_STAT
| +-- IPC_RMID
|
+-- semop()
|
+-- sem_num
+-- sem_op
| -1 prendre
| +1 libérer
| 0 attendre zéro
|
+-- sem_flg
IPC_NOWAIT
SEM_UNDO
- System V crée des sets de sémaphores.
- semop() effectue les opérations.
- sem_op < 0 peut bloquer.
- sem_op > 0 augmente la valeur.
- sem_op == 0 attend zéro.
- plusieurs opérations peuvent être atomiques dans un même semop().
- SEM_UNDO peut corriger certains ajustements à la terminaison du processus.
- l'initialisation doit être conçue avec attention.
- IPC_RMID supprime le set.
