Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
В данный момент, ядро портировано на следующие архитектуры:
ARM Cortex-M cores: Cortex-M0/M0+/M1/M3/M4/M4F (supported toolchains: GCC, Keil RealView, clang, IAR)
Microchip: PIC32/PIC24/dsPIC
TN_ListItem, для каждого добавляемого объекта в список, ядру пришлось бы отдельно где-то выделить этот TN_ListItem: работает гораздо дольше, нужно больше памяти, cache locality хуже.А про списки, правильно ли я понимаю, что при старте новой задачи память под новый экземпляр struct TN_Task выделяется приложением, а не ядром?Да, правильно. Небольшое уточнение только: память выделяется не при старте задачи, а при ее создании (созданную задачу можно останавливать и запускать сколько угодно раз; правда, в реальной жизни мне это ни разу не пригождалось).
struct TN_Task *task — это как раз указатель на дескриптор задачи, который приложение должно где-то выделить до вызова этого сервиса.девайс, анализирующий аналоговый сигнал с автомобильной свечи— уж не занимаетесь ли вы системами, оптимизирующими УОЗ через анализ функции тока плазмы от времени?
Позволяете ли вы вызывать из ISR все вызовы RTOS, или только подмножество?Как можно все сервисы RTOS вызывать из прерывания? В прерывании нельзя вызывать любой сервис, который может ждать. Пока что, по образу и подобию TNKernel и других РТОС, для прерываний существует отдельный набор сервисов, но вообще-то, как минимум на поддерживаемых в данный момент архитектурах, можно было бы просто позволить вызывать любые сервисы, которые не могут ждать. Но пока, как наследие от TNKernel, есть отдельный набор сервисов. Доп. инфу см. тут, тут.
Как код переноса SP в область ISR после входа (и по все видимости, сохранения минимального контекста — флагов и IP) обрабатывает ситуацию вложенных прерываний? Насколько он эффективен (какая латентность привносится в обработчик прерываний до пользовательского кода)?
Видел статью на хабре, но не зарегистрирован там… и тем не менее.
Такая реализация как у вас, довольно наивна. (комментирую статью на хабре)
у вас какие-то архитектурные недостатки видны.
ну например зачем спасать контекст треда если обрабатывается прерывание? если вы хотите переключить треды в результате обработки этого прерывания, это надо сделать позже.
треды должны быть изолированы и нельзя чтобы один тред останавливал другой. это в большинстве случаев сделать корректно нельзя. все что может сделать один тред по отношению к другому — запустить тред, послать сообщение в очередь. все. чтобы тред остановить надо посылать ему в очередь сообщение об остановке. поскольку корректно извне тред не остановишь в общем случае.
далее. надо изолировать треды и прерывания вообще. треды ничего не могут знать про прерывания, а прерывания не могут вызывать тредовые функции. обработчики прерываний лишь берут данные(или не берут ничего) по внешнему событию, и кладут их в очереди, кольцевые буферы, и сигналят неким способом о событии в “мир тредов”. такая штука надывается каналом(обычно кольцевой буфер). Кернел с разделенными мирами прерываниий и тредов запрещает прерывания всего в паре мест
при занесии данных в канал, и выборе из него. При переключении тредов и всех реализации всех функций из мира тредов, как то — локи мьютексов, всякие слипы и проч. вплоть до переключения тредов — не запрещаются прерывания вообще. тогда система не теряет событий. Наивная же реализация, когда прерывания везде запрещены-разрешены приводит к тому что на частых событиях происходит потеря данных.
Вообще рилтайм эт оне вполне — немедля запустить тред обработки ивента с железа, а немедля забрать у железа данные и положить в буфер для обработки, — это как минимум.
таймеры у вас какие-от странные — там просто сортированная очередь обьектов(двусвязный список) — ближе к голове — наиболее ранний по срабатыванию… а у вас там какой-то наворот, даже не стал разбираться.
таймеры надо делать — жесткие и мягкие. жесткие могут работать прямо из контекста таймерного прерывания, а мягкие — запускаются текущим тредом, например при ближайшем переключении тредов. жесткие для жесткого рантайма, мягкие — для каких-то не сильно рантаймовых действия, например светодиодом помигать и так далее.
обработку прерываний можно разбить на два этапа — isr и dsr. dsr — просто активируемые из isr хуки постобрабтоки данных из прерывания, работающие в контексте текущего треда или собственном стеке, например(поскольку в isr вообще не видят тредов и не знают что это такое(в системе строгого разделения прерываний и тредов))…
ну и так далее…
критические секции лучше не давать юзеру, любой слип или ожидание в них заблокируют систему. синхронизация делается мьютексом.
критическая секция это не запрет прерываний(это убийственно для рантаймовой системы), а запрет переключений тредов. запрет прерываний это вообще стоит особенной секцией оформлять.
как вы взаимную блокировку там обнаруживаете — непонятно, она может глубоко косвенной, через цепь тредов, когда они по кольцу захватили мьютексы и не отдают.
все опреации дложны быть с таймаутом, потому дедлоки там в принципе невозможны… дедлоки это у студентов в учебниках.
вообщем вот так.
While both Segmented and Unified Interrupt Architecture RTOSes enable deterministic real-time management of an embedded system, there are significant differences between them with regard to the efficiency and simplicity of the resulting system. Segmented RTOSes can accurately claim that they never disable interrupts within system services. However, in order to achieve this, they instead must delay application threads, introducing a possibly worse consequence. The segmented approach also adds measurable overhead to the context switch process, and complicates application development. In the end, a unified interrupt architecture RTOS demonstrates clear advantages for use in real-time embedded systems development.
ну например зачем спасать контекст треда если обрабатывается прерывание? если вы хотите переключить треды в результате обработки этого прерывания, это надо сделать позже.
треды должны быть изолированы и нельзя чтобы один тред останавливал другой. это в большинстве случаев сделать корректно нельзя. все что может сделать один тред по отношению к другому — запустить тред, послать сообщение в очередь. все. чтобы тред остановить надо посылать ему в очередь сообщение об остановке. поскольку корректно извне тред не остановишь в общем случае.
надо изолировать треды и прерывания вообще. треды ничего не могут знать про прерывания, а прерывания не могут вызывать тредовые функции. обработчики прерываний лишь берут данные(или не берут ничего) по внешнему событию, и кладут их в очереди, кольцевые буферы, и сигналят неким способом о событии в “мир тредов”. такая штука надывается каналом(обычно кольцевой буфер)
таймеры у вас какие-от странные — там просто сортированная очередь обьектов(двусвязный список) — ближе к голове — наиболее ранний по срабатыванию… а у вас там какой-то наворот, даже не стал разбираться.
таймеры надо делать — жесткие и мягкие. жесткие могут работать прямо из контекста таймерного прерывания, а мягкие — запускаются текущим тредом, например при ближайшем переключении тредов. жесткие для жесткого рантайма, мягкие — для каких-то не сильно рантаймовых действия, например светодиодом помигать и так далее.
обработку прерываний можно разбить на два этапа — isr и dsr…
критические секции лучше не давать юзеру, любой слип или ожидание в них заблокируют систему. синхронизация делается мьютексом.
как вы взаимную блокировку там обнаруживаете — непонятно, она может глубоко косвенной, через цепь тредов, когда они по кольцу захватили мьютексы и не отдают.
все опреации дложны быть с таймаутом, потому дедлоки там в принципе невозможны… дедлоки это у студентов в учебниках.
посмотрел по вашим таймерам. вот вопрос — зачем вообще модифицировать что-то в списке таймеров каждый тик???
делается так.
на системном тике сидит isr который просто инкрементит счетчик системных тиков. при старте он равен 0.
есть общий сортированный по времени срабатывания двусвязный список таймеров.
в начале он пуст.
при запуске таймера высчитывается его время срабатывания = текущее значение счетчика тиков+интервал срабатывания заданный таймеру. то есть вы храните не сам интервал(который декрементите там) а сразу значение счетчике систиков когда сработать(вам больше никакие декременты не нужны).
обьект с посчитанным временем срабатывания заносится в сортированный список. те кто раньше — те в голове.
isr тамера не просто икрементит счетчик тиков, но после этого смотрит — не больше или равен текущий счетчик, тому значению, при котором должен сработать первый в списке(самый ранний таймер). если текущий счетчик больше — первый таймер извлекается и запускается его хук(или что там у вас). если текущий счетчик меньше времени у первого таймера, то ни один таймер еще не созрел. ибо далее в списке — более поздние.
псевдокод иср
на самом деле если таймеров разумное количесвто — не тыщщи, таймерная иср только проверит первый в списке таймер на готовность и выйдет.void timerISR(){ ++__SysCLocks; //это типа системное время в тиках while(!__TImers.empty() && (__TImers.first()._clocks<__SysClocks)){ //есть созревший таймер TImer *lt = __Timers.removeFirst(); // lt->timerAction(); //тут зависит от реализации как реализуется периодичский таймер. //сам ли он себя обратно вставляет в список таймеров, или это делает данная функция //а этом псевдокоде таймер однократный //заметим, что посольку тут while - будут в цикле извлекаьтся все таймеры что созрели к данному тику. } }
к чему огород со специальными списками — непонятно.
остается только в момент старта таймера его поставить в нужное место в списке таймеров(сортировка по времени срабатывания в тиках ).
псевдокод startTimer(Timer* ft, int fticks){ ft->_clocks=__SysClocks+fticks; //вычисляем таймеру абс.время его сраатывания в тиках Timers.addSorted(ft); }
для оптимизации можно организовать несколько списков таймеров. например для которых квант времени 1 мс, 8 мс, 64 мс… если есть опасение что таймеров будет много… но в релаьном устройстве их, активных ну не более 10. в моей практике всегда одного списка хватало в силу небольшого количества запущенных таймеров.
TNeo сохраняет только «callee-saved» регистры, т.е. те, которые должна сохранять вызываемая функция, если эти регистры ей нужны
Находятся даже люди, которые не используют никаких ОС в микроконтроллерах, но я не считаю это хорошей практикой: если железо позволяет мне использовать ОС, я ее использую.
Внимательный читатель может задаться вопросом: почему мы используем только (N — 1) tick-списков, когда у нас вообще-то есть N списков? Это из-за того, что мы как раз хотим иметь возможность модифицировать таймеры из таймерных коллбэков. Если бы мы использовали N списков, и пользователь добавляет таймер с таймаутом, равным N, то новый таймер будет добавлен именно в тот список, который мы проходим в данный момент. Этого делать нельзя.
Если же мы используем (N — 1) списков, то мы гарантируем, что новый таймер не может быть добавлен в tick-список, который мы проходим в данный момент (кстати, таймер может быть убран из этого списка, но это не создаст проблем).
Как я, в итоге, написал новую RTOS, протестированную и стабильную