Как реализовать критические разделы на ARM Cortex A9

15

Я портирую некоторый устаревший код с ядра 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);

Идея «ключа» состоит в том, чтобы разрешить вложенные критические секции, и они используются в начале и конце функций для создания реентерабельных функций.

Благодарность!

CodePoet
источник
1
пожалуйста, обратитесь к infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/… не делайте этого во встроенном asm кстати. сделайте это функцией, как в статье.
Джейсон Ху
Я ничего не знаю об ARM, но я ожидаю, что для мьютекса (или любой межпоточной или межпроцессной функции синхронизации) вы должны использовать «память» clobber, чтобы гарантировать, что a) все значения памяти, кешируемые в настоящее время в регистрах, сбрасываются обратно в память перед выполнением asm и b) любые значения в памяти, к которым осуществляется доступ после повторной загрузки asm. Обратите внимание, что выполнение вызова (как рекомендует HuStmpHrrr) должно неявно выполнять этот клоббер за вас.
Кроме того, хотя я до сих пор не говорю на ARM, ваши ограничения для 'key_' выглядят некорректно. Поскольку вы говорите, что он предназначен для повторного входа, объявление его как "= r" в блокировке кажется подозрительным. '=' означает, что вы намереваетесь перезаписать его, и существующее значение не имеет значения. Скорее всего, вы намеревались использовать «+», чтобы указать свое намерение обновить существующее значение. И снова для разблокировки, перечисление его в качестве ввода говорит gcc, что вы не собираетесь его менять, но если я не ошибаюсь, вы делаете (измените его). Я предполагаю, что это также должно быть указано как «+».
1
+1 за кодирование в сборке для такого высокопроизводительного ядра. В любом случае, это может быть связано с режимами привилегий?
Дзарда
Я уверен, что вам нужно будет использовать ldrexи strexделать это правильно. Вот веб-страница, показывающая, как использовать ldrexи strexреализовать спин-блокировку.

Ответы:

14

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

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

Имея функцию, подобную приведенной выше, можно легко ввести мьютекс через что-то вроде

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

В отсутствие ОС основная трудность часто связана с кодом «не удалось получить мьютекс». Если прерывание происходит, когда защищенный мьютексом ресурс занят, может потребоваться, чтобы код обработки прерывания установил флаг и сохранил некоторую информацию, чтобы указать, что он хотел сделать, а затем иметь любой основной код, который получает проверка мьютекса всякий раз, когда он собирается освободить мьютекс, чтобы увидеть, хочет ли прерывание что-то сделать, пока мьютекс удерживается, и, если это так, выполнить действие от имени прерывания.

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

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

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

Supercat
источник
7

Это тяжелый способ делать критические секции; отключить прерывания. Это может не работать, если ваша система имеет / обрабатывает ошибки данных. Это также увеличит задержку прерывания. В Linux irqflags.h есть несколько макросов, которые справляются с этим. cpsieИ cpsidинструкции , может быть полезным; Однако они не сохраняют состояние и не допускают вложения. cpsне использует регистр.

Для Cortex-A серии, ldrex/strexявляются более эффективными и может работать , чтобы сформировать взаимную блокировку для критической секции , или они могут быть использован с безблокировочными алгоритмами , чтобы избавиться от критической секции.

В некотором смысле ldrex/strexкажется, что ARMv5 swp. Однако их гораздо сложнее реализовать на практике. Вам нужен рабочий кеш и целевая память ldrex/strexдолжна быть в кеше. Документация ARM ldrex/strexотносительно неясна, поскольку они хотят, чтобы механизмы работали на процессорах не Cortex-A. Однако для Cortex-A механизм синхронизации локального кэша ЦП с другими ЦП тот же, что и для реализации ldrex/strexинструкций. Для серии Cortex-A резервный гранулят (размер ldrex/strexзарезервированной памяти) такой же, как строка кэша; вам также необходимо выровнять память по строке кэша, если вы хотите изменить несколько значений, например, с помощью двусвязного списка.

Я подозреваю, что есть небольшая ошибка.

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

Вы должны убедиться, что последовательность никогда не может быть прервана . В противном случае вы можете получить две ключевые переменные с включенными прерываниями, и снятие блокировки будет неправильным. Вы можете использовать swpинструкцию с памятью ключей для обеспечения согласованности на ARMv5, но эта инструкция устарела на Cortex-A в пользу того, ldrex/strexчто она лучше работает для многопроцессорных систем.

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

Кроме того, есть ли библиотека с открытым исходным кодом, которая имеет эти типы примитивов для ARM (или даже хорошую легкую библиотеку спинлока / семефоров)?

Это трудно написать портативным способом. Т.е. такие библиотеки могут существовать для определенных версий процессоров ARM и для конкретных ОС.

бесшумный шум
источник
2

Я вижу несколько потенциальных проблем с этими критическими разделами. Есть предостережения и решения для всех из них, но вкратце:

  • Ничто не мешает компилятору перемещать код по этим макросам для оптимизации или других случайных причин.
  • Они сохраняют и восстанавливают некоторые части состояния процессора, и компилятор ожидает, что встроенная сборка останется одна (если не указано иное).
  • Ничто не мешает возникновению прерывания в середине последовательности и изменяет состояние между моментом, когда он прочитан, и когда он записан.

Во-первых, вам определенно нужны некоторые барьеры памяти компилятора . 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

Брайан Сильверман
источник