Пилю я тут потихоньку свой фреймворк для микроконтроллеров (https://github.com/KivApple/controllerFramework) и дошёл до осознания того, что там обязательно нужна небольшая RTOS (разумеется, опциональная). Разобраться, как переключать задачи на ARM Cortex-M3 мне труда не составило (для M4, насколько я понимаю, надо просто добавить сохранение состояния FPU), но теперь встали некоторые архитектурные вопросы.
1) Реализация планировщика. Собственно как? Сейчас тупой алгоритм со сложностью О(N). Бежим по связанному списку процессов и ищем процесс с максимальным приоритетом, который при этом готов к исполнению. Переключаемся на найденный процесс.
int maxPriority = THREAD_PRIORITY_STOPPED;
Thread *thread = currentThread;
do {
thread = thread->next;
if ((thread->priority > maxPriority) && (threadReady(thread, tickCounter))) {
maxPriority = thread->priority;
nextThread = thread;
}
} while (thread != currentThread->next);
Но это, вероятно, что-то не то. Нужна очередь с приоритетом. Типа извлекаем из очереди очередной процесс и переключаемся на него, а текущий процесс пихаем обратно в очередь. Если мы попали в планировщик раньше времени (обычно мы попадаем туда из обработчика SysTick), потому что процесс уснул - то не пихаем его обратно в очередь. Если мы хотим разбудить процесс (это делается в функции отправки сигнала процессу, а не в планировщике), то пихаем его в очередь. И тут я вижу множество разных вариантов реализации очереди с приоритетом - бинарная куча, полиноминальная куча, куча Фибоначчи, 2-3 куча... Какой из них следует выбрать? Какие алгоритмы применяются в других RTOS?
2) Как организовать запуск RTOS? Я создаю процесс с именем main и переключаюсь на него. Однако получается, что выполнение начинается с нового адреса, указанного юзером при инициализации RTOS. А существующие RTOS позволяют просто вызвать из main функцию типа start_rtos и дальнейший код автоматически продолжает исполнятся в рамках первого процесса. Как они так делают? Это получается надо весь текущий стек скопировать на место стека только что созданного процесса, а начинать с адреса возврата из функции инициализации (текущий стек обычно остаётся в качестве стека для прерываний)? Я туплю, как всё происходит.