При всей бесперспективности повторного изобретательства велосипедов порой от этого праздного занятия просто невозможно удержаться. И вот сейчас, зачем нужна ещё одна пародия на RTOS, когда их и так полно, и тут есть несколько соображений. Во-первых, как ни крути, а своя рубашка ближе к телу. И во-вторых, после некоторого опыта писания прошивок под наши приборы я осознал, что для полного счастья мне необходимо совсем не много. Это немного и является отличительной особенностью этого произведения.
1. Таймеры. Программные, с характерной длительностью в 10-тки миллисекунд. Неблокирующие ожидания таймаутов. Wait-ы внутри задачи.
2. Кооперативная многозадачность. В топку приоритеты, вытеснения, жесткое распределение времени. Специфика такая, что прошивка состоит из множества небольших задач, которые не несут сложных расчётов, а большую часть времени ждут срабатывания какого-нить флага или исключения, или запускаются периодически, чтобы собрать данные.
3. Быстрое определение необходимости выполнения задачи, и переход на следующую в противном случае. Переключение контекста - дорогостоящая операция, поэтому хотелось бы понять, чего ждёт задача и проверить это условие не входя в контекст задачи.
Вот, пожалуй, три вещи необходимые для счастья.
Про ОС как таковую тут можно говорить вообще с большой натяжкой. По сути нам необходим лишь планировщик, который бы в цикле перебирал все задачки. Прототипом был планировщик из какого-то примера с www.atmel.com. Уж очень мне нравится фирма, и даташиты толковые, и контроллеры функциональные, производительные и не обременённые ненужными сложностями, и примеры есть, и gcc умеет компилить под avr. Да, изначально MacrOS написана под AVR архитектуру, но для переноса на другой контроллер требуется лишь переписать код для миллисекундного таймера, т.к. всё остальное написано на C. Это в теории. на практике надо переписывать всё, что касается прерываний, а это usart, spi и т.д. Но вернёмся к планировщику. К сожалению, он не умел управлять контекстом, а наличие такой фичи сильно облегчает написание каждой отдельной задачи.
Спасение я нашел в функции setjmp. Но её использование весьма нетривиально, и именно тогда родилась идея, "а почему бы не обернуть ей вызов в макрос?" Сказано - сделано. Заводим на каждую задачку структуру, типа task, в которой храним контекст и ещё немного инфы, о которой скажу позже. Пишем макрос MOS_Scheduler(), который сохраняет контекст текущей задачи и делает longjmp в контекст планировщика. Всё просто! Планировщик, получив управление, переходит к следующей задаче, сохраняет свой контекст и делает longjmp в следующую task-у, и так по кругу. Но это слишком медленно, учитывая что часто надо проверить лишь состояние флага. Делаем финт ушами, в структуру задачи добавляем ссылку на фнкцию checker и пару переменных, которые будем передавать этой функции как параметры. Одна - указатель, вторая - значение которое надо проверить. После в планировщике вместо перехода в задачу сначала делаем вызов checker-а, а по его результатам определяем, требуется ли переход в задачу. И тут Остапа понесло: можно сделать checker-ы разного типа, которые будут проверять налицие флага, отсутствие, делать anr или or со значением в указателе. В общем, практически всё, что может понадобится для отслеживания нажатия на кнопку или отслаживания аппаратного флага.
Теперь ввести таймеры не составит большого труда. Ставим условие, что проверка на таймаут должна быть быстрой, а старт можно сделать и дольше. Создаём массив с таймерами. Заводим глобальный счётчик, который инктементируется от аппаратного таймера. При старте высчитываем время, когда таймер закончится, и сбрасываем флаг таймаута. В прерывании от аппаратного таймера пробегаемся по всему массиву, и выставляем флаги для тех таймеров, чьи значения меньше текущего. Значения этих флагов позже проверятся в задаче, которая ждёт таймаута.
На этом пока всё, минимально-необходимое окружение есть. Надеюсь, что это окружение будет полезно не только мне, и велосипед приобретёт статус ноу-хау. Исходники имеются и будут выложены, как я приведу их в божеский вид. Нетерпеливым просьба писать на мыло.
1. Таймеры. Программные, с характерной длительностью в 10-тки миллисекунд. Неблокирующие ожидания таймаутов. Wait-ы внутри задачи.
2. Кооперативная многозадачность. В топку приоритеты, вытеснения, жесткое распределение времени. Специфика такая, что прошивка состоит из множества небольших задач, которые не несут сложных расчётов, а большую часть времени ждут срабатывания какого-нить флага или исключения, или запускаются периодически, чтобы собрать данные.
3. Быстрое определение необходимости выполнения задачи, и переход на следующую в противном случае. Переключение контекста - дорогостоящая операция, поэтому хотелось бы понять, чего ждёт задача и проверить это условие не входя в контекст задачи.
Вот, пожалуй, три вещи необходимые для счастья.
Про ОС как таковую тут можно говорить вообще с большой натяжкой. По сути нам необходим лишь планировщик, который бы в цикле перебирал все задачки. Прототипом был планировщик из какого-то примера с www.atmel.com. Уж очень мне нравится фирма, и даташиты толковые, и контроллеры функциональные, производительные и не обременённые ненужными сложностями, и примеры есть, и gcc умеет компилить под avr. Да, изначально MacrOS написана под AVR архитектуру, но для переноса на другой контроллер требуется лишь переписать код для миллисекундного таймера, т.к. всё остальное написано на C. Это в теории. на практике надо переписывать всё, что касается прерываний, а это usart, spi и т.д. Но вернёмся к планировщику. К сожалению, он не умел управлять контекстом, а наличие такой фичи сильно облегчает написание каждой отдельной задачи.
Спасение я нашел в функции setjmp. Но её использование весьма нетривиально, и именно тогда родилась идея, "а почему бы не обернуть ей вызов в макрос?" Сказано - сделано. Заводим на каждую задачку структуру, типа task, в которой храним контекст и ещё немного инфы, о которой скажу позже. Пишем макрос MOS_Scheduler(), который сохраняет контекст текущей задачи и делает longjmp в контекст планировщика. Всё просто! Планировщик, получив управление, переходит к следующей задаче, сохраняет свой контекст и делает longjmp в следующую task-у, и так по кругу. Но это слишком медленно, учитывая что часто надо проверить лишь состояние флага. Делаем финт ушами, в структуру задачи добавляем ссылку на фнкцию checker и пару переменных, которые будем передавать этой функции как параметры. Одна - указатель, вторая - значение которое надо проверить. После в планировщике вместо перехода в задачу сначала делаем вызов checker-а, а по его результатам определяем, требуется ли переход в задачу. И тут Остапа понесло: можно сделать checker-ы разного типа, которые будут проверять налицие флага, отсутствие, делать anr или or со значением в указателе. В общем, практически всё, что может понадобится для отслеживания нажатия на кнопку или отслаживания аппаратного флага.
Теперь ввести таймеры не составит большого труда. Ставим условие, что проверка на таймаут должна быть быстрой, а старт можно сделать и дольше. Создаём массив с таймерами. Заводим глобальный счётчик, который инктементируется от аппаратного таймера. При старте высчитываем время, когда таймер закончится, и сбрасываем флаг таймаута. В прерывании от аппаратного таймера пробегаемся по всему массиву, и выставляем флаги для тех таймеров, чьи значения меньше текущего. Значения этих флагов позже проверятся в задаче, которая ждёт таймаута.
На этом пока всё, минимально-необходимое окружение есть. Надеюсь, что это окружение будет полезно не только мне, и велосипед приобретёт статус ноу-хау. Исходники имеются и будут выложены, как я приведу их в божеский вид. Нетерпеливым просьба писать на мыло.