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

Очередь RTOS (message queue)

Когда одна задача производит данные, а другая их обрабатывает, нужен буфер, который не сломается от одновременного доступа и позволит потребителю спать, пока данных нет. В RTOS это очередь: она сочетает FIFO, защиту от гонки и ожидание с таймаутом.

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

Очередь RTOS (message queue) — объект операционной системы реального времени для передачи данных между задачами и из прерываний в задачи. Это FIFO с элементами фиксированного размера, доступ к которому ядро делает безопасным для нескольких отправителей и получателей. Задача, читающая пустую очередь, может заблокироваться до появления данных или до таймаута.

Свойства очереди FreeRTOS

СвойствоКак сделано
Размердлина и размер элемента задаются при создании
ПорядокFIFO; можно записать в начало или перезаписать единственный элемент
Передача данныхкопированием элемента в очередь
Доступлюбые задачи и прерывания; обычно много писателей, один читатель
Ожиданиепри чтении и записи задаётся время блокировки в тиках

Руководство FreeRTOS объясняет выбор копирования: отправитель может сразу переиспользовать переменную, данные можно отправить прямо со стека, а ядро само отвечает за память. Передать большой буфер можно, положив в очередь указатель на него.

Пример: прерывание и задача

static QueueHandle_t rx_queue;

void app_init(void) {
    rx_queue = xQueueCreate(64, sizeof(uint8_t));   /* 64 bytes */
}

void USART1_IRQHandler(void) {
    BaseType_t woken = pdFALSE;
    uint8_t byte = USART1->DR;
    xQueueSendFromISR(rx_queue, &byte, &woken);
    portYIELD_FROM_ISR(woken);
}

void rx_task(void *arg) {
    uint8_t byte;
    for (;;) {
        if (xQueueReceive(rx_queue, &byte, pdMS_TO_TICKS(100)) == pdTRUE) {
            /* handle the byte */
        } else {
            /* 100 ms of silence: end of message */
        }
    }
}

Это вариант отложенной обработки прерываний, который заодно передаёт сами данные. Если данных нет и нужен только сигнал о событии, хватает семафора.

Пример нагляден, но руководство FreeRTOS прямо предупреждает: побайтная очередь из прерывания UART в демо-проектах нужна для проверки порта и нагрузки на систему и не годится для рабочего кода, если данные идут не медленно.

Очередь или кольцевой буфер

По руководству FreeRTOS очередь неэффективна, если данные приходят с высокой частотой. Для потока байтов оно предлагает два способа: принимать через DMA или складывать байты в потокобезопасный буфер в ОЗУ — например, кольцевой буфер, — а задачу будить уведомлением, когда сообщение принято целиком или в линии наступила пауза. В очередь тогда кладут уже готовые сообщения. Как устроен такой буфер — в статье «Кольцевой буфер для UART на прерываниях».

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

Чем очередь RTOS отличается от кольцевого буфера?

Кольцевой буфер — просто структура данных: защиту от одновременного доступа и ожидание данных программист делает сам. Очередь RTOS включает и то и другое: ядро сериализует доступ, а читающая задача спит, пока данных нет, не тратя процессор. Цена — накладные расходы на вызов ядра при каждом элементе, поэтому FreeRTOS не советует гнать через очередь частый поток байтов.

Передавать в очередь данные или указатель?

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

Можно ли писать в очередь из прерывания?

Да, но специальной функцией xQueueSendFromISR(), без ожидания. Она сообщает через параметр pxHigherPriorityTaskWoken, разблокировала ли задачу с более высоким приоритетом; если да, в конце обработчика вызывают portYIELD_FROM_ISR(), чтобы переключиться на неё сразу.

Источники

  1. R. Barry, FreeRTOS Team. Mastering the FreeRTOS Real Time Kernel — A Hands-On Tutorial Guide, Release 1.1.0 (Amazon Web Services, 2023) — разд. 5.2: очередь хранит конечное число элементов фиксированного размера, длина и размер задаются при создании; обычно FIFO — запись в хвост, чтение из головы, возможна запись в голову и перезапись; FreeRTOS копирует данные в очередь (queue by copy), что не мешает передавать указатели; отправитель сразу может переиспользовать переменную; к очереди обращаются любые задачи и прерывания, обычно много писателей и один читатель; при чтении можно задать время блокировки, задача просыпается при появлении данных или по таймауту; разд. 7.7: очередь из прерывания удобна, но неэффективна при высокой частоте данных; побайтные очереди UART в демо — для проверки порта, в рабочем коде вместо них DMA или потокобезопасный буфер в ОЗУ с уведомлением задачи; разд. 8.4 — задача-привратник на очереди
  2. FreeRTOS Kernel V11.1.0, include/queue.h (GitHub FreeRTOS/FreeRTOS-Kernel) — прототипы: xQueueCreate(uxQueueLength, uxItemSize); xQueueSend(xQueue, pvItemToQueue, xTicksToWait); xQueueReceive(xQueue, pvBuffer, xTicksToWait); xQueueOverwrite(xQueue, pvItemToQueue); xQueueSendFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) — устанавливает флаг, если разблокирована задача с приоритетом выше текущей

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