Я портирую некоторый устаревший код с ядра ARM926 на CortexA9. Этот код является непромокаемым и не включает ОС или стандартные библиотеки, все пользовательские. У меня сбой, который, по-видимому, связан с состоянием гонки, которое следует предотвратить путем критического разбиения кода.
Я хочу получить отзывы о моем подходе, чтобы увидеть, могут ли мои критические разделы быть неправильно реализованы для этого процессора. Я использую GCC. Я подозреваю, что есть небольшая ошибка.
Кроме того, есть ли библиотека с открытым исходным кодом, которая имеет эти типы примитивов для ARM (или даже хорошую легкую библиотеку спинлока / семефоров)?
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"orr r1, %[key], #0xC0\n\t"\
"msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))
Код используется следующим образом:
/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);
<access registers, shared globals, etc...>
ARM_INT_UNLOCK(key);
Идея «ключа» состоит в том, чтобы разрешить вложенные критические секции, и они используются в начале и конце функций для создания реентерабельных функций.
Благодарность!
источник
ldrex
иstrex
делать это правильно. Вот веб-страница, показывающая, как использоватьldrex
иstrex
реализовать спин-блокировку.Ответы:
Самая сложная часть обработки критической секции без ОС - это не создание мьютекса, а выяснение того, что должно произойти, если код хочет использовать ресурс, который в настоящее время недоступен. Инструкции исключая загрузку и исключительные условия условного хранилища позволяют довольно легко создать функцию «подкачки», которая при наличии указателя на целое число атомарно сохраняет новое значение, но возвращает то, что содержало указанное целое число:
Имея функцию, подобную приведенной выше, можно легко ввести мьютекс через что-то вроде
В отсутствие ОС основная трудность часто связана с кодом «не удалось получить мьютекс». Если прерывание происходит, когда защищенный мьютексом ресурс занят, может потребоваться, чтобы код обработки прерывания установил флаг и сохранил некоторую информацию, чтобы указать, что он хотел сделать, а затем иметь любой основной код, который получает проверка мьютекса всякий раз, когда он собирается освободить мьютекс, чтобы увидеть, хочет ли прерывание что-то сделать, пока мьютекс удерживается, и, если это так, выполнить действие от имени прерывания.
Хотя можно избежать проблем с прерываниями, желающими использовать ресурсы, защищенные мьютексом, просто отключив прерывания (и действительно, отключение прерываний может устранить необходимость в любом другом типе мьютекса), в целом желательно избегать отключения прерываний дольше, чем необходимо.
Полезным компромиссом может быть использование флага, как описано выше, но с кодом основной линии, который собирается освобождать прерывания отключения мьютекса, и проверять вышеупомянутый флаг непосредственно перед этим (повторно активировать прерывания после освобождения мьютекса). Такой подход не требует, чтобы прерывания оставались отключенными очень долго, но будет защищать от возможности того, что если код основной линии проверяет флаг прерывания после освобождения мьютекса, существует опасность, что между временем, когда он видит флаг, и временем, когда он воздействуя на него, он может быть вытеснен другим кодом, который получает и освобождает мьютекс и действует на флаг прерывания; если основной код не проверяет флаг прерывания после освобождения мьютекса,
В любом случае, наиболее важным будет иметь средство, с помощью которого код, который пытается использовать защищенный мьютексом ресурс, когда он недоступен, будет иметь возможность повторить свою попытку после освобождения ресурса.
источник
Это тяжелый способ делать критические секции; отключить прерывания. Это может не работать, если ваша система имеет / обрабатывает ошибки данных. Это также увеличит задержку прерывания. В Linux irqflags.h есть несколько макросов, которые справляются с этим.
cpsie
Иcpsid
инструкции , может быть полезным; Однако они не сохраняют состояние и не допускают вложения.cps
не использует регистр.Для Cortex-A серии,
ldrex/strex
являются более эффективными и может работать , чтобы сформировать взаимную блокировку для критической секции , или они могут быть использован с безблокировочными алгоритмами , чтобы избавиться от критической секции.В некотором смысле
ldrex/strex
кажется, что ARMv5swp
. Однако их гораздо сложнее реализовать на практике. Вам нужен рабочий кеш и целевая памятьldrex/strex
должна быть в кеше. Документация ARMldrex/strex
относительно неясна, поскольку они хотят, чтобы механизмы работали на процессорах не Cortex-A. Однако для Cortex-A механизм синхронизации локального кэша ЦП с другими ЦП тот же, что и для реализацииldrex/strex
инструкций. Для серии Cortex-A резервный гранулят (размерldrex/strex
зарезервированной памяти) такой же, как строка кэша; вам также необходимо выровнять память по строке кэша, если вы хотите изменить несколько значений, например, с помощью двусвязного списка.Вы должны убедиться, что последовательность никогда не может быть прервана . В противном случае вы можете получить две ключевые переменные с включенными прерываниями, и снятие блокировки будет неправильным. Вы можете использовать
swp
инструкцию с памятью ключей для обеспечения согласованности на ARMv5, но эта инструкция устарела на Cortex-A в пользу того,ldrex/strex
что она лучше работает для многопроцессорных систем.Все это зависит от того, какое планирование имеет ваша система. Похоже, у вас есть только магистрали и прерывания. Вам часто нужны примитивы критического раздела, чтобы иметь некоторые привязки к планировщику в зависимости от того, с какими уровнями (системное / пользовательское пространство / и т. Д.) Вы хотите, чтобы критический раздел работал с ним.
Это трудно написать портативным способом. Т.е. такие библиотеки могут существовать для определенных версий процессоров ARM и для конкретных ОС.
источник
Я вижу несколько потенциальных проблем с этими критическими разделами. Есть предостережения и решения для всех из них, но вкратце:
Во-первых, вам определенно нужны некоторые барьеры памяти компилятора . GCC реализует их как клобберы . По сути, это способ сказать компилятору: «Нет, вы не можете перемещать доступы к памяти через этот фрагмент встроенной сборки, поскольку это может повлиять на результат доступа к памяти». В частности, вам нужны оба
"memory"
и"cc"
clobbers, как в начале, так и в конце макросов. Это предотвратит переупорядочение других вещей (например, вызовов функций) относительно встроенной сборки, потому что компилятор знает, что они могут иметь доступ к памяти. Я видел GCC для состояния удержания ARM в регистрах кода состояния через встроенную сборку с"memory"
клобберами, поэтому вам определенно нужен"cc"
клоббер.Во-вторых, эти критические секции сохраняют и восстанавливают намного больше, чем просто то, включены ли прерывания. В частности, они сохраняют и восстанавливают большую часть CPSR (Текущий регистр состояния программы) (ссылка для Cortex-R4, потому что я не смог найти хорошую диаграмму для A9, но она должна быть идентичной). Существуют тонкие ограничения, вокруг которых можно изменять части состояния, но здесь это более чем необходимо.
Среди прочего, это включает коды условий (где
cmp
сохраняются результаты таких инструкций , чтобы последующие условные инструкции могли воздействовать на результат). Компилятор определенно будет смущен этим. Это легко разрешимо, используя"cc"
клоббер, как упомянуто выше. Однако это будет приводить к сбою кода каждый раз, поэтому это не похоже на то, с чем вы сталкиваетесь. Хотя в некоторой степени тикающая бомба замедленного действия, в этом случайном модифицировании другой код может заставить компилятор сделать что-то немного другое, что будет нарушено этим.Это также попытается сохранить / восстановить биты IT, которые используются , чтобы он мог делать все виды плохих вещей, но, вероятно, ничего не будет делать вообще. реализации условного выполнения Thumb . Обратите внимание, что если вы никогда не выполняете код Thumb, это не имеет значения. Я никогда не понимал, как встроенная сборка GCC имеет дело с битами IT, кроме того, что это не означает, что компилятор никогда не должен помещать встроенную сборку в IT-блок и всегда ожидает, что сборка заканчивается за пределами IT-блока. Я никогда не видел, чтобы GCC генерировал код, нарушающий эти предположения, и я сделал довольно сложную встроенную сборку с интенсивной оптимизацией, поэтому я уверен, что они верны. Это означает, что он, вероятно, не будет пытаться изменить биты ИТ, в этом случае все в порядке. Попытка изменить эти биты классифицируется как «архитектурно-непредсказуемый»
Последняя категория битов, которые будут сохранены / восстановлены (кроме тех, которые фактически отключают прерывания), являются битами режима. Они, вероятно, не изменятся, поэтому это, вероятно, не будет иметь значения, но если у вас есть какой-либо код, который намеренно меняет режимы, эти секции прерывания могут вызвать проблемы. Я полагаю, что переключение между привилегированным и пользовательским режимами - единственный случай, когда это делается.
В-третьих, ничто не мешает прерыванию изменять другие части CPSR между
MRS
иMSR
вARM_INT_LOCK
. Любые такие изменения могут быть перезаписаны. В большинстве разумных систем асинхронные прерывания не изменяют состояние кода, в котором они прерываются (включая CPSR). Если они это сделают, становится очень трудно рассуждать о том, что будет делать код. Однако это возможно (мне кажется, что изменение бита отключения FIQ наиболее вероятно), поэтому вам следует подумать, если ваша система это делает.Вот как я мог бы реализовать их таким образом, чтобы решить все потенциальные проблемы, которые я указал:
Не забудьте скомпилировать,
-mcpu=cortex-a9
потому что по крайней мере некоторые версии GCC (например, моя) по умолчанию используют более старый процессор ARM, который не поддерживаетcpsie
иcpsid
.Я использовал
ands
вместо простоand
в,ARM_INT_LOCK
так что это 16-битная инструкция, если она используется в коде Thumb. В"cc"
любом случае Clobber необходим, так что это только выигрыш в производительности / размере кода.0
и1
являются локальными метками , для справки.Они должны быть использованы так же, как ваши версии. Это
ARM_INT_LOCK
так же быстро / маленький, как ваш оригинальный. К сожалению, я не смог придумать, как сделать этоARM_INT_UNLOCK
безопасно, за несколько инструкций.Если ваша система имеет ограничения на отключение IRQ и FIQ, это можно упростить. Например, если они всегда отключены вместе, вы можете объединить в один
cbz
+,cpsie if
например:В качестве альтернативы, если вы вообще не заботитесь о FIQ, то это похоже на полное удаление / отключение их.
Если вы знаете, что ничто иное не изменяет какие-либо другие биты состояния в CPSR между блокировкой и разблокировкой, вы можете также использовать continue с чем-то, очень похожим на ваш исходный код, за исключением обоих
"memory"
и"cc"
clobbers в обоихARM_INT_LOCK
иARM_INT_UNLOCK
источник
для относительно простых критических секций вы можете использовать инструкции LDREX и STREX.
/programming/51795537/critical-sections-in-arm http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204f/Cihbghef.html
источник