Как стать автором
Обновить

Комментарии 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 для студентов рассматриваю...

НЛО прилетело и опубликовало эту надпись здесь

Не точно выразился, на самом деле стек мы не редактируем, а лишь добавляем новый кадр исключения, сейчас поменяю.

А как иначе-то? Как вынырнуть из обработчика исключения не туда откуда нырнул в этот обработчик, а в другое место, при этом не трогая стек «вручную»?

Тогда нельзя будет вызвать высокопритетную задачу синхронно.


В примере, например, Thread1Task постит событие TargetTask синхронно, т.е. прямо из Thread режима и тут же вызывает планировщик, который сразу вызывает задачу TargetThread без всяких PendSv. Т. Е. на переключение на высокопритетную задачу тут не тратится ничего, переключение практически мгновенное. Если планировщик вызывать из прерывания, то для такого случая придется вызывать исключение, которое вызовет планировщик. А зачем эти лишние телодвижения?


Преимущество именно в том, что одна задача может сигналить другой и тут же переключиться, если у нее приоритет выше без накладных вообще.


А из прерывания в любом случае надо будет на задачу переходить и переключаться в Thread режим.

Только начал читать, и сразу непонятка: UB — это что? UncleBob, UnsignedBit, UnversalBatut?

Подправил… Undefined/Unspecified Behaviour (UB)

Спасибо за статью!
В кадре исключения сохраняются регистры 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, управление и слежение за ними — отдаем на откуп компилятору, он точно сделает все как надо.

Да, компилятор все сделает за Вас если у Вас ассемблерная вставка в сишный файл. А если функция написана в ассемблерном файле (так конечно давно уже никто не делает), с компилятором ARMCC(Keil), приходится в ручную делать PUSH и POP регистров R4-R11.

Да, если на чистом ассемблере, то совершенно верно — вы сами должны будете обеспечивать сохранение регистров, которые в этой функции собираетесь использовать. Но это же правдиво ко всем случаям, даже если вы просто собираетесь из Си вызывать самописную функцию на ассемблере, то вы должны обеспечить соглашение об вызове для С и в том числе и сохранение регистров.

Спасибо. Приятно читать статьи от профессионалов. Отличный слог, простое и понятное изложение, фактическая точность — просто отлично. А современное железо делает ее крайне актуальной. Будет куда отправлять тех, кто кричит что хочу современное железо, а не абстрактный или устаревший процессор. И да — решение совершенно не перегружено. В той же FreeFTOS понять что-то становится сложно. Кросплатформенность просто так не дается. Поэтому просто отлично.

Одно жаль — почему-то с телефона комметарий не пошел, потому пишу как только смог добраться до компьютера. Не буду про студентов — опять ветка не туда уйдет. А вот про указатели таки скажу. Мне кажется, что задача отказа от указателей (как минимум применительно к данной задаче) — она фантомная. И фантомно же решена. В том смысле, что указатели никуда не делись. Просто код написан так, что операции с ними явно не упоминаются. Вам удалось сделать это просто классно. Снимаю шляпу. Но в целом — не кажется Вам что это несколько… хм… неправильно. Архитектурно процессор оперирует указателями. Вся адресная арифметика это именно они. И если мы пишем код непосредственно на нем исполняющися, то… по мне знание и понимание адресной арифметики один из краеугольных камней. Ладно, когда речь идет о Java, Python или ком-то подобном. Но С/С++? Да по голому железу. В конце-концов PC и SP тоже указатели…

Спасибо, за столь развернутый комментарий.
ПО поводу указателей, да я понимаю, что арифметика указателей на микроконтроллере — это важная вещь, но я приверженец безопасного программирования, где программисту дано делать не много, шаг влево- шаг вправо расстрел :). Потому, что самые плохие и стремные ошибки, они как раз из-за этих указателей. А не хотелось бы, чтобы датчик с ошибкой в ПО пошел в продукт.


Я хочу, чтобы программист использовал возможности компилятора, который сам сделает так как оптимальнее на данной машине и проверит все ваши задумки на этапе компиляции. В иделе — чтобы и бизнес логику проверил тоже(но это уже больше для языков поддерживающие формальные спецификации и формальные проверки и доказательства типа Haskell, да даже Rust и Ada более подходят для этого, чем С++, но к сожалению их мало кто знает, а С++ программистов пока много)


На Си, да там не обойтись без указателей, там это столп на котором все построено, но вот на С++ можно частично все это переложить на компилятор. Самое интересное то, что при оптимизации компилятор превратит ваш код без указателей на С++ в такой компактный, что на Си будет трудно такое написано без тонкого продумывания алгоритма. Т.е. на самом деле во многих случаях код будет не только надежнее, но еще и компактнее. У меня есть идея написания небольшой статьи по этому поводу, показать, как на Си и С++ одна и та же задача решается, при этом на С++ при оптимизации все получается компактнее, чем на таком же уровне оптимизации на Си. Я уже пытался это один раз сделать, но там все равно есть массив и указатель на объект интерфейса… https://habr.com/ru/post/347980/
А ведь можно обойтись и без массива и без циклов и без указателей вообще.

Ну, мы с Вами так или иначе не первый раз пересекаемся в комментариях. Я бы очень хотел работать с Вами или Вашими студентами. Очень может быть что код выпускаемый компанией стал бы надежнее (что главное), а может даже и быстрее и проще сопровождаемым. Увы, но пока понимание как написать такие вещи красиво есть у очень небольшого числа людей. Я приверженец «классического C» как «платформо независимого ассемблера с элементами структурного синтаксиса» (с) OCTAGRAM Увы, мой опыт говорит о том, что по крайней мере пока от этого подхода не уйти. Да и плюсы от ухода сомнительны. Должна прийти «та молодая шпана, что сотрет нас с лица земли» (с) Чайф и своим результатам покажет динозаврам на дверь. Но строго по тексту от Чайф'а: «Ее нет, нет, не-ет. Hет, нет, не-ет...» Так что жду Ваших студентов. И это вполне серьезно. Лучше таких знатоков плюсов, чем тех кто кричит «дайте Linux, а лучше Windows — без них не могем».

Но жизнь забавно поворачивается. Два моих учителя спорили о том, что лучше С или ассемблер. Один кричал: «ты на асме пишешь как на С — у тебя сплошные макросы», второй отвечал: «за то указателями я кручу куда как более виртуозно и производительно». Первый парировал: «а вот о сохранении регистров и целостности стека я даже не думаю, да и в критических местах ассемблерные вставки никто не запрещал». И не было числа тем спорам. А сейчас вот уже и С с пьедестала низкоуровневого языка двигают. Сначала Java замахнулась, но не смогла. Теперь вот то Rust, то С++. И ведь подвинут. Вопрос только кто именно. И, самое главное, на что мне на старости лет переучиваться придется. И нужен ли я буду хоть кому-то со своим С, как нужны сейчас спецы по коболу (и частично фортрану).

Но это черт возьми риторика. И комментарий здесь оставил ровно за тем, чтоб не возникало мысли о том, что в принципе без указателей обойтись можно. Нельзя. Вопрос только в том кто ими рулить будет — разработчик ПО или разработчик компилятора. И с кого спрашивать будут. Ошибка работы с указателями когда она в исполнении разработчика ПО — это всего лишь ошибка. Неприятно. Временами даже смертельно, но это можно исправить. А вот та же ошибка в компиляторе сильно более опасна. Так кто и как будет контролировать контролеров? Вот те мысли, которые крутятся в моей голове.

Но все равно пишите. Вас приятно читать. Мне плюсы не сильно знакомы, но пока вроде ничего неподьемного нет. Мало ли пригодится где. И еще раз — статья просто шикарна.
Упомянутая песня написана не «Чайфом», а Борисом Гребенщиковым. Она вышла в «Синем альбоме» Аквариума в 81-году, за 4 года до образования «Чайфа». А ошибки в компиляторах, бывают весьма серьёзные — но от языка это не сильно зависит. Потому что компиляторы давно уже не компилируют отдельные операторы, а выделяют из кода более сложные конструкции для лучшей оптимизации. Даже простой switch иногда неправильно компилируется на высоком уровне оптимизации.
Спасибо за поправку. Да, я в курсе что БГ был первым. Но мне в 81-ом было всего три, и, как следствие мне она знакома в исполнении Чайфа. Однако фактическая точность безусловно важна. Абсолютно согласен и принимаю.

Про ошибки в компиляторах и уровни оптимизации. Тот же компилятор С от Sun позволял себе оптимизировать даже ассемблерные вставки. Крайне сомнительная функциональность, которая к счастью отключалась. Вообще я бы поспорил с утверждением «от языка не сильно зависит». Зависит довольно серьезно. Чем проще «внутренний мир» языка, тем меньше простора для оптимизции. Тот же С сам по себе прост. Его за то и любят. По сути его «внутренний мир» эквивалентен «внутреннему миру» практически любого современного процессора. И именно по этой причине не все можно пихать в switch() и в нем настолько неудобная работа со строками. Это обратная сторона переносимости. Да и потом — всем котроллерщикам известна простая истинна — самый большой уровень оптимизации совсем не значит самый лучший. Все ответственные места просто необходимо проверять на ассемблере. Благо JTAG/SWD с современными IDE это позволяют без утомительного изучения промежуточного ассемблера как это было некоторое время назад. И да, очень часто приходится бороться с «шибко умным» компилятором. Впрочем, как правило код пишется так что его уму особо разгуляться негде.

Хотя надо еще посмотреть что будет получаться на выходе у того же Rust'а когда код на нем не будет содержать unsafe блоков (за что ратуют фанаты Rust, и без чего он превращается просто в очередной диалект С/С++).
Кому интересно, здесь пример отчета по такому курсовому
Решил вспомнить молодость и почитал. Возник вопрос к Вам как к преподавателю: почему позволяете студентам так небрежно относиться к курсовым работам? Количество ошибок правописания зашкаливает.

Да, согласен, оформление там не очень, но — 1. это черновая, первая версия, которую они прислали на почту для проверки, подписанная и проверенная на нормоконтроле уже лучше. 2. — да я не очень строго отношусь к оформлению, возможно зря. Буду строже относится.

Добрый День!

Помогите пожалуйста разобраться с Thumb2, Cortex-M3. Есть ли возможность переключаться между 16 и 32 битными инструкциями, или все жёстко зафиксировано ? Я вот смотрю дизассемблер в Keil, там почти все 32 битное. ( Mov, add, orr ...). Можно ли их сделать 16 битными, понятно с ограничениями ?

В кейле я не знаю, в IAR есть настройка режима ключем компиляции --thumb --arm, arm по моему будет всегда в 32 битный набор, в Thumb - как ляжет, так как он поддерживает Thumb2 - там часть команд 16 битная, часть 32 битная.

Спасибо за ответ ! Собственно вопрос был был о том как внутри Thumb2 управлять выбором 16 и 32 битных команд. Ответ уже найден.

"Thumb 16-bit Instruction Set" - команды которые пакуются в 16 бит

"Thumb 32-bit Instruction Set" - команды которые пакуются в 32 бит

эти таблицы легко найти в интернете.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории