Programmation système (Linux et C)

LPS : Linux : principes et programmation Système

Autres références Cnam Liban et cofares.net

This project is maintained by ISSAE

Index Home |

Les Sémaphores

Description

La plus importante contribution au problème de la synchronisation entre processus fut l’introduction par Dijkstra, en 1965, du concept des Sémaphores.

Conceptuellement, un sémaphore joue le rôle d’un distributeur de tickets, qui, initialement dispose d’un certain nombre de tickets, éventuellement nul.

Un processus utilisateur du sémaphore demande un ticket en invoquant une opération particulière appelée P : si au moins un ticket est disponible, le processus appelant le prend et poursuit son exécution ; dans le cas contraire, le demandeur est enregistré dans une file d’attente et est bloqué dans l’attente de l’obtention d’un ticket. Autrement dit, l’opération P n’est exécutable par le processus appelant que s’il existe un ticket libre, elle peut se retrouver bloquante dans le cas contraire.

Grâce à une opération inverse, appelée V, un processus dépose son ticket dans le distributeur. Si des processus sont en attente dans la file du sémaphore, le premier d’entre eux est débloqué et obtient son ticket.

Plus précisément, un sémaphore est une variable entière non négative à laquelle, sauf pour l’initialisation, on accède seulement à travers deux opérations standards atomiques (non interruptibles) : P et V.

Ce mécanisme permet donc de limiter l’exécution d’une portion de code _(et donc l’accès à une ressource quelconque), encadrée par des appels respectifs à P et à V, _à un nombre fixé de processus. Nous allons voir par la suite, que cela permet de réaliser l’exclusion mutuelle nécessaire à la prévention des conflits d’accès aux données non partageables.

Les sémaphores sous Linux

Il existe deux ensembles de primitives pour réaliser des sémaphores sous Linux:

  1. Les sémaphores de la norme System V, plutôt adapté a la synchronisation inter-processus (décrit dans cette page).
  2. Les sémaphore de la norme POSIX plutôt adapté au concurrence inter-thread

présentation sémaphores System V

La gestion des sémaphores sous Unix, est prise en compte par des primitives systèmes (system calls) étant donné leur principe de fonctionnement très bas niveau et la nécessité d’avoir les opérations P et V qui ne soient pas interruptibles. Les sémaphores sont donc gérés par le noyau, ils appartiennent à la classe des objets IPC.

La technique IPC Unix système V, propose un ensemble de mécanismes purement mémoires, qui permettent dans des limites de droits, à n’importe quels couples de processus d’échanger des informations et/ou de se synchroniser. Les objets IPC sont composés en :

Le point commun des objets IPC, et qui nous intéresse plus particulièrement en ce qui concerne les sémaphores, est qu’ils forment des ressources maintenues par le noyau dans son espace personnel ; ils sont donc indépendants d’un processus particulier. Chaque processus ne fait que requérir auprès du noyau l’obtention et l’utilisation de ceux-ci.

A cette double fin (centralisation et partage), Unix maintient pour tout type d’objet IPC, une table personnelle identifiant chacune des instances crées. Ceci explique pourquoi, les files de messages, les mémoires partagées et les sémaphores sont tous identifiés sous la forme d’un entier (type int) souvent appelé xxxid (ou xxx est soit msg pour les files, shm pour les mémoires ou sem pour les sémaphores) ou encore clé.

L’IPC sémaphore d’Unix possède deux généralisations qui en enrichissent encore l’utilisation.

création ou acquisition

Pour créer, ou acquérir un sémaphore, on dispose de la primitive suivante :

int semget(key_t cle, int nsems, int semflag);

Cette fonction crée le sémaphore si celui-ci n’existe pas, ou renvoie un identificateur sur ce dernier s’il a déjà été créé par un autre processus.

Les paramètres :

En retour, la fonction renvoie soit -1 si une erreur c’est produite, ou alors l’identificateur (semid) du sémaphore.

Ex. pour créer un sémaphore simple :

semid = semget(cle, 1, IPC_CREAT | 0700);

initialisation

Pour affecter une valeur initiale (le nombre de jetons) à un sémaphore, on utilise la primitive semctl(), qui permet aussi entre autre, comme nous le verrons plus tard, de libérer un sémaphore.

int semctl(int semid, int semnum, SETVAL, int valeur);

Les paramètres :

En retour, la fonction renvoie soit -1 si une erreur s’est produite.

Ex. pour initialiser un sémaphore simple à 1 (identifié par semid) :

valRet = semctl(semid, 0, SETVAL, 1);

utilisation (Écriture de P et V)

En ce qui concerne les opérations P et V, elles peuvent être réalisées par des appels directs à la primitive système semop(), de la manière suivante :

void p(int semid) {
    int rep;
    struct sembuf pd={0,-1,0};
    rep=semop(semid, &pd, 1);
    return(rep);
}

void v(int semid) {
    int rep;
    struct sembuf pd={0,1,0};
    rep=semop(semid, &pd, 1);
    return(rep);
}

NB : il est très important pour le bon fonctionnement de ces deux opérations, que leur code ne comportent uniquement que des appels à semop() et rien de plus, car seul le déroulement de cette primitive est garanti comme n’étant pas interruptible par le noyau ! Cette précaution est nécessaire pour préserver le caractère atomique de ces deux opérations.

Opération sur sémaphore System V

La primitive semop() permet d’augmenter ou de diminuer la valeur d’un sémaphore, et comme nous l’avons vu précédemment, elle peut le faire avec un nombre d’une valeur quelconque et sur un sémaphore qui peut éventuellement être décomposé en un sous-ensemble de sémaphore.

int semop(int semid, struct sembuf *sops, size_t nsops);

Les paramètres :

Dans le cas précédent, un seul sémaphore (non décomposable) nous suffit, donc la plupart de ces paramètres sont à zéro.

L’action réelle de semop()dépend de la valeur du paramètre __sem_op :

Ainsi, dans les définitions précédentes de P et de V, on peut remarquer que le paramètre sem_op prend respectivement la valeur -1 (pour décrémenter le sémaphore) et la valeur 1 (pour l’incrémenter).

Remarque:

libération

La libération d’un sémaphore est faite par un appel à semctl(), a qui l’on transmet l’identificateur du sémaphore, ainsi que la commande IPC_RMID.

int semctl(int semid, IPC_RMID, NULL);

Les paramètres :

En retour, la fonction renvoie soit -1 si une erreur s’est produite.

Ex. pour libérer un sémaphore (identifié par semid) :

valRet = semctl(semid, IPC_RMID, NULL);