Выскажу ещё раз: FreeRTOS сырая недоделка, смысла особого, без реализации ряда перечисленного (см. ниже) не имеет и, хуже того, наоборот вносит существенные ограничения для программиста. Если бы стояла задача выбрать ОС для МК, то я бы скорей https://en.wikipedia.org/wiki/NuttX
ориентировался на Nuttx (вот в Sony, Motorola и Samsung тоже так думают, см. по ссылкам в Wikipedia).
Какие проблемы есть у FreeRTOS. В первую очередь она реализует далеко не все функции ОС (
https://ru.wikiped …0%BA%D1%86%D0%B8%D0%B8), а только в основном многозадачность на уровне параллельных потоков (freertos.org/a00106.html), остальное отдано на откуп программисту. Т.е. само название ОС, на мой взгляд, уже немного не к месту. Это не ОС, а скорей библиотека для организации многозадачного исполнения. Для сравнения Nuttx (
http://nuttx.org/) помимо возможности запуска множества потоков даёт и загрузку программ, и распределение памяти, и файловую систему, и сеть, и графику даже, и понятие драйверов устройств, и типичное окружение подобное POSIX-подобной ОС, и полноценную C библиотеку. Т.е. программирование не начинает носить какой-то специфический характер.
Значимой проблемой FreeRTOS является отсутствие "типичного" окружения для C-программы. Сюда входят понятие загрузки/запуска процесса (и само понятие процесса), аргументы программы, переменные окружения, аллокатор памяти, файлы, включая стандартный ввод-вывод. Это отдано на откуп программисту и авторам С-библиотеки доступной для данной платформы.
С самой C-библиотекой возможна фундаментальная проблема: если FreeRTOS обеспечивает переключение потоков, то библиотека вообще должна знать о существовании ОС и уметь с ней интегрироваться. Потому что ряд функций в C-библиотеке не является принципиально потоко-безопасным. Начиная с аллокатора памяти, функций работы со временем, функций rand() и strtok(), файловых операций, и других. Более того, если библиотека осознанно не создавалась для работы в многопоточной среде, то она может и не сможет в ней работать при всём желании. Чтобы библиотека могла нормально работать в многопоточной среде она должна обладать понятием Thread Local Storage (TLS). К слову ряд алгоритмов синхронизации вообще эффективно без TLS не реализуется, поэтому в современных CPU для реализации TLS даже делаются специальные регистры (TLS можно реализовать на любом CPU, где есть указатель стека, но такое решение неэффективно...) Ранее в FreeRTOS не было понятия TLS, что очень удивляло. Но начиная с какой-то версии оно появилось:
https://www.freert …-storage-pointers.html Но сама интеграция C-библиотеки с FreeRTOS -- отдана на откуп программисту. Вроде бы тот же IAR интегрируется, но где сказано на каком уровне? Об этом не пишется, и наверняка не всё гладко, раз в прошлом об поддержке многозадачности никто особо не думал. Ответом может быть "не используйте функции libc" -- но зачем такая ОС нужна, если она программирование не упрощает, и не приближает к традиционному виду (для ПК), а наоборот, заводит в какие-то дебри нетрадиционного программирования, где невозможно повторное использование кода, использование сторонних библиотек (которые сильно полагаются на C-библиотеку) и т.п.
Функции предоставляемые FreeRTOS тоже не блещут. Вообще не понятно чем руководствуются авторы при выборе набора функций. Выбор в мелких RTOS часто не очень адекватный. Основными примитивами синхронизации доступными в современных "больших" ОС являются:
1) рекурсивный мьютекс (требует для реализации атомарных операций)
2) условная переменная (требует для реализации один мьютекс, один мьютекс или семафор, а также TLS для эффективной реализации);
3) счётный семафор (может быть реализован на условной переменной или на атомарных операциях), иногда не реализуется (в чистом виде может быть не нужен);
4) очередь сообщений (может быть реализована на мьютексе и семафоре);
5) атомарные операции:
5.1) спинлок (test and set);
5.2) счётчик;
5.3) чтение-инкремент (fetch and add);
5.4) сравнение и обмен (compare and swap, CAS, x86) или LL/SC (MIPS/ARM).
6) более сложные примитивы вроде read-write lock (реализуются на условной переменной или семафорах, последнее худший вариант).
Атомарные операции типа обычно реализуются непосредственно процессором и без их реализации невозможна эффективная реализация других примитивов, таких как семафор или мьютекс.
Краеугольным камнем здесь является условная переменная. Она позволяет реализовать множество остальных более сложных функций, любых нужных программисту. Сама реализация условной переменной -- достаточно сложна. Есть известная микрософтовская статья описывающая процесс создания алгоритма условной переменной и работающий результат там получается где-то с пятой попытки (
http://birrell.org …rs/ImplementingCVs.pdf).
Во FreeRTOS условных переменных нет. Есть семафоры, мьютексы, очереди сообщений и ряд малопонятных сущностей, которые скорей относятся не к ОС, а к уровню прикладной библиотеки (т.е. не решают функциональную задачу, а являются абстракцией нужной лишь для упрощения программирования). Из этого следствие, что на прикладном уровне практически очень сложно реализовать другие, не реализованные в FreeRTOS примитивы синхронизация -- их реализация становится достаточно сложна (хотя и не невозможна).
Нужно немного отступить и сказать как работает в других ОС. В самой ОС нет смысла реализовывать всё, более того, само ядро ОС, например у Linux, реализует ровно одну функцию -- futex. Идея в том, что, условно, где-то в ОС хранится адрес ячейки памяти (хранящей примитив синхронизации), на которой заблокировался поток. С этим адресом связана цепочка ожидающих потоков. И другой работающий поток может просигналить и ОС пробудит один или несколько потоков заблокированных на данной ячейке памяти. Такой функции достаточно для реализации семафоров, мьютексов и условных переменных, остальное может быть реализовано поверх них в библиотеке, а не в ОС.
На мой взгляд фундаментальной проблемой FreeRTOS (и многих мелких RTOS) является возможность ожидания потоком одного из _нескольких_ возможных событий. Аналог функций select() в Linux или WaitForMultipleEvents() в Windows. Потому, что когда один поток может ожидать ровно одно событие -- толку от такой многозадачности становится совсем нисколько, годится только для моргания светодиодом. Конечно, можно возразить, мол такая функция легко реализуется на очереди сообщений, которые во FreeRTOS есть, а не будь их, легко реализуются и без условной переменной. Да, действительно, но один ньюанс: ОЧЕРЕДЬ сообщений обрабатывает события СТРОГО ПО ОЧЕРЕДИ. Т.е. обработка в порядке приоритета становится невозможна. Более того, возможен очевидный вопрос (его всегда задавали про Quantum Leap, теперь state-machine.com) -- мол очередь может и переполниться. И действительно такая проблема есть и она не имеет решения без введения понятия атомарного события, которое или произошло (но неизвестно один раз или больше) или нет.
Для решения проблемы ожидания множества событий во FreeRTOS внедрили Event Flags, как это было сделано в ITRON. Есть функция ожидания одного из 32-х флагов одновременно. Лучше чем ничего, но всё ещё далековато от WaitForMultipleEvents(). И самое главное, такого примитива в "больших" ОС часто нет. Он не нужен. Если он нужен его можно тривиально реализовать на условной переменной. А вот в обратную сторону -- сильно сложней.
Реализация атомарных операций во FreeRTOS под вопросом. Обычно она размазывается по всем слоям между CPU, компилятором, библиотекой и ОС, в зависимости от архитектуры. На современных CPU от ОС обычно требуется только функция типа yield(). На старых CPU вся "атомарность" может быть программной (например, через запрет прерываний или вызов соответствующей функции ОС).
Нужно отдельно сказать о многозадачности. Обычно открывают Таненбаума, читают там про потоки и делают вывод, что многозадачная система -- многопоточная. И что задача -- то же самое, что и поток. Нет. Это порочный подход. ПРОГРАММЫ РЕАЛИЗУЮЩИЕ ФУНКЦИИ ЛОГИЧЕСКОГО УПРАВЛЕНИЯ (аппаратурой) ОБЫЧНО ЯВЛЯЕТСЯ СИЛЬНО ПАРАЛЛЕЛЬНЫМИ, и скорей представляются взаимосвязанной системой из большого числа конечных автоматов. Простая автомобильная сигнализация может реализовывать одновременно под двести параллельных задач. Если следовать логике, что одна задача -- один поток, то во-первых под каждую нужно выделить память под стек, причём нужно всегда помнить, что могут найтись функции требующие значительного объёма памяти на стеке, и практически сложно предсказать будут ли таковые вызываться в данном потоке. Такого объёма памяти под стеки уже может не найтись не то, что в микроконтроллере, но и в большой микропроцессорной 32-битной системе. Следующей и наиболее существенной проблемой будет СИНХРОНИЗАЦИЯ такого количества потоков. Практически это может быть не решаемая задача, с бесконечными исправлениями взаимоблокировок (deadlocks). Любой "большой" программист работавший с большими программными проектами представляет что это такое. Не даром большие программные проекты уходят от многопоточной архитектуры к событийно-ориентированной, где даже часто основная обработка событий ведётся в одном выделенном потоке, а другие потоки выполняют какие-то изолированные задачи и между собой практически не пересекаются. Пример? Классическим примером является графическая оконная система: практически все известные системы однопоточные и событийно-ориентированы.
Какое может быть решение? В моём представлении сложная современная многозадачная система использует комбинированный подход, который заключается в следующем:
1) существует небольшой пул потоков, буквально несколько штук;
2) существуют атомарные события (либо произошло в прошлом, либо нет, другая информация не несётся), которые могут быть сгенерированы любым потоком, и даже прерыванием, события хранятся в очереди с приоритетом (priority queue), события уже присутствующие в очереди не помещаются в неё повторно, и обрабатываться в порядке приоритета, а НЕ ПО ОЧЕРЕДИ;
3) существуют сторонние очереди сообщений, fifo-буфера и т.п. для передачи ассоциированной с событиями информации, если нужно (например, клавиатурный ввод генерирует соответвующее событие и помещает в fifo-буфер ввода код клавиши);
4) существует планировщик, который планирует обработку, вначале НАИБОЛЕЕ ПРИОРИТЕТНЫХ, событий очередным свободным потоком из пула: для соответствующего события запускается функция обработчика каком-либо потоке, и так в цикле, по мере освобождения потоков;
5) существуют функции обработки событий, за которыми стоит событийно-ориентированная система или система конечных автоматов взаимодействующая между собой через события (и другие механизмы, используемые для передачи любой другой информации, кроме самого факта возникновения события в прошлом);
Преимуществами данной системы перед однопоточной событийно-ориентированной системой с очередью событий (какими являются, например, графическая система Windows), являются:
* возможность обработчиков событий блокироваться на функциях синхронизации потоков, функциях ввода вывода, возможность выполнять длительные вычисления, БЕЗ ОСТАНОВКИ СИСТЕМЫ в целом, т.к. последующие события будут обработаны на других потоках -- такой подход существенно упрощает программирование для программистов;
* большая реактивность, меньшее время реакции;
* обработка событий в порядке приоритета, а не в порядке поступления;
* невозможность переполнения очереди событий (размер очереди не может быть больше количества всех возможных событий, которое конечно и известно в момент компиляции).
Преимуществами данной системы перед полностью многопоточной системой являются:
* ограниченное использование памяти (возможность исполнения разных задач, в разное время, на одном потоке или стеке);
* упрощение синхронизации, уменьшение возможности вызвать взаимоблокировку (deadlock).
Нужно сказать и о преимуществе такого подхода перед Big Loop: последний практически невозможнен для сколько-нибудь больших систем. Суть Big Loop, что программа постоянно исполняется в цикле и проверяет условия (факт возникновения событий) в этом же цикле. При росте числа задач цикл начинает оборачиваться исключительно медленно, на это тратится большая часть энергии CPU, реактивность становится очень низкой. Да и программирование BigLoop крайне неудобно для программиста, особенно в случае, когда есть блокирующиеся функции, длительные вычисления, ввод-вывод.
О существовании подобных описанной выше open source систем на настоящий момент я не знаю. Существуют системы реализующие отдалённо похожий функционал, это, в частности библиотека libevent.
На мой взляд современная ОС для embedded, для микроконтроллеров, должна реализовать похожий функциoнал.
Ссылки по теме:
О кооперативных ОС:
http://caxapa.ru/266984.html
Об архитектуре Big Loop:
http://caxapa.ru/279767.html http://caxapa.ru/619114.html