Семафор в 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 называет два применения:
| Применение | Начальное значение | Give | Take |
|---|---|---|---|
| Подсчёт событий | 0 | при каждом событии | при обработке события |
| Управление ресурсами | число ресурсов | ресурс освобождён | ресурс занят |
Семафор и мьютекс
Для защиты общего ресурса во FreeRTOS есть мьютекс — двоичный семафор с наследованием приоритета. Разница в назначении: семафор для синхронизации обычно не возвращают, мьютекс для взаимного исключения возвращают всегда. Если данные из прерывания надо не только сигнализировать, но и передать, вместо семафора берут очередь.
Частые вопросы
Почему задача не просыпается, хотя семафор создан?
Двоичный семафор, созданный xSemaphoreCreateBinary(), по semphr.h изначально пуст: первый xSemaphoreTake() будет ждать, пока кто-то не выполнит give. Если прерывание отдаёт семафор раньше, чем задача начала ждать, событие не потеряется — семафор останется отданным. Проверяют и то, что прерывание вызывает именно xSemaphoreGiveFromISR().
Когда нужен счётный семафор, а не двоичный?
Когда событий может прийти несколько, пока задача занята. Двоичный семафор запомнит только одно — остальные потеряются. Счётный увеличивает счёт при каждом give и уменьшает при каждом take, поэтому задача обработает все. Второе применение — учёт свободных ресурсов, например буферов: начальное значение равно их числу.
Можно ли защищать общий ресурс двоичным семафором?
Технически да, но лучше мьютексом. Во FreeRTOS у двоичного семафора нет наследования приоритета, и при защите ресурса возможна неограниченная инверсия приоритетов. Двоичный семафор оставляют для синхронизации, мьютекс — для взаимного исключения.
Источники
- 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: семафор для взаимного исключения возвращают, для синхронизации — обычно нет
- FreeRTOS Kernel V11.1.0, include/semphr.h (GitHub FreeRTOS/FreeRTOS-Kernel) — xSemaphoreCreateBinary(): во многих случаях быстрее и экономнее уведомление задачи; созданный так двоичный семафор сначала пуст и его нужно отдать перед первым взятием; двоичный семафор не использует наследование приоритета, для этого есть xSemaphoreCreateMutex(); xSemaphoreGive() нельзя вызывать из прерывания, для этого xSemaphoreGiveFromISR() с параметром pxHigherPriorityTaskWoken; xSemaphoreCreateCounting()
Нашли ошибку?
Смотрите также
- Мьютекс (mutex)Объект RTOS для взаимного исключения задач при доступе к общему ресурсу
- Отложенная обработка прерыванийКороткий обработчик прерывания и основная работа в задаче, которую он будит
- Очередь RTOSПотокобезопасный FIFO для обмена данными между задачами и прерываниями в RTOS
- ПрерываниеЗапрос периферии, вектор и обработчик прерывания
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер