Потоковый буфер (stream buffer)
Прерывание UART или DMA отдаёт байты пачками разной длины, а задача хочет спать, пока не накопится целая посылка. Очередь FreeRTOS для этого тяжеловата, кольцевой буфер не умеет будить задачу. Потоковый буфер — компромисс между ними.
Обновлено Редакция GAW
Потоковый буфер (stream buffer) — примитив FreeRTOS для передачи непрерывного потока байтов от одного писателя одному читателю: из обработчика прерывания в задачу, из задачи в задачу или между ядрами двухъядерного процессора. Данные копируются: отправитель кладёт байты в буфер, читатель забирает их оттуда. Читатель может заблокироваться и не тратить процессорное время, пока в буфере не накопится заданное число байтов. Поверх потокового буфера построен message-буфер (message buffer), который передаёт не поток, а дискретные сообщения переменной длины.
Правило «один писатель — один читатель»
Это единственный объект FreeRTOS, который не защищён от нескольких одновременных писателей или читателей. Писатель и читатель могут быть разными задачами или прерываниями, но если писателей несколько, вызовы xStreamBufferSend() нужно сериализовать самим; то же с xStreamBufferReceive() для читателей. Документация предлагает выполнять вызов внутри критической секции с временем блокировки 0. Из прерывания пишут через xStreamBufferSendFromISR(). Для пробуждения читателя буфер использует уведомление задачи с индексом 0, поэтому собственные уведомления в задаче-читателе должны занимать другие индексы.
Уровень срабатывания
Буфер создаётся вызовом xStreamBufferCreate(xBufferSizeBytes, xTriggerLevelBytes) (или xStreamBufferCreateStatic() с собственным массивом). Второй параметр — уровень срабатывания: сколько байтов должно быть в буфере, чтобы заблокированный на чтении читатель проснулся.
| Уровень | Поведение читателя |
|---|---|
| 1 | просыпается от каждого байта |
| 10 | ждёт, пока накопится не меньше 10 байт |
| 0 | заменяется на 1 |
| больше размера буфера | недопустимо |
Если время блокировки истекает раньше, чем набрался уровень, читатель всё равно получает те байты, что есть. Так одна задача может разбирать посылки по UART, просыпаясь не на каждый байт, а на заголовок известной длины.
С чем не путать
- Очередь RTOS хранит элементы фиксированного размера и допускает много писателей и читателей; потоковый буфер легче, но рассчитан на пару «один — один» и работает с байтами произвольными порциями.
- Кольцевой буфер — структура данных без участия ядра: он не умеет блокировать читателя и будить его по уровню, зато не требует RTOS вовсе. Для чистого «голого» прерывания UART это обычный выбор, см. статью «Кольцевые буферы и UART».
- DMA переносит данные без процессора, но ему всё равно нужен буфер, из которого задача заберёт результат; потоковый буфер здесь годится как связка «прерывание завершения DMA — задача».
В заголовке stream_buffer.h объявлен и третий тип — батч-буфер (sbTYPE_STREAM_BATCHING_BUFFER) с тем же параметром уровня срабатывания. Поддержка потоковых буферов включена по умолчанию: configUSE_STREAM_BUFFERS равно 1, если не определено. Общие правила обмена между прерыванием и задачей — в статье «Прерывания и параллельность».
Частые вопросы
Чем потоковый буфер отличается от очереди FreeRTOS?
Очередь хранит элементы фиксированного размера и допускает сколько угодно писателей и читателей. Потоковый буфер передаёт непрерывный поток байтов произвольными порциями и рассчитан ровно на одного писателя и одного читателя — за счёт этого его реализация легче. Если писателей несколько, их вызовы xStreamBufferSend() придётся сериализовать самостоятельно, например в критической секции с нулевым временем блокировки. Данные в обоих случаях копируются.
Что такое уровень срабатывания (trigger level)?
Число байтов, которое должно накопиться в буфере, чтобы заблокированный читатель проснулся. При уровне 1 задача просыпается от каждого байта, при уровне 10 — когда в буфере не меньше 10 байт. Если время блокировки истекло раньше, задача всё равно получает те байты, что есть. Уровень 0 заменяется на 1, а уровень больше размера буфера задавать нельзя.
Чем message-буфер отличается от потокового?
Message-буфер передаёт дискретные сообщения переменной длины: читатель получает ровно то сообщение, что было записано, а не произвольный кусок потока. Он построен поверх потокового буфера, поэтому наследует его правило «один писатель — один читатель» и тоже копирует данные.
Источники
- FreeRTOS. FreeRTOS Kernel documentation: Stream and message buffers (Kernel features), 2026 — разд. Introduction: примитивы для связи задача — задача и прерывание — задача, оптимизированы под одного читателя и одного писателя — например, из обработчика прерывания в задачу или между ядрами двухъядерного процессора; данные копируются в буфер отправителем и из буфера читателем; потоковый буфер передаёт непрерывный поток байтов, message-буфер — дискретные сообщения переменной длины и построен поверх потокового; важное примечание: единственный объект FreeRTOS, где допустимы только один писатель и один читатель (разные задачи или прерывания — можно, несколько писателей или читателей — нельзя); при нескольких писателях вызовы xStreamBufferSend() нужно сериализовать, при нескольких читателях — xStreamBufferReceive(); способ — вызов внутри критической секции с временем блокировки 0
- FreeRTOS. FreeRTOS-Kernel V11.3.3: include/stream_buffer.h (GitHub, MIT) — xStreamBufferCreate( xBufferSizeBytes, xTriggerLevelBytes ); xTriggerLevelBytes — число байтов, которое должно оказаться в буфере, чтобы задача, заблокированная на чтении, была разблокирована: при уровне 1 задача просыпается от одного байта, при уровне 10 — не раньше, чем накопится 10 байт, либо по истечении времени блокировки, и тогда получает сколько есть; уровень 0 заменяется на 1; уровень больше размера буфера недопустим; типы sbTYPE_STREAM_BUFFER, sbTYPE_MESSAGE_BUFFER, sbTYPE_STREAM_BATCHING_BUFFER; xStreamBufferSendFromISR() для записи из прерывания; xStreamBufferCreateStatic() с собственным массивом uint8_t
- FreeRTOS. FreeRTOS Kernel documentation: Direct to task notifications; FreeRTOS-Kernel V11.3.3 include/FreeRTOS.h — потоковые и message-буферы используют уведомление задачи с индексом 0; FreeRTOS.h: configUSE_STREAM_BUFFERS равно 1, если не определено
Нашли ошибку? ·
Смотрите также
- Очередь RTOSПотокобезопасный FIFO для обмена данными между задачами и прерываниями в RTOS
- Кольцевой буферОчередь фиксированного размера с индексами головы и хвоста, замкнутая в кольцо
- Уведомление задачи (task notification)Событие FreeRTOS, отправляемое прямо задаче: быстрее и экономнее семафора, но получатель только один
- Критическая секцияУчасток кода под запретом прерываний
- Кольцевой буфер UARTБуфер SPSC с маской, ISR приёма и передачи на AVR и STM32, ORE и DOR, размер
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер
- DMAПрямой доступ к памяти без участия ядра