Встраиваемые системы
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% случаев. Как спроектировать архитектуру задач и как измерить что требование выполняется?