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

Мьютекс (mutex) в RTOS

Когда две задачи RTOS пишут в один UART или в одну структуру данных, нужен способ пускать к ресурсу только одну из них. Для этого служит мьютекс — «жетон», без которого к ресурсу не подходят. Разбираем, как он работает во FreeRTOS и чем отличается от семафора.

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

Мьютекс (mutex, от mutual exclusion — взаимное исключение) — объект операционной системы реального времени, который пропускает к общему ресурсу только одну задачу. Задача «берёт» мьютекс перед обращением к ресурсу и «отдаёт» его после. Пока мьютекс занят, другие задачи, пытающиеся его взять, ждут в заблокированном состоянии.

Как это выглядит во FreeRTOS

SemaphoreHandle_t uart_mutex;

void app_init(void) {
    uart_mutex = xSemaphoreCreateMutex();      /* before the scheduler starts */
}

void log_line(const char *s) {
    if (xSemaphoreTake(uart_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
        uart_write(s);                           /* only one task at a time */
        xSemaphoreGive(uart_mutex);              /* always give it back */
    } else {
        /* timeout: likely a design error, e.g. a deadlock */
    }
}

Руководство FreeRTOS подчёркивает, что механизм держится только на дисциплине: ничто не мешает задаче обратиться к ресурсу без мьютекса, но все задачи «договариваются» так не делать.

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

МьютексДвоичный семафор
Назначениезащита общего ресурсасинхронизация с событием
Кто отдаётта же задача, что взялаобычно прерывание или другая задача
Наследование приоритетаестьнет
Из прерываниянельзяможно (FromISR-функции)

Наследование приоритета нужно против инверсии приоритетов: если мьютекс держит низкоприоритетная задача, её приоритет временно поднимается до приоритета ждущей. Вторая опасность при работе с несколькими мьютексами — взаимная блокировка.

Когда мьютекс не нужен

Для обмена данными между прерыванием и основным циклом без RTOS мьютекс бесполезен: прерывание не может ждать. Там используют критическую секцию — короткий запрет прерываний. Общие приёмы борьбы с состоянием гонки — в статье «Прерывания и общие данные».

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

Чем мьютекс отличается от двоичного семафора?

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

Можно ли взять мьютекс в обработчике прерывания?

Во FreeRTOS — нет: semphr.h прямо запрещает это. Наследование приоритета имеет смысл только для задач, а прерывание не может ждать освобождения мьютекса. Из прерывания работают с двоичным семафором или уведомлением задачи, а сам ресурс обрабатывают в задаче.

Что будет, если задача возьмёт мьютекс и не отдаст?

Все остальные задачи, которым нужен ресурс, будут ждать вечно или до таймаута. Поэтому мьютекс отдают на всех путях выхода из функции, а ждут его с таймаутом, немного большим ожидаемого времени удержания. По руководству 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) — разд. 8.3: мьютекс — особый двоичный семафор для управления доступом к ресурсу, общему для нескольких задач (MUTual EXclusion); включается configUSE_MUTEXES; задача берёт жетон перед доступом и обязана его вернуть; семафор для взаимного исключения всегда возвращают, для синхронизации — обычно нет; механизм работает только на дисциплине программиста; 8.3.3: в отличие от двоичного семафора у мьютекса есть наследование приоритета, поэтому мьютексы нельзя использовать в прерываниях; 8.3.4: тупик; 8.3.5: рекурсивные мьютексы
  2. FreeRTOS Kernel V11.1.0, include/semphr.h (GitHub FreeRTOS/FreeRTOS-Kernel) — xSemaphoreCreateMutex(): мьютекс берут и отдают макросами xSemaphoreTake() и xSemaphoreGive(), рекурсивные макросы с ним использовать нельзя; семафор этого типа использует наследование приоритета, поэтому взявшая его задача ОБЯЗАНА его вернуть; мьютексы нельзя использовать в обработчиках прерываний; xSemaphoreCreateMutexStatic() — без динамической памяти

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