Перейти к содержанию

Семафор в RTOS: двоичный и счётный

Прерывание сообщает: «пришли данные», а обработать их должна задача, которая сейчас спит. Самый простой способ разбудить её — семафор. Во FreeRTOS их два вида: двоичный — для одиночного события и счётный — для событий, которые могут накапливаться.

Обновлено Редакция GAW

Семафор — объект операционной системы реального времени (RTOS), с помощью которого задачи и прерывания сообщают друг другу о событиях и учитывают свободные ресурсы. Над ним две операции: give (отдать, увеличить счёт) и take (взять, уменьшить счёт). Если счёт нулевой, задача при take блокируется до прихода события или до таймаута.

Двоичный семафор

По руководству FreeRTOS двоичный семафор — это очередь длиной в один элемент: она либо пуста, либо полна. Типичное применение — разбудить задачу из прерывания:

static SemaphoreHandle_t rx_sem;

void USART1_IRQHandler(void) {
    BaseType_t woken = pdFALSE;
    /* read data, clear the flag */
    xSemaphoreGiveFromISR(rx_sem, &woken);
    portYIELD_FROM_ISR(woken);               /* switch to the woken task now */
}

void rx_task(void *arg) {
    for (;;) {
        xSemaphoreTake(rx_sem, portMAX_DELAY);   /* sleep until the IRQ */
        /* process the data */
    }
}

Так строится отложенная обработка прерываний: в обработчике только самое необходимое, остальное — в задаче. В semphr.h отмечено, что свежесозданный двоичный семафор пуст и что для таких случаев уведомление задачи работает быстрее и экономнее.

Счётный семафор

Счётный семафор — очередь длиной больше единицы, в которой важно только число элементов. FreeRTOS называет два применения:

ПрименениеНачальное значениеGiveTake
Подсчёт событий0при каждом событиипри обработке события
Управление ресурсамичисло ресурсовресурс освобождёнресурс занят

Семафор и мьютекс

Для защиты общего ресурса во FreeRTOS есть мьютекс — двоичный семафор с наследованием приоритета. Разница в назначении: семафор для синхронизации обычно не возвращают, мьютекс для взаимного исключения возвращают всегда. Если данные из прерывания надо не только сигнализировать, но и передать, вместо семафора берут очередь.

Частые вопросы

Почему задача не просыпается, хотя семафор создан?

Двоичный семафор, созданный xSemaphoreCreateBinary(), по semphr.h изначально пуст: первый xSemaphoreTake() будет ждать, пока кто-то не выполнит give. Если прерывание отдаёт семафор раньше, чем задача начала ждать, событие не потеряется — семафор останется отданным. Проверяют и то, что прерывание вызывает именно xSemaphoreGiveFromISR().

Когда нужен счётный семафор, а не двоичный?

Когда событий может прийти несколько, пока задача занята. Двоичный семафор запомнит только одно — остальные потеряются. Счётный увеличивает счёт при каждом give и уменьшает при каждом take, поэтому задача обработает все. Второе применение — учёт свободных ресурсов, например буферов: начальное значение равно их числу.

Можно ли защищать общий ресурс двоичным семафором?

Технически да, но лучше мьютексом. Во FreeRTOS у двоичного семафора нет наследования приоритета, и при защите ресурса возможна неограниченная инверсия приоритетов. Двоичный семафор оставляют для синхронизации, мьютекс — для взаимного исключения.

Источники

  1. R. Barry, FreeRTOS Team. Mastering the FreeRTOS Real Time Kernel — A Hands-On Tutorial Guide, Release 1.1.0 (Amazon Web Services, 2023) — разд. 7.4: двоичный семафор разблокирует задачу при каждом прерывании — синхронизирует задачу с прерыванием; его можно считать очередью длиной 1; задача ждёт в xSemaphoreTake() с временем блокировки, прерывание отдаёт его xSemaphoreGiveFromISR(); уведомление задачи эффективнее; разд. 7.5: счётный семафор — очередь длиной больше 1, где важно только число элементов; применения — подсчёт событий (начальное значение 0) и управление ресурсами (начальное значение равно числу ресурсов); включается configUSE_COUNTING_SEMAPHORES; разд. 8.3: семафор для взаимного исключения возвращают, для синхронизации — обычно нет
  2. FreeRTOS Kernel V11.1.0, include/semphr.h (GitHub FreeRTOS/FreeRTOS-Kernel) — xSemaphoreCreateBinary(): во многих случаях быстрее и экономнее уведомление задачи; созданный так двоичный семафор сначала пуст и его нужно отдать перед первым взятием; двоичный семафор не использует наследование приоритета, для этого есть xSemaphoreCreateMutex(); xSemaphoreGive() нельзя вызывать из прерывания, для этого xSemaphoreGiveFromISR() с параметром pxHigherPriorityTaskWoken; xSemaphoreCreateCounting()

Нашли ошибку?