System V Semaphores sous Linux

Ce forum est dédié à apprendre le développement de programmes user mode sur Linux

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

System V Semaphores sous Linux

Post by Hydraxx »

System V Semaphores sous Linux

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.
1. Principe

Un sémaphore contient une valeur entière positive ou nulle.

Exemple de sémaphore binaire :

Code: Select all

valeur = 1
Processus A prend le sémaphore :

Code: Select all

-1
Valeur :

Code: Select all

1 -> 0
Processus B tente ensuite -1.

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>
3. Particularité : un ensemble de sémaphores

semget() crée ou ouvre un ensemble de sémaphores.

Prototype :

Code: Select all

int semget(key_t key, int nsems, int semflg);
Exemple :

Code: Select all

int semid = semget(key,
                   3,
                   IPC_CREAT | 0600);
Cela crée un set :

Code: Select all

sem[0]
sem[1]
sem[2]
Chaque sémaphore est identifié dans le set par son numéro.

4. Création stricte

Code: Select all

int semid = semget(key,
                   1,
                   IPC_CREAT | IPC_EXCL | 0600);
IPC_EXCL permet de détecter si un autre processus a déjà créé le set.

5. Initialisation

Un nouveau sémaphore doit être correctement initialisé avant utilisation.

Pour un verrou binaire classique :

Code: Select all

valeur initiale = 1
Pour cela on utilise semctl().

6. semctl()

Prototype conceptuel :

Code: Select all

int semctl(int semid, int semnum, int cmd, ...);
Commandes importantes :

Code: Select all

SETVAL
GETVAL
SETALL
GETALL

IPC_STAT
IPC_SET
IPC_RMID

GETPID
GETNCNT
GETZCNT
7. union semun

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;
};
Elle sert à fournir le quatrième argument de certaines commandes semctl().

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");
}
Ici :

Code: Select all

semnum = 0
valeur = 1
9. GETVAL

Lire la valeur :

Code: Select all

int value = semctl(semid, 0, GETVAL);

if (value == -1) {
    perror("semctl GETVAL");
}
10. SETALL / GETALL

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);
11. Opérations : semop()

Prototype :

Code: Select all

int semop(int semid,
          struct sembuf *sops,
          size_t nsops);
La structure essentielle :

Code: Select all

struct sembuf {
    unsigned short sem_num;
    short          sem_op;
    short          sem_flg;
};
12. sem_num

sem_num désigne le sémaphore du set sur lequel effectuer l'opération.

Exemple :

Code: Select all

op.sem_num = 2;
=> travailler sur sem[2].

13. sem_op > 0

Ajouter une valeur.

Exemple :

Code: Select all

op.sem_op = +1;
Valeur :

Code: Select all

0 -> 1
C'est typiquement utilisé pour libérer une ressource.

14. sem_op < 0

Soustraire une valeur.

Exemple :

Code: Select all

op.sem_op = -1;
Si la valeur actuelle permet la soustraction, l'opération est effectuée immédiatement.

Sinon, le processus bloque par défaut.

Exemple :

Code: Select all

valeur = 0
sem_op = -1
=> blocage
15. sem_op == 0

Attendre que le sémaphore devienne exactement 0.

Exemple :

Code: Select all

op.sem_op = 0;
Si le sémaphore n'est pas à 0, le processus attend.

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");
}
17. Unlock classique

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");
}
Schéma :

Code: Select all

initialisation = 1

lock:
    -1
    1 -> 0

section critique

unlock:
    +1
    0 -> 1
18. IPC_NOWAIT

Par défaut, semop() peut bloquer.

Pour éviter le blocage :

Code: Select all

op.sem_flg = IPC_NOWAIT;
Si l'opération ne peut pas être effectuée immédiatement, l'appel retourne une erreur, généralement EAGAIN.

19. SEM_UNDO

SEM_UNDO demande au kernel de mémoriser certains ajustements du processus.

Exemple :

Code: Select all

op.sem_flg = SEM_UNDO;
Scénario :

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é
Cela aide à éviter qu'un sémaphore reste définitivement réservé après la mort d'un processus.

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);
Le kernel traite l'ensemble atomiquement :
  • 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.
C'est une capacité importante des sémaphores System V.

21. semtimedop()

Linux propose :

Code: Select all

int semtimedop(int semid,
               struct sembuf *sops,
               size_t nsops,
               const struct timespec *timeout);
C'est comparable à semop(), avec un délai maximal d'attente.

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
Le programme doit décider s'il faut recommencer ou abandonner.

23. Suppression : IPC_RMID

Code: Select all

if (semctl(semid, 0, IPC_RMID) == -1) {
    perror("semctl IPC_RMID");
}
La suppression réveille les processus bloqués sur le set avec une erreur appropriée.

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");
}
Cette structure contient notamment :
  • permissions ;
  • nombre de sémaphores ;
  • date de dernière opération ;
  • date de dernier changement.
25. GETPID

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
Le set existe alors que ses valeurs ne sont pas encore correctement initialisées.

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
Conceptuellement :

Code: Select all

reserveSem();

SECTION CRITIQUE

releaseSem();
30. Sémaphore de comptage

Un sémaphore peut aussi représenter plusieurs ressources disponibles.

Exemple :

Code: Select all

valeur initiale = 5
Cela peut représenter 5 ressources.

Chaque utilisateur fait :

Code: Select all

-1
Chaque libération :

Code: Select all

+1
31. Synchronisation avec shared memory

Usage très classique :

Code: Select all

System V shared memory
        +
System V semaphore
La mémoire contient les données.
Le sémaphore protège l'accès.

Exemple :

Code: Select all

lock semaphore
modifier mémoire partagée
unlock semaphore
32. Limites kernel

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.
Noms historiques rencontrés :

Code: Select all

SEMMNI
SEMMSL
SEMMNS
SEMOPM
SEMVMX
Il n'est pas utile de mémoriser les valeurs.

33. Inspection

Afficher les sémaphores :

Code: Select all

ipcs -s
Informations kernel :

Code: Select all

cat /proc/sysvipc/sem
Supprimer :

Code: Select all

ipcrm -s <semid>
34. Erreurs importantes
  • 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.
35. API à retenir

Code: Select all

semget()       // créer / ouvrir un set
semctl()       // initialiser / lire / supprimer
semop()        // opérations de synchronisation
semtimedop()   // semop avec timeout
36. Carte mentale

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
37. Points à retenir absolument
  • 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.

Who is online

Users browsing this forum: No registered users and 1 guest