Очередь 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(), чтобы переключиться на неё сразу.
Источники
- 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 — задача-привратник на очереди
- 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) — устанавливает флаг, если разблокирована задача с приоритетом выше текущей
Нашли ошибку?
Смотрите также
- FIFO (буфер «первым пришёл — первым ушёл»)Аппаратная очередь между периферией и программой
- Кольцевой буферОчередь фиксированного размера с индексами головы и хвоста, замкнутая в кольцо
- Семафор (двоичный и счётный)Объект RTOS для сигнала о событиях и учёта свободных ресурсов
- Отложенная обработка прерыванийКороткий обработчик прерывания и основная работа в задаче, которую он будит
- Кольцевой буфер UARTБуфер SPSC с маской, ISR приёма и передачи на AVR и STM32, ORE и DOR, размер
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер