Я вижу несколько потенциальных проблем с этими критическими разделами. Есть предостережения и решения для всех из них, но вкратце:
- Ничто не мешает компилятору перемещать код по этим макросам для оптимизации или других случайных причин.
- Они сохраняют и восстанавливают некоторые части состояния процессора, и компилятор ожидает, что встроенная сборка останется одна (если не указано иное).
- Ничто не мешает возникновению прерывания в середине последовательности и изменяет состояние между моментом, когда он прочитан, и когда он записан.
Во-первых, вам определенно нужны некоторые барьеры памяти компилятора . 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 наиболее вероятно), поэтому вам следует подумать, если ваша система это делает.
Вот как я мог бы реализовать их таким образом, чтобы решить все потенциальные проблемы, которые я указал:
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"ands %[key], %[key], #0xC0\n\t"\
"cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
"tst %[key], #0x40\n\t"\
"beq 0f\n\t"\
"cpsie f\n\t"\
"0: tst %[key], #0x80\n\t"\
"beq 1f\n\t"\
"cpsie i\n\t"
"1:\n\t" :: [key]"r" (key_) : "memory", "cc")
Не забудьте скомпилировать, -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
например:
#define ARM_INT_UNLOCK(key_) asm volatile (\
"cbz %[key], 0f\n\t"\
"cpsie if\n\t"\
"0:\n\t" :: [key]"r" (key_) : "memory", "cc")
В качестве альтернативы, если вы вообще не заботитесь о FIQ, то это похоже на полное удаление / отключение их.
Если вы знаете, что ничто иное не изменяет какие-либо другие биты состояния в CPSR между блокировкой и разблокировкой, вы можете также использовать continue с чем-то, очень похожим на ваш исходный код, за исключением обоих "memory"
и "cc"
clobbers в обоих ARM_INT_LOCK
иARM_INT_UNLOCK