Aller au contenu

Les Semaphore

Les sémaphores sont des variables qui permettent de synchroniser plusieurs tâches concurrentes. Le concept a été inventé dans les années 60 par Edsger Dijkstra. Le livre gratuit The Little Book of Semaphores est un excellent moyen d’approfondir vos connaissances avec les sémaphores et la programmation concurrente en général. Pour les plus téméraires, vous pouvez aussi lire l’article original EWD123 de Dijkstra.

FreeRTOS propose deux types de sémaphores avec l’interface semphr. h :

  • Counting semaphores
  • Binary semaphores

Le counting semaphore est le sémaphore traditionnel avec un compteur représenté par un entier non signé. On crée un sémaphore avec la fonction xSemaphoreCreateCounting :

SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
                                           UBaseType_t uxInitialCount);

Le uxMaxCount est la valeur maximum que peut prendre le compteur. Ce n’ est pas habituel de fixer une limite supérieure pour un sémaphore, et FreeRTOS impose cette limite à cause de la manière dont est implémenté le sémaphore. Dans la mesure du possible, on préfère utiliser des queues plutôt que des sémaphores pour synchroniser des tâches.

Quand le sémaphore est créé, on utilise la fonction xSemaphoreTake pour décrémenter le sémaphore. C’est l’équivalent de la fonction P de Dijkstra ou de la méthode acquire() de Java.

xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);

Le paramètre xTicksToWait permet de définir le temps à attendre avant de retourner si le sémaphore est à zéro. Ça permet de tester si un sémaphore est disponible ou non.

Exemple :

SemaphoreHandle_t xSemaphore = NULL;

// A task that creates a semaphore.
void vATask(void* pvParameters) {
    // Create the semaphore to guard a shared resource.  As we are using
    // the semaphore for mutual exclusion we create a mutex semaphore
    // rather than a binary semaphore.
    xSemaphore = xSemaphoreCreateMutex();
}

// A task that uses the semaphore
void vAnotherTask(void* pvParameters) {
    // ... Do other things.

    if (xSemaphore != NULL) {
        // See if we can obtain the semaphore.  If the semaphore is not
        // available wait 10 ticks to see if it becomes free.
        if (xSemaphoreTake(xSemaphore, (TickType_t)10) == pdTRUE) {
            // We were able to obtain the semaphore and can now access the
            // shared resource.

            // ...

            //  We have finished accessing the shared resource.  Release the
            // semaphore.
            xSemaphoreGive(xSemaphore);
        } else {
            // We could not obtain the semaphore and can therefore not access
            // the shared resource safely.
        }
    }
}

Pour incrémenter le sémaphore, in utilise la méthode xSemaphoreGive(). C’est l’équivalent de la fonction V de Dijkstra ou de la méthode release() de Java.

xSemaphoreGive(SemaphoreHandle_t xSemaphore);

Exemple :

SemaphoreHandle_t xSemaphore = NULL;

void vATask(void* pvParameters) {
    // Create the semaphore to guard a shared resource.  As we are using
    // the semaphore for mutual exclusion we create a mutex semaphore
    // rather than a binary semaphore.
    xSemaphore = xSemaphoreCreateMutex();

    if (xSemaphore != NULL) {
        if (xSemaphoreGive(xSemaphore) != pdTRUE) {
            // We would expect this call to fail because we cannot give
            // a semaphore without first "taking" it!
        }
        // Obtain the semaphore - don't block if the semaphore is not
        // immediately available.
        if (xSemaphoreTake(xSemaphore, (TickType_t)0)) {
            // We now have the semaphore and can access the shared resource.
            // ...
            // We have finished accessing the shared resource so can free the
            // semaphore.
            if (xSemaphoreGive(xSemaphore) != pdTRUE) {
                // We would not expect this call to fail because we must have
                // obtained the semaphore to get here.
            }
        }
    }
}

Le binary semaphore est un sémaphore binaire. C’est comme un counting semaphore avec un uxMaxCount de 1.

On crée un sémaphore binaire avec la fonction xSemaphoreCreateBinary:

SemaphoreHandle_t xSemaphoreCreateBinary(void);

Exemple :

SemaphoreHandle_t xSemaphore;

void vATask(void* pvParameters) {
    // Attempt to create a semaphore.
    xSemaphore = xSemaphoreCreateBinary();

    if (xSemaphore == NULL) {
        // There was insufficient FreeRTOS heap available for the semaphore to
        // be created successfully.
    } else {
        // The semaphore can now be used. Its handle is stored in the
        // xSemahore variable.  Calling xSemaphoreTake() on the semaphore here
        // will fail until the semaphore has first been given.
    }
}

Lors de son initialisation, le compteur du sémaphore binaire vaut zéro.

Pour interagir avec le sémaphore binaire, nous utilisons les mêmes fonctions qu’avec le counting semaphore :

  • xSemaphoreTake pour décrémenter le sémaphore binaire
  • xSemaphoreGive pour incrémenter le sémaphore binaire