Lorsque plusieurs processus coopèrent, ils doivent souvent intéragir entre eux, ils doivent parfois attendre qu’une opération soit effectuée par un autre processus pour travailler.
Il faut donc avoir des mécanismes qui permettent d’envoyer des événements aux processus (un processus doit pouvoir attendre l’évènement).
Types de synchronisation
Sous UNIX, les mécanismes suivants sont mis en oeuvre pour la synchronisation :
- Les signaux
- Les sémaphores
On parlera de point de synchronisation lorsqu’un processus attend un autre.
Les signaux
Un signal est un événement capturé par un processus, c’est aussi un mécanisme simple utilisé par le système d’exploitation pour signaler aux processus une erreur (SIGILL, SIGFPE, SIGUSR1, SIGUSR2, etc).
Exemple
Voici par exemple un programme dont la fonction sighandler
est
appellée lorsque le signal SIGUSR1 est déclenché :
Opérations
Il existe plusieurs opérations différentes sur les signaux :
signal
etsigset
qui lient un signal à une fonction.signal
la lie une seule fois, tandis quesigset
la lie continuellement.alarm
déclenche le signal SIGALARM au processus courrant.pause
suspend le processus jusqu’a la réception d’un signalkill
envoie un certain signal au processus dont le PID est donné.
Les sémaphores
Un sémaphore est une variable entière en mémoire qui excepté pour son
initialisation est accédée uniquement au moyen de fonction atomiques
(ne pouvant pas être décomposée) p()
et v()
.
La fonction p(sem)
va vérifier que la valeur est plus grande que zero,
si c’est le cas, la variable est décrementée et l’exécution continue, si
ce n’est pas le cas, alors il attend que ce soit le cas.
La fonction v(sem)
va simplement incrémenter la variable de 1, et va
ainsi réveiller tous les processus qui attendrait ce sémaphore.
Ces foncitons ne sont pas présente dans C de base il faut importer les
fichiers semadd.h
et semadd.c
depuis l’espace de cours.
Exemple
Voici un exemple d’un programme qui communique avec un processus fils
via 2 sémaphores. Il est intéressant de noter que généralement un
processus ne va faire qu’une seule opération par sémaphore (par exemple
que des p()
sur sem1 et que des v()
sur sem2 ou inversément)
Semget - Allocation de sémaphores
L’allocation se fait via int semget(int key, int nb, int flag)
, où
- La valeur retournée est un descripteur “semid”
- La clé est la valuer qui identifie le sémaphore
- Les flags définissent les permissions, comme pour les mémoires
partagées
IPC_CREAT
permet de demander la création des sémaphores
On peut aussi simplifier l’allocation à partir d’une clé en utilisant
int sem_transf(int key)
, cette fonction n’est pas officielle mais
le fichier est disponible sur HELMo Learn.
Semctl - Gestion de sémaphores
On peut gérer les sémaphores (nottament pour libérer la mémoire) en
utilisant int semctl(int semid, int semnum, int cmd, union semun attr)
où
- semid est le descripteur du sémaphore
- semnum identifie le sémaphore (généralement c’est 0 si il n’y en a qu’un)
- cmd identifie la commande (IPCSET, GETVAL, SETVAL, IPCRMID ou IPCSTAT).
union semun attr
est une “union” (un type de structure où chaqun des éléments partagent la même zone mémoire, ainsi ce ne peut être qu’un seul élément à la fois, un peu comme une enum en Rust). Il faut généralement définir cette structure soi-même en revanche.
Semop - Faire les opérations sur les sémaphores
int semop(int semid, struct sembuf* sops, unsigned nsops)
est la
fonction qui est derrière les fonctions p()
et v()
.
- semid est le descripteur du sémaphore
- sops est un tableau de structures semfus (contenant l’opération)
- nsops est le nombre d’éléménts du tableau sops