Комментарии 34
Упомяну также, что надо следить за нечетностью адресов, которая возникает из-за отличия команд ARM и Thumb-2.
В «нормальных» RTOS, идея работы с задачами состоит в том, чтобы PSP стек использовался отдельными задачами, а MSP стек использовался обработчиками исключений и ядром.
[...]
С другой стороны, переключение контекста не такое быстрое, а из-за того, что каждая задача имеет свой стек — дополнительный расходуется ОЗУ.
А в вашем случае откуда берётся экономия ОЗУ?
Да, тут неточность. По ОЗУ выигрыша нет, но справедливости ради, надо заметить, что для каждой задачи стек берется с запасом, поэтому откушивается ОЗУ с каждой задачей немного больше, чем хотелось бы.
Хотя, есть небольшой выигрыш, не надо сохранять контекст задачи, регистры R4-R11 и если с форматом работаем регистры флоат модуля тоже...
Очень крутая статья. И очень сложный материал.
А почему вы нормальные бесплатные компиляторы не используете, gcc то бишь? Там нет никаких искусственных ограничений по размеру
Спасибо, да так исторически сложилось, на работе только IAR (сертификат безопасности IEC 61508 у него есть https://www.iar.com/iar-embedded-workbench/certified-tools-for-functional-safety/, а это значит можно использовать библиотечные функции и оптимизацию, а то бы пришлось все функции покрывать юнит тестами, либо самим писать), ну и студентов на него же подсадил, но вариант с gcc для студентов рассматриваю...
www.edx.org/course/real-time-bluetooth-networks-shape-the-world
Тогда нельзя будет вызвать высокопритетную задачу синхронно.
В примере, например, Thread1Task постит событие TargetTask синхронно, т.е. прямо из Thread режима и тут же вызывает планировщик, который сразу вызывает задачу TargetThread без всяких PendSv. Т. Е. на переключение на высокопритетную задачу тут не тратится ничего, переключение практически мгновенное. Если планировщик вызывать из прерывания, то для такого случая придется вызывать исключение, которое вызовет планировщик. А зачем эти лишние телодвижения?
Преимущество именно в том, что одна задача может сигналить другой и тут же переключиться, если у нее приоритет выше без накладных вообще.
А из прерывания в любом случае надо будет на задачу переходить и переключаться в Thread режим.
github.com/AVI-crak/Rtos_cortex
Только начал читать, и сразу непонятка: UB — это что? UncleBob, UnsignedBit, UnversalBatut?
В кадре исключения сохраняются регистры R0-R3, R12 и LR, PC, xPSR.-все верно! Но если мы вручную прерываем задачу, которая работала с регистрами R4-R7, то нам их тоже необходимо сохранить в стеке, иначе при возврате из прерывания данные регистры могут быть испорчены и программа развалится.
Я добавил там "Остальные регистры R4-R11 не могут использоваться (в соответствии с C/C++ standard Procedure Call Standard for the ARM Architecture) в обработчике исключения и поэтому не входят в данный кадр."
так как..
Пока правил, что-то нажал не так и все пропало… не удобно на телефоне писать.
Еще раз.
А я понял вопрос: Это и есть вся идея вообще этого планировщика. Дело в том, что для компилятора все выглядит так, что вы начали работать в SomeTask, пришло прерывание и вы как бы продолжили в ней работать (хотя на самом деле попали в Планировщик) и как бы из SomeTask вы вызываете HighPriority Task,
Т.е. для компилятора все выглядит как прерывание задачи SomeTask, возврат в SometTask, вызов HightPriority Task из SomeTask, обратный возврат в SomeTask снова прерывание SomeTask и снова возврат в SomeTask.
Мы не вызываем Планировщик, мы просто возвращаемся из прерывания в место, где находится код планировщика, но по сути мы выполняем, всю ту же SomeTask, с её же регистрами и стеком.
Т.е. по сути Планировщик — это часть SomeTask..., поэтому никакие регистры мы не порушим, потому что когда мы вызовем HighPriority task компилятор будет использовать соглашение об вызове C++ и все сделает сам. В этом и есть фишка такого неблокирующего, Run to completion планировщика, там в статье про это написано более понятно :).
https://www.embedded.com/build-a-super-simple-tasker/
P.S. Если бы это была "нормальная" РТОС с задачами с бесконечным циклом (не Run Completion), то да — там надо было бы все это дело сохранить, а в нашем случае, все делается автоматом:
using the machine's natural stack protocol
Я походу пьяный был — такой бред написал.
Теперь правильный ответ:
Все функции используют соглашения об вызовах Си и С++, при входе в функцию, она должна сохранить preserved registers (R4-R11), если эта функция их использует. Так вот, например, функцию Schedule() компилятор переведет во что-то такое:
Schedule:
PUSH {R3-R5, LR} ; Собираемся использовать R4,R5, поэтому сохраняем их
LDR.N R5, [PC, #0x1c]
LDR R4, R5 ; А вот и использование
...
POP {R0, R4, R5, PC} ; Восстанавливаем R4, R5
После PendSV и вызова
; Выходим из исключения со значением 0xFFFFFFF9 и попадаем после этого в Schedule
BX r0
Попадаем прямиком на
Schedule:
PUSH {R3-R5, LR} ; Собираемся использовать R4,R5, поэтому сохраняем их
Вот и весь фокус с регистрами R4-R11, управление и слежение за ними — отдаем на откуп компилятору, он точно сделает все как надо.
Да, если на чистом ассемблере, то совершенно верно — вы сами должны будете обеспечивать сохранение регистров, которые в этой функции собираетесь использовать. Но это же правдиво ко всем случаям, даже если вы просто собираетесь из Си вызывать самописную функцию на ассемблере, то вы должны обеспечить соглашение об вызове для С и в том числе и сохранение регистров.
del
Одно жаль — почему-то с телефона комметарий не пошел, потому пишу как только смог добраться до компьютера. Не буду про студентов — опять ветка не туда уйдет. А вот про указатели таки скажу. Мне кажется, что задача отказа от указателей (как минимум применительно к данной задаче) — она фантомная. И фантомно же решена. В том смысле, что указатели никуда не делись. Просто код написан так, что операции с ними явно не упоминаются. Вам удалось сделать это просто классно. Снимаю шляпу. Но в целом — не кажется Вам что это несколько… хм… неправильно. Архитектурно процессор оперирует указателями. Вся адресная арифметика это именно они. И если мы пишем код непосредственно на нем исполняющися, то… по мне знание и понимание адресной арифметики один из краеугольных камней. Ладно, когда речь идет о Java, Python или ком-то подобном. Но С/С++? Да по голому железу. В конце-концов PC и SP тоже указатели…
Спасибо, за столь развернутый комментарий.
ПО поводу указателей, да я понимаю, что арифметика указателей на микроконтроллере — это важная вещь, но я приверженец безопасного программирования, где программисту дано делать не много, шаг влево- шаг вправо расстрел :). Потому, что самые плохие и стремные ошибки, они как раз из-за этих указателей. А не хотелось бы, чтобы датчик с ошибкой в ПО пошел в продукт.
Я хочу, чтобы программист использовал возможности компилятора, который сам сделает так как оптимальнее на данной машине и проверит все ваши задумки на этапе компиляции. В иделе — чтобы и бизнес логику проверил тоже(но это уже больше для языков поддерживающие формальные спецификации и формальные проверки и доказательства типа Haskell, да даже Rust и Ada более подходят для этого, чем С++, но к сожалению их мало кто знает, а С++ программистов пока много)
На Си, да там не обойтись без указателей, там это столп на котором все построено, но вот на С++ можно частично все это переложить на компилятор. Самое интересное то, что при оптимизации компилятор превратит ваш код без указателей на С++ в такой компактный, что на Си будет трудно такое написано без тонкого продумывания алгоритма. Т.е. на самом деле во многих случаях код будет не только надежнее, но еще и компактнее. У меня есть идея написания небольшой статьи по этому поводу, показать, как на Си и С++ одна и та же задача решается, при этом на С++ при оптимизации все получается компактнее, чем на таком же уровне оптимизации на Си. Я уже пытался это один раз сделать, но там все равно есть массив и указатель на объект интерфейса… https://habr.com/ru/post/347980/
А ведь можно обойтись и без массива и без циклов и без указателей вообще.
Но жизнь забавно поворачивается. Два моих учителя спорили о том, что лучше С или ассемблер. Один кричал: «ты на асме пишешь как на С — у тебя сплошные макросы», второй отвечал: «за то указателями я кручу куда как более виртуозно и производительно». Первый парировал: «а вот о сохранении регистров и целостности стека я даже не думаю, да и в критических местах ассемблерные вставки никто не запрещал». И не было числа тем спорам. А сейчас вот уже и С с пьедестала низкоуровневого языка двигают. Сначала Java замахнулась, но не смогла. Теперь вот то Rust, то С++. И ведь подвинут. Вопрос только кто именно. И, самое главное, на что мне на старости лет переучиваться придется. И нужен ли я буду хоть кому-то со своим С, как нужны сейчас спецы по коболу (и частично фортрану).
Но это черт возьми риторика. И комментарий здесь оставил ровно за тем, чтоб не возникало мысли о том, что в принципе без указателей обойтись можно. Нельзя. Вопрос только в том кто ими рулить будет — разработчик ПО или разработчик компилятора. И с кого спрашивать будут. Ошибка работы с указателями когда она в исполнении разработчика ПО — это всего лишь ошибка. Неприятно. Временами даже смертельно, но это можно исправить. А вот та же ошибка в компиляторе сильно более опасна. Так кто и как будет контролировать контролеров? Вот те мысли, которые крутятся в моей голове.
Но все равно пишите. Вас приятно читать. Мне плюсы не сильно знакомы, но пока вроде ничего неподьемного нет. Мало ли пригодится где. И еще раз — статья просто шикарна.
Про ошибки в компиляторах и уровни оптимизации. Тот же компилятор С от Sun позволял себе оптимизировать даже ассемблерные вставки. Крайне сомнительная функциональность, которая к счастью отключалась. Вообще я бы поспорил с утверждением «от языка не сильно зависит». Зависит довольно серьезно. Чем проще «внутренний мир» языка, тем меньше простора для оптимизции. Тот же С сам по себе прост. Его за то и любят. По сути его «внутренний мир» эквивалентен «внутреннему миру» практически любого современного процессора. И именно по этой причине не все можно пихать в switch() и в нем настолько неудобная работа со строками. Это обратная сторона переносимости. Да и потом — всем котроллерщикам известна простая истинна — самый большой уровень оптимизации совсем не значит самый лучший. Все ответственные места просто необходимо проверять на ассемблере. Благо JTAG/SWD с современными IDE это позволяют без утомительного изучения промежуточного ассемблера как это было некоторое время назад. И да, очень часто приходится бороться с «шибко умным» компилятором. Впрочем, как правило код пишется так что его уму особо разгуляться негде.
Хотя надо еще посмотреть что будет получаться на выходе у того же Rust'а когда код на нем не будет содержать unsafe блоков (за что ратуют фанаты Rust, и без чего он превращается просто в очередной диалект С/С++).
Кому интересно, здесь пример отчета по такому курсовомуРешил вспомнить молодость и почитал. Возник вопрос к Вам как к преподавателю: почему позволяете студентам так небрежно относиться к курсовым работам? Количество ошибок правописания зашкаливает.
Добрый День!
Помогите пожалуйста разобраться с Thumb2, Cortex-M3. Есть ли возможность переключаться между 16 и 32 битными инструкциями, или все жёстко зафиксировано ? Я вот смотрю дизассемблер в Keil, там почти все 32 битное. ( Mov, add, orr ...). Можно ли их сделать 16 битными, понятно с ограничениями ?
В кейле я не знаю, в IAR есть настройка режима ключем компиляции --thumb --arm, arm по моему будет всегда в 32 битный набор, в Thumb - как ляжет, так как он поддерживает Thumb2 - там часть команд 16 битная, часть 32 битная.
Переключение контекста и простой вытесняющий планировщик для CortexM