|
scmRTOS+STM32L15x+OpenSTM(+CUBE), проблема с переключением контекста |
|
|
|
Oct 7 2015, 23:04
|
Участник

Группа: Участник
Сообщений: 31
Регистрация: 20-02-08
Пользователь №: 35 238

|
Добрый день. Перенес проект в Eclipse на CDT с таким набором: scmRTOS+STM32L15x+OpenSTM(+CUBE). Столкнулся с проблемой переключения контекста (после обновления Eclipse до Mars, всех плагинов и самой порты ОС): Выполняется только задача с максимальным приоритетом, остально время висим тут: CODE {...} while(CurProcPriority != SchedProcPriority); // until context switch done В отладчике вижу что CurProcPriority = SchedProcPriority, проблема знакомая с AVR8 и оптимизатором в IAR. Я грешил на что оставил stdlib при переносе и откатился в далекое прошлое, когда в проекте не было sdtlib был родной (порты OC) startup и все работало, поиграть с оптимизацией, собрать yagarto'ой, но это не изменило ситуацию. Решилось так: CODE volatile uint_fast8_t CurProcPriority; Хочется узнать почему она изначально не объявлена volatile как и SchedProcPriority?
|
|
|
|
|
Oct 8 2015, 08:47
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
День добрый.
volatile для CurProcPriority не нужен, потому что SchedProcPriority - volatile, и компилятор уже и так не имеет права выбросить это сравнение. Конечно, теоретически, компилятор может загрузить переменную CurProcPriority в регистр, и не перечитывать её каждый раз при сравнении. Но на практике такого до сих пор не встречалось. Думаю, что проблема всё-таки в чём-то другом. Обновление Eclipse и плагинов вряд ли могло повлиять. А вот обновление sdtlib (это что? SrdPeriphLib?) и особенно замена стартап-файла - повлиять могли. Такие симптомы бывают, когда не вызывается прерывание переключателя контекста или прерывание системного таймера. Проверьте имена обработчиков прерываний в стартап-файле. Они должны называться PendSVC_ISR() и SystemTimer_ISR().
Кроме того, насколько я знаю, CUBE захватывает системный таймер. Как вы решили эту проблему?
ЗЫ. Напишите версию компилятора и scmRTOS, а также какой порт вы используете (GCC/Cortex-M3 или GCC/Cortex-Mx).
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Oct 8 2015, 09:23
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
QUOTE (Сергей Борщ @ Oct 8 2015, 14:55)  для CurProcPriority volatile не нужен, потому что она локальная для каждого процесса. Эта переменная не меняется в течении всего времени выполнения программы. Поскольку при переключении процессов контекст также переключается полностью, то и сравнение всегда присходит с CurProcPriority текущего процесса вне зависимости от того, где она размещена - в ОЗУ или регистре. Согласен с Антоном, надо смотреть по какой причине не вызывается обработчик прерывания переключения контекста. Погоди, CurProcPriority - это же переменная ядра. Может ты попутал с const TPriority TBaseProcess::Priority? CurProcPriority - это приоритет текущего процесса, значение этой переменной меняется при переключении контекста. Думается, Антон прав, что формально компилятор может засунуть эту переменную в регистр и там держать, не перечитывать каждый раз из памяти. Вот бы и посмотреть по листингу, что там реально. Хотя тоже склоняюсь, что причина, скорее всего, не в этом. Смутно припоминаю, что этот момент пристально рассматривался, и даже был вариант, когда сравнение было вынесено во встраиваемую функцию, объявленную с квалификатором volatile, но потом остался нынешний вариант. Подробностей не помню.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Oct 8 2015, 14:42
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
QUOTE (dxp @ Oct 8 2015, 15:23)  Смутно припоминаю, что этот момент пристально рассматривался, и даже был вариант, когда сравнение было вынесено во встраиваемую функцию, объявленную с квалификатором volatile, но потом остался нынешний вариант. Подробностей не помню. Значит так, появилась ясность в вопросе про волатильность, Сергей наставил на путь истинный.  Суть в следующем. Главная тонкость состоит в том, что (цитата Сергея): QUOTE enable_context_switch() происходит в одном потоке, а disable_context_switch() - уже в другом CODE void TKernel::sched() { uint_fast8_t NextPrty = highest_priority(ReadyProcessMap); if(NextPrty != CurProcPriority) { SchedProcPriority = NextPrty; raise_context_switch(); do { enable_context_switch(); DUMMY_INSTR(); disable_context_switch(); } while(CurProcPriority != SchedProcPriority); // until context switch done } } Переменная CurProcPriority в контексте каждого процесса фактически константа, точнее, не константа, конечно, но в условнии CODE while(CurProcPriority != SchedProcPriority); происходит её чтение и с этого момента уже не важно, из памяти читать или из регистра, в данном процессе эта переменная не меняется Переменная SchedProcPriority является по сути признаком (меткой, тегом) следующего процесса. Если переключение контекстов произошло, т.е. поток управления прошёл через функцию CODE OS::TKernel::context_switch_hook(stack_item_t* sp) где перед выходом выполняется CODE CurProcPriority = SchedProcPriority; то мы оказываемся в "правильном" - т.е. в том, который был запланирован для переключения, - процессе, у которого его значение CurProcPriority равно SchedProcPriority, и даже если эта переменная закеширована в регистре, то это не мешает, т.к. для этого нового процесса это значение в регистре соответствует SchedProcPriority, хотя операцией CODE CurProcPriority = SchedProcPriority; это значение в регистр не заносилось. Иными словами, любой процесс, прошедший через CODE while(CurProcPriority != SchedProcPriority); имеет в регистре это значение, которое у каждого процесса своё. Т.е. тут можно было бы вообще не CurProcPriority использовать, а просто Priority процесса, но CurProcPriority в функциях ядра технически использовать проще (члены одного класса). Волатильность для SchedProcPriority нужна как раз по этой же причине: если компилятор закеширует значение этой переменной в регистре (см код функции), то после переключения контекста - т.е. в регистре из контекста другого процесса - будет лежать неверное значение: должно будет лежать значение, равное запланированному, которое в свою очередь равно приоритету процесса, на который произошло переключение, а будет лежать значение запланированного процесса, которое было загружено туда при прошлом переключении этого (на который переключились) процесса.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Oct 8 2015, 15:13
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Ага, идея понятна. Каждый процесс, засыпая, читает текущее значение CurProcPriority в какой-то регистр, потом проходит через строчку enable_context_switch(); , потом спит, потом, просыпаясь, уже имеет в нужном регистре правильное значение CurProcPriority. Поэтому, если переключение произошло, то в нужном регистре автоматически будет нужное значение.
В этой логике есть изъян. Проблема в том, что если процесс ещё не засыпал, то у него в регистре нет нужного значения! То есть, в начале работы оси, при переключении с наиболее приоритетного процесса на менее приоритетный, могут возникнуть проблемы. И, судя по описанию, возникли... В этой связи ещё прошу автора темы дать информацию о ключах компиляции.
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Oct 8 2015, 16:23
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
QUOTE (AHTOXA @ Oct 8 2015, 21:13)  В этой логике есть изъян. Проблема в том, что если процесс ещё не засыпал, то у него в регистре нет нужного значения! То есть, в начале работы оси, при переключении с наиболее приоритетного процесса на менее приоритетный, могут возникнуть проблемы. И, судя по описанию, возникли... Да, действительно, в самом начале все процессы, кроме самого приоритетного получают управление, "выныривая" из переключателя контекста, т.е. не проходя весь код функции, поэтому, если компилятор закешировал CurProcPriority при первом обращении, то в условии while будет использоваться неверное значение. QUOTE (AHTOXA @ Oct 8 2015, 21:13)  В этой связи ещё прошу автора темы дать информацию о ключах компиляции. Да, хотелось бы воспроизвести ситуацию, дабы убедиться, что так всё и есть. А заодно и workaround применить и проверить более эффективный, нежели простое volatile CurProcPriority.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Oct 8 2015, 16:44
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(dxp @ Oct 8 2015, 19:23)  Да, действительно, в самом начале все процессы, кроме самого приоритетного получают управление, "выныривая" из переключателя контекста, т.е. не проходя весь код функции, Нет. В самом начале все процессы "выныривают" в начало TProcess::exec. А уже потом, в процессе работы, попадают в sched() и выныривают из него. При старте все присходит совершенно аналогично прерыванию менее приоритетного процесса - его прерывают где угодно и после возврата управления он продолжает исполнение с той точки, где управление у него отобрали. Выходить из sched() процесс будет только в том случае, если сам добровольно отдал управление в каком-нибудь из сервисов ОС. И это бует происходить только тогда, когда процесс перед этим в sched() зашел. Так что все здесь с логикой хорошо и правильно. Цитата(AHTOXA @ Oct 8 2015, 18:13)  Ага, идея понятна. Каждый процесс, засыпая, читает текущее значение CurProcPriority в какой-то регистр, потом проходит через строчку enable_context_switch(); , потом спит, потом, просыпаясь, уже имеет в нужном регистре правильное значение CurProcPriority. Я думаю, логика была бы более прозрачна, если бы каждый процесс имел собственную функцию sched(). Тогда бы было ясно видно, что программа уходит в sched() одного процесса, а выскакивает из sched() другого. А из первого вынырнет, когда первый процесс снова получит управление. У нас же код этой функции один на все процессы и неочевидно, что по ходу выполнения программы enable_context_switch() делает один процесс, а следующий за ним disable_context_switch() будет делать уже другой. Но это должно быть хорошо заметно при первых отдачах управления, когда процесс делает enable_context_switch() и после прерывания выполнение продолжается с точки входа в exec() следующего процесса. Очень легко это увидеть в программе из двух-трех процессов, состоящих только из sleep(1). Еще раз про CurProcPriority - значение этой переменной по ходу выполнения программы, разумеется, меняется. Но каждый процесс всегда видит в ней только одно, свое значение. Иначе у нас не работал бы ни один сервис. Единственное место, где значение этой переменной меняется, вызывается из прерывания переключения контекста, а в прерывании понятие "текущий процесс" смысла не имеет.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Oct 8 2015, 17:20
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Цитата(Сергей Борщ @ Oct 8 2015, 21:44)  Еще раз про CurProcPriority - значение этой переменной по ходу выполнения программы, разумеется, меняется. Но каждый процесс всегда видит в ней только одно, свое значение. Вот смотри. Запускается ось, CurProcPriority = 0. Процесс 0 отработал, хочет отдать управление. Вызывает sleep(), оттуда вызывается TKernel::sched(): Там у нас вот что (см. комментарии): Код { uint_fast8_t NextPrty = highest_priority(ReadyProcessMap); if(NextPrty != CurProcPriority) /// [1] <<-Вот в этой строчке компилятор засовывает CurProcPriority в R1. Это R1 от процесса 0. { SchedProcPriority = NextPrty; raise_context_switch(); do { enable_context_switch(); // [2]-- здесь процесс 0 засыпает. DUMMY_INSTR(); disable_context_switch(); // [3]-- здесь просыпается процесс 1. Но в его R1 нет значения CurProcPriority! Он ещё не был в точке [1], } while(CurProcPriority != SchedProcPriority); // until context switch done } } Вот так и висим. А работало до этого у нас всё потому, что компилятор, по всей видимости, не догадывался кешировать CurProcPriority в регистре.
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Oct 8 2015, 17:35
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
QUOTE (AHTOXA @ Oct 8 2015, 23:20)  CODE { uint_fast8_t NextPrty = highest_priority(ReadyProcessMap); if(NextPrty != CurProcPriority) /// [1] <<-Вот в этой строчке компилятор засовывает CurProcPriority в R1. Это R1 от процесса 0. { SchedProcPriority = NextPrty; raise_context_switch(); do { enable_context_switch(); // [2]-- здесь процесс 0 засыпает. DUMMY_INSTR(); disable_context_switch(); // [3]-- здесь просыпается процесс 1. Но в его R1 нет значения CurProcPriority! Он ещё не был в точке [1], } while(CurProcPriority != SchedProcPriority); // until context switch done } } Вот так и висим. А работало до этого у нас всё потому, что компилятор, по всей видимости, не догадывался кешировать CurProcPriority в регистре. С чего ты взял, что процесс 1 выныривает в точке [3]? Первый раз он выныривает, как заметил Сергей, в начале своей exeс.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Oct 8 2015, 23:40
|
Участник

Группа: Участник
Сообщений: 31
Регистрация: 20-02-08
Пользователь №: 35 238

|
Добрый день.
Я проковырялся пару дней с разными версиями развития событий, хотя не первый год использовал scmRTOS на разных процах, в т.ч. и на STM32L15x (и F1xx). Пробовал обе порты, пробовал откатиться к версии когда был родной startup и старая версия порты (проект давний и он на старых прошивках живет). Вызов raise_context_switch() проходит, PendSVC_ISR() вызывается, контекст переключается (!), но из while(...) он выходит только в процесс с максимальным приоритетом, вот только он один исправно и живет. SystemTimer_ISR() тоже зовется, что касается "кубовского" обработчика, то он вызывается из хука таймера (там только счетчик миллисекунд, раньше так жил DMAшный драйвер I2C в какой-то версии CPAL). А, что интересно, "кубовская" часть живет себе исправно и scmRTOS-ные обертки для дров исправно работают - события и сообщения исправно ходят из ISR'ных callback'ов куба. В приступе паранои, я отключил все дрова и пробовал просто подергать один поток событиями из другого или еще проще поморгать светодиодами из разных потоков по sleep. Еще думал, что может дебаг портит все - попробовал отключить его и посмотреть времянки анализатором дергая ножками из raise_context_switch(), PendSVC_ISR() и SystemTimer_ISR() - все зовется, while(...) висит) Помню, что-то похожее давным-давно видел на AVR8, но там как-то быстро решилось все и я уже не помню причины.
Пока живу вторую неделю с volatile - работает. Одно не попробовал пройтись там дизасмом и посмотреть разницу - обрадовался, что заработало)
ЗЫ Ac6 версии 1.4, GNU Tools 1.3, ставил как плагин Eclipse.
|
|
|
|
|
Oct 9 2015, 02:03
|
Участник

Группа: Участник
Сообщений: 31
Регистрация: 20-02-08
Пользователь №: 35 238

|
Глянул дизасмом: Вариант без volatile (нерабочий): Код 08002206: beq.n 0x8002220 <OS::TKernel::sched()+40> 84 SchedProcPriority = NextPrty; >0800220a: str r2, [r0, #12] 90 DUMMY_INSTR(); >08002216: nop 93 while(CurProcPriority != SchedProcPriority); // until context switch done >0800221a: ldr r3, [r0, #0] 87 do >0800221c: cmp r3, r2 0800221e: bne.n 0x8002214 <OS::TKernel::sched()+28> Вариант с volatile (рабочий): Код 08002206: beq.n 0x8002222 <OS::TKernel::sched()+42> 84 SchedProcPriority = NextPrty; 0800220a: str r3, [r0, #12] 90 DUMMY_INSTR(); 08002216: nop 93 while(CurProcPriority != SchedProcPriority); // until context switch done 0800221a: ldr r2, [r0, #0] 0800221c: ldr r3, [r0, #12] 87 do 0800221e: cmp r2, r3 08002220: bne.n 0x8002214 <OS::TKernel::sched()+28> Все-таки регистр r2 перед сравнением не обновляется. Оптимизатор?
|
|
|
|
|
Oct 9 2015, 02:42
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
Спасибо за листинг! QUOTE (skyspark @ Oct 9 2015, 08:03)  Глянул дизасмом: Вариант без volatile (нерабочий): CODE 08002206: beq.n 0x8002220 <OS::TKernel::sched()+40> 84 SchedProcPriority = NextPrty; >0800220a: str r2, [r0, #12] 90 DUMMY_INSTR(); >08002216: nop 93 while(CurProcPriority != SchedProcPriority); // until context switch done >0800221a: ldr r3, [r0, #0] 87 do >0800221c: cmp r3, r2 0800221e: bne.n 0x8002214 <OS::TKernel::sched()+28> Загрузка SchedProcPriority производится в регистр r2 до nop, но перед сравнением этого не делается, а делаться должно, потому что SchedProcPriority - volatile. Посмотрите, пожалуйста, объявлена ли эта переменная у вас волатильной? И какая версия оси используется?
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Oct 9 2015, 04:41
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Цитата(skyspark @ Oct 9 2015, 07:03)  Вариант без volatile (нерабочий): Код 08002206: beq.n 0x8002220 <OS::TKernel::sched()+40> 84 SchedProcPriority = NextPrty; >0800220a: str r2, [r0, #12] 90 DUMMY_INSTR(); >08002216: nop 93 while(CurProcPriority != SchedProcPriority); // until context switch done >0800221a: ldr r3, [r0, #0] 87 do >0800221c: cmp r3, r2 0800221e: bne.n 0x8002214 <OS::TKernel::sched()+28> Хм. [r0, #12] - это SchedProcPriority [r0, #0] - это CurProcPriority У вас здесь похоже, всё наоборот: CurProcPriority - volatile (перечитывается перед сравнением), а SchedProcPriority - нет. Наверное, вы для проверки убирали volatile, и убрали немного не там
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Oct 16 2015, 16:24
|
Участник

Группа: Участник
Сообщений: 31
Регистрация: 20-02-08
Пользователь №: 35 238

|
Цитата(AHTOXA @ Oct 9 2015, 07:41)  Хм. [r0, #12] - это SchedProcPriority [r0, #0] - это CurProcPriority У вас здесь похоже, всё наоборот: CurProcPriority - volatile (перечитывается перед сравнением), а SchedProcPriority - нет. Наверное, вы для проверки убирали volatile, и убрали немного не там  Вот только добрался снова до этого кода. Да, по всей видимости ошибся (уж под утро дело было), но эффект был такой же как и ранее. Пробежался по истории репозитория увидел, что потребовалось еще одно изменение CODE INLINE static volatile uint_fast8_t & cur_proc_priority() { return Kernel.CurProcPriority; } Ну без этого "строгие" ++-ы валили ошибку.
|
|
|
|
|
Oct 16 2015, 16:52
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Эх, мы так не разберёмся. На вопросы о версии компилятора вы не отвечаете, ось используете со своими правками. Давайте попробуем по порядку. Попробуйте взять оригинальную версию оси, прямо из транка. Или вот временное зеркало версии для Cortex-Mx на гитхабе. Если ошибка повторится - давайте сюда версию компилятора, ключи компиляции и листинг проблемного места. Если нет - напишите тоже, мы все выдохнем и разойдёмся  PS. Чтобы узнать версию компилятора, наберите arm-none-eabi-gcc --version в командной строке.
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Oct 17 2015, 00:18
|
Участник

Группа: Участник
Сообщений: 31
Регистрация: 20-02-08
Пользователь №: 35 238

|
Цитата(AHTOXA @ Oct 16 2015, 19:52)  Если нет - напишите тоже, мы все выдохнем и разойдёмся  Выдыхаем, спасибо. Совладал. Проблемы обнаружилось две: 1. если собирать внешним make-ом получается не понятно что и виснет где и раньше. Переключился на internal стало интересно. 2. если отлаживаться st-util'ом виснет как и раньше, но после аппаратного сброса (без разрыва debug-сессии) работает правильно. Разобрался с настройками родного opencdt и с ним все стало хорошо. на всякий случай версия компилера (http://www.openstm32.org): arm-none-eabi-gcc.EXE (GNU Tools for ARM Embedded Processors) 4.9.3 20150529 (release) [ARM/embedded-4_9-branch revision 224288] P.S. Извините за беспокойство
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|