Utilisation des sémaphores Système V

Décembre 2016


Introduction


Il n'est pas forcément facile de trouver un mode d'emploi clair des sémaphores System V, surtout en français. Cette astuce ne se veut pas exhaustive, mais on va quand même essayer de débroussailler un peu le chemin et clouer quelques panneaux pour savoir où aller.

Sommaire:



Au commencement: la création d'une clé


Nous allons utiliser une fonctionnalité gérée selon la mode des IPC (Inter Processus Communication).
Et tout objet IPC est créé ou retrouvé à partir d'une clé unique. Cette clé est un entier et pour la choisir vous avez deux manières:
  • Au pif
  • En utilisant une fonction qui crée une clé à partir d'un chemin vers un fichier


Si vous choisissez votre clé au hasard, vous risquez d'entrer en conflit avec un autre programme qui a voulu utiliser la même.
L'idéal, c'est plutôt de créer une clé générée à partir d'un nom de fichier, en l'occurrence le nom de votre programme.
Pour ça, on va utiliser deux choses, d'abord la fonction ftok dont le prototype est le suivant:
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok (char *pathname, char proj ) 

Celle-ci va nous permettre de créer notre clé à partir de deux choses:
  • pathname: C'est le chemin vers le fichier qui sera choisi pour générer la clé.
  • proj: Une valeur qu'il vous revient de définir, mettez le caractère que vous voulez. Ça peut vous permettre de définir plusieurs clés pour un même fichier.


Cette fonction retourne un key_t, synonyme d'un entier.
Maintenant, la manière la plus simple pour connaître le nom de fichier de votre programme, c'est de le prendre dans les arguments du main. Si votre liste d'arguments s'appelle argv, alors le nom de votre programme se trouve dans argv[0].

Exemple


# include <sys/types.h>
# include <sys/ipc.h>
#define ID_PROJET 'P'

int main(int argc, char **argv)
{
    key_t clef;
    clef = ftok(argv[0], ID_PROJET);
    return 0;
}

Création du/des sémaphore(s)


Maintenant que nous avons notre clé, nous allons pouvoir nous en servir pour créer notre sémaphore.
C'est la fonction semget qui nous intéresse ici, voici son prototype:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget ( key_t key, int nsems, int semflg )

Explication des paramètres:
  • key: La clé que nous venons de créer
  • nsems: Le nombre de sémaphores que nous voulons créer
  • semflg: Les permissions et les paramètres lors de la création de notre sémaphore


Il va falloir un peu plus de précisions sur smflg. Cette variable peut prendre plusieurs valeurs combinées par le biais de l'opérateur | qui est l'opérateur "ou" binaire.
Il y a deux parties à prendre en compte: les paramètres de création du sémaphore et les permissions sur ce sémaphore.
Les paramètres de création sont IPC_CREAT (indique que l'on veut créer un sémaphore) que l'on peut éventuellement combiner avec IPC_EXCL (indique que la fonction doit échouer si le sémaphore associé à la clé existe déjà). Concernant les permissions, le codage est le même que pour la commande chmod. Par exemple 0666 donnera les permissions de lecture/écriture pour tout le monde sur ce(s) sémaphore(s). Notez la présence d'un 0 au début du nombre pour dire au compilateur que 666 est à prendre en temps que nombre codé en octal.
Si vous ne voulez pas vous prendre la tête, choisissez 0666, ça fonctionnera toujours, mais si vous développez une véritable application, pensez à adapter ces permissions pour la sécurité.

La fonction semget retourne un identifiant vers votre (ou vos) sémaphore(s). C'est à partir de cet identifiant que vous pourrez manipuler votre sémaphore par la suite.

Exemple


Création d'un sémaphore ; échoue si le sémaphore associé à la clé existe déjà:
# include<sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define ID_PROJET 'P'

int main(int argc, char **argv)
{
    key_t clef;
    int semid;
    clef = ftok(argv[0], ID_PROJET);
    semid = semget(clef, 1, IPC_CREAT | IPC_EXCL | 0666);
    return 0;
}

Initialisation du sémaphore


A présent que nous avons créé notre sémaphore (on en crée un seul pour faire simple), nous allons devoir l'initialiser à la valeur que l'on souhaite. Mettons que l'on veuille que notre sémaphore soit un mutex (donc ne pouvant prendre que les valeurs 1 ou inférieur), nous allons lui mettre la valeur 1.

Pour cela, on utilise la fonction semctl dont le prototype est le suivant:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl ( int semid, int semno, int cmd, union semun arg ) 
  • semid est l'identifiant de notre sémaphore que nous venons de créer
  • semno est le énième sémaphore créé qui nous intéresse. Attention, ça commence à zéro, et comme on en a créé qu'un seul, celui qui nous intéresse est le zéro.
  • cmd est la commande à appliquer à notre sémaphore ; ici, celle qui nous intéresse est SET_VAL pour donner une valeur à notre sémaphore.
  • arg est un paramètre dont le type a une interprétation différente selon la commande envoyée. Comme nous avons choisi SET_VAL, ici arg sera de type int et représentera la valeur que l'on veut affecter à notre sémaphore.

Exemple


Initialisation de notre sémaphore à 1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define ID_PROJET 'P'

int main(int argc, char **argv)
{
    key_t clef;
    int semid;
    clef = ftok(argv[0], ID_PROJET);
    semid = semget(clef, 1, IPC_CREAT | IPC_EXCL | 0666);
    semctl(semid, 0, SET_VAL, 1);
    return 0;
}

Equivalent du P() ou down()


Maintenant, nous voulons faire un P() (ou down() ) sur notre sémaphore, ce qui signifie que nous voulons le décrémenter. En l'occurrence, comme c'est un mutex, cela signifie lui dire que l'on entre dans une section critique.

Ça va peut-être vous paraître étrange, mais les opérations sur les sémaphores doivent être modélisées dans une structure.
Voici cette structure:
struct sembuf{
    short sem_num;
    short sem_op;
    short sem_flg;
};

Comme décrit ici, sem_num est le numéro de votre sémaphore (0 pour nous), sem_op est l'opération à réaliser (-1 pour décrémenter et 1 pour incrémenter) et sem_flg contient les paramètres de l'opération. Ce dernier n'est pas important pour nous, on va le mettre à zéro.

On va donc initialiser notre structure ainsi pour un P():
struct sembuf op;
op.sem_num = O; //Numéro de notre sémaphore: le premier et le seul
op.sem_op = -1; //Pour un P() on décrémente
op.sem_flg = 0; //On ne s'en occupe pas

Pour exécuter notre opération, on utilisera la fonction semop. On n'hésite pas à l'exhiber en public:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop ( int semid, struct sembuf *sops, unsigned nsops ) 

Les paramètres:
  • semid: l'identifiant de votre sémaphore, comme d'habitude. Celui qui vous a été donné par semget.
  • sops: l'adresse de votre structure sembuf
  • nsops: le nombre d'opérations à effectuer. Ici nous n'avons qu'une seule opération à effectuer, donc on mettra 1. Si vous en avez plusieurs, il vous faudra passer un tableau de structures sembuf dans le paramètre sops.

Exemple


#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define ID_PROJET 'P'

int main(int argc, char **argv)
{
    key_t clef;
    int semid;
    sembuf op;

    clef = ftok(argv[0], ID_PROJET);
    semid = semget(clef, 1, IPC_CREAT | IPC_EXCL | 0666);
    semctl(semid, 0, SET_VAL, 1);
     
    op.sem_num = O; //Numéro de notre sémaphore: le premier et le seul
    op.sem_op = -1; //Pour un P() on décrémente
    op.sem_flg = 0; //On s'en occupe pas

    semop(semid, &op, 1);
    //Zone critique
    //.......

    return 0;
}

Equivalent du V() ou up()


Le V() incrémente le mutex. Il est utilisé pour libérer le mutex lorsque l'on sort d'une section critique.
Pour l'utiliser, rien de plus simple: c'est comme pour un P() sauf qu'il faut mettre 1 dans op.sem_op.

Destruction du sémaphore


Comme toujours, lorsqu'on utilise une ressource offerte gracieusement par le système d'exploitation, il faut la libérer ensuite.

Pour cela, on utilise la fonction semctl avec semid comme identifiant de notre sémaphore et IPC_RMID dans cmd. Les autres arguments peuvent être réglés avec la valeur que vous voulez, il ne seront pas pris en compte.
Ici, on fera donc:
semctl(semid, 0, IPC_RMID, 0);

NOTE: Si vous aviez plusieurs sémaphores attachés à ce semid, ils seront tous détruits.
NOTE: Si vous omettez cette étape de destruction, vos sémaphores existeront encore après la fin de votre processus. Vous pourrez néamoins les effacer avec la commande ipcrm.

Bilan


Tous les processus qui voudront utiliser la même section critique devront utiliser le même sémaphore.
Pour retrouver le sémaphore créé par un programme, il suffit d'utiliser ftok avec le chemin vers le programme qui a créé le sémaphore et le même id de projet. Puis, un appel à semget avec le paramètre IPC_CREAT sans IPC_EXCL permettra de retrouver ce sémaphore partagé.

Voici notre code final pour créer un sémaphore mutex et l'utiliser pour entrer dans une section critique:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define ID_PROJET 'P'

int main(int argc, char **argv)
{
    key_t clef;
    int semid;
    sembuf op;

    clef = ftok(argv[0], ID_PROJET); //Obtention d'un clé
    semid = semget(clef, 1, IPC_CREAT | IPC_EXCL | 0666); //Obtention d'un identifiant de sémaphore
    semctl(semid, 0, SET_VAL, 1); //Initialisation du sémapore à 1
     
    op.sem_num = O; //Numéro de notre sémaphore: le premier et le seul
    op.sem_op = -1; //Pour un P() on décrémente
    op.sem_flg = 0; //On ne s'en occupe pas

    semop(semid, &op, 1); //Entrée dans la section critique (P() ou down())
    //Section critique
    op.sem_op = 1; //Pour un V() on incrémente
    semop(semid, &op, 1); //Sortie de la section critique (V() ou up())

    semctl(semid, 0, IPC_RMID, 0); //Destruction du sémaphore

    return 0;
}

Sources et compléments


A voir également :

Ce document intitulé «  Utilisation des sémaphores Système V  » issu de CommentCaMarche (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.