Встраиваемые системы

RTOS: FreeRTOS

2002 год. Ричард Барри пишет FreeRTOS в свободное время. Цель: Real-Time OS для 8-битных микроконтроллеров. Первая версия - 6 файлов, 9000 строк кода. Сегодня - в составе AWS IoT, на 40+ млрд устройств. Amazon Echo обрабатывает wake-word за < 10 мс. Tesla управляет климат-контролем как набором изолированных задач. Boeing 777 flight management computer работает под FreeRTOS с жёсткими временными гарантиями.

  • **Amazon Echo:** FreeRTOS обрабатывает wake-word detection за < 10 мс - отдельная задача с наивысшим приоритетом постоянно слушает микрофон через DMA + очередь
  • **Tesla:** климат-контроль и seat heating - отдельные RTOS-задачи с разными приоритетами, общаются через очереди без прямых вызовов
  • **Медицинские устройства:** кардиостимулятор - жёсткие deadline требования, пропущенный импульс критичен; FreeRTOS гарантирует детерминированный отклик
  • **Промышленные роботы:** FreeRTOS на ARM Cortex-M для 1 кГц контрольного цикла - vTaskDelayUntil обеспечивает точный период без накопленного drift

Tasks: бесконечный цикл с приоритетом

**FreeRTOS работает на 40+ млрд устройств. Amazon Echo. AirPods. Tesla климат-контроль. Boeing 777 flight management computer. Один планировщик, написанный в 2003 году.** В центре FreeRTOS - task: функция с бесконечным циклом и числовым приоритетом (0 = наименьший). Каждая задача получает собственный стек. Task Control Block (TCB) хранит состояние задачи: стек-указатель, приоритет, имя, статус. Планировщик прерывает любой task в любой момент - это не баг, это суть вытесняющего планирования. Для edge inference: задача обработки сенсора получает приоритет 5, задача логирования - 1. При поступлении данных от датчика ML-задача вытесняет логгер мгновенно.

**Состояния задачи:** Running (выполняется прямо сейчас, одна на ядро), Ready (готова, ждёт CPU), Blocked (ждёт события: очередь, семафор, таймер), Suspended (явно приостановлена через vTaskSuspend). Задача сама переходит в Blocked через vTaskDelay или xQueueReceive - это освобождает CPU для других. **Никогда не используй busy-wait loops** (`while (!flag)`) в задачах - это тратит CPU и может зависнуть при неправильных приоритетах.

В FreeRTOS задача logging_task (приоритет 1) выполняется. Задача ml_task (приоритет 5) переходит из Blocked в Ready. Что происходит?

Queues: IPC без shared memory

**Задача сенсора получила данные температуры. Задача ML-инференса должна их обработать. Как передать данные без гонок и volatile-хаков?** FreeRTOS Queue - thread-safe FIFO с copy semantics: данные копируются в очередь, не передаётся указатель. Это ключевое отличие от shared memory: нет гонок по умолчанию. Queue имеет фиксированный размер (N элементов фиксированного размера). xQueueSend блокируется если очередь полна, xQueueReceive - если пуста. Из ISR - только xQueueSendFromISR с portYIELD_FROM_ISR.

**Queue Sets:** если задача должна ждать данных из нескольких очередей (датчик температуры + датчик влажности + команды по UART), используй xQueueAddToSet / xQueueSelectFromSet. Это эффективнее, чем опрос каждой очереди по очереди. **Размер очереди:** слишком маленькая - ISR теряет данные при пике нагрузки. Слишком большая - RAM на Cortex-M0 стоит дорого. Правило: N = (max ISR rate) * (max ML task latency).

ISR вызывает xQueueSend (не FromISR) для передачи данных задаче. Что произойдёт?

Semaphores и мьютексы: синхронизация доступа

**Две задачи пишут в UART одновременно. Без синхронизации: обрывки строк, мусор в выводе.** FreeRTOS предлагает три инструмента. Binary semaphore: сигнал от ISR к задаче - ISR даёт (xSemaphoreGiveFromISR), задача берёт (xSemaphoreTake). Counting semaphore: пул ресурсов из N единиц, каждый Take уменьшает счётчик, Give - увеличивает. Mutex: эксклюзивный доступ к ресурсу - с priority inheritance. Критичное правило: mutex ДОЛЖЕН быть взят и отдан одной и той же задачей. Семафор - можно Give из другой задачи или ISR.

**Deadlock через неправильный порядок:** если задача A берёт mutex1 затем mutex2, а задача B берёт mutex2 затем mutex1 - deadlock гарантирован. Правило: всегда захватывать мьютексы в одном и том же порядке во всех задачах. **Mutex из ISR запрещён:** мьютекс имеет priority inheritance механизм, который требует контекста задачи. Из ISR - только binary semaphore.

Задача A (приоритет 1) держит мьютекс. Задача C (приоритет 5) пытается взять тот же мьютекс. Что происходит с приоритетом задачи A при использовании FreeRTOS mutex?

Планировщик: тики, приоритеты, real-time

**Real-time - это не 'быстро'. Real-time - это 'предсказуемо'. Система, которая отвечает за 10 мс в 99.999% случаев, лучше для RTOS, чем система, которая отвечает за 1 мс в 90% случаев.** Сердце FreeRTOS - tick interrupt: аппаратный таймер генерирует прерывание с частотой configTICK_RATE_HZ (обычно 1000 Гц = 1 мс тик). Каждый тик планировщик проверяет: есть ли задача с более высоким приоритетом в Ready-очереди? Равный приоритет - round-robin между задачами. Tickless idle: при отсутствии активных задач MCU переходит в sleep mode, пропуская тики. Для пробуждения от глубокого сна - wake timer.

**configMAX_PRIORITIES:** не устанавливай слишком большое значение - каждый уровень приоритета требует RAM для Ready-очереди. Для большинства embedded проектов достаточно 5-8 уровней. **vTaskDelay vs vTaskDelayUntil:** vTaskDelay откладывает на N тиков от текущего момента - накапливает drift. vTaskDelayUntil откладывает до абсолютного времени - период стабильный даже при джиттере тела задачи.

Повышение configTICK_RATE_HZ улучшает real-time характеристики системы

Более высокая частота тика увеличивает накладные расходы на прерывания и снижает время на полезную работу. Real-time определяется детерминизмом дедлайнов, а не частотой тика.

При 1000 Гц тик interrupt отнимает ~1-2% CPU на Cortex-M4 (1 мкс прерывание из 1000 мкс). При 10000 Гц - уже 10-20%. Для soft real-time достаточно 100-1000 Гц. Для hard real-time (медицина, авиация) - детерминизм важнее частоты: нужно гарантировать WCET (Worst-Case Execution Time), а не минимизировать средний отклик.

Задача управления мотором использует vTaskDelay(pdMS_TO_TICKS(1)) для 1 кГц цикла. Тело задачи занимает 0.3 мс. Какова реальная частота выполнения?

FreeRTOS: ключевые примитивы

  • **Task:** бесконечный цикл + приоритет + стек. TCB хранит состояние. Вытесняющий планировщик переключает контекст на каждый tick interrupt
  • **Queue:** thread-safe FIFO с copy semantics. ISR использует FromISR-версии. Основной IPC механизм - данные, не указатели
  • **Semaphore/Mutex:** binary semaphore для ISR->task сигнала, mutex с priority inheritance для exclusive resource access. Mutex - только в задаче, не в ISR
  • **Планировщик:** tick interrupt 1 кГц, вытесняющий + round-robin. vTaskDelayUntil для точного периода. Tickless idle для экономии энергии

Связанные темы

FreeRTOS строится на аппаратных механизмах и абстракциях ОС:

  • C для embedded: volatile и ISR — Фундамент межзадачного взаимодействия в FreeRTOS
  • Периферийные шины: SPI, I2C, UART — Управляются из отдельных FreeRTOS-задач через очереди
  • Real-Time Systems: модели и гарантии — Формальные модели RTOS, Rate-Monotonic Scheduling, анализ WCET
  • Синхронизация в ОС — Те же примитивы (mutex, semaphore, condvar) в ОС общего назначения
  • Параллельные вычисления — Shared memory, гонки данных и lock-free структуры - общая теория для RTOS и многопоточности

Вопросы для размышления

  • Задача A держит мьютекс на I2C-шину и пишет данные в сенсор (10 мс операция). Задача B с более высоким приоритетом хочет читать с того же сенсора. Как priority inheritance меняет поведение системы - и что произойдёт без него?
  • Очередь от ADC к ML-задаче переполняется при пике нагрузки. Какие стратегии решения существуют - и как выбрать между увеличением очереди, снижением частоты ADC и повышением приоритета ML-задачи?
  • Система с FreeRTOS получила требование: ответ на внешний сигнал не должен превышать 5 мс в 99.9% случаев. Как спроектировать архитектуру задач и как измерить что требование выполняется?

Связанные уроки

  • emb-04
  • emb-06
  • rts-05
  • os-05-sync
  • par-05
  • os-02-processes
  • rts-01
RTOS: FreeRTOS

0

1

Войти