Работа __asm__ __volatile__ (""::: "memory")
Что в принципе __asm__ __volatile__ ()
делает и каково значение "memory"
для архитектуры ARM?
4 ответа
asm volatile("" ::: "memory");
создает барьер памяти на уровне компилятора, заставляя оптимизатор не изменять порядок доступа к памяти через барьер.
Например, если вам нужно получить доступ к какому-либо адресу в определенном порядке (возможно, потому, что эта область памяти фактически поддерживается другим устройством, а не памятью), вы должны иметь возможность сообщить об этом компилятору, иначе он может просто оптимизировать ваши шаги для ради эффективности.
Предположим, в этом сценарии вы должны увеличить значение в адресе, прочитать что-то и увеличить другое значение в соседнем адресе.
int c(int *d, int *e) {
int r;
d[0] += 1;
r = e[0];
d[1] += 1;
return r;
}
Проблема в компиляторе (gcc
в этом случае) может изменить доступ к вашей памяти, чтобы получить лучшую производительность, если вы попросите об этом (-O
). Вероятно, приводит к последовательности инструкций, как показано ниже:
00000000 <c>:
0: 4603 mov r3, r0
2: c805 ldmia r0, {r0, r2}
4: 3001 adds r0, #1
6: 3201 adds r2, #1
8: 6018 str r0, [r3, #0]
a: 6808 ldr r0, [r1, #0]
c: 605a str r2, [r3, #4]
e: 4770 bx lr
Выше значений для d[0]
а также d[1]
загружаются одновременно. Предположим, это то, чего вы хотите избежать, тогда вам нужно сказать компилятору не переупорядочивать обращения к памяти и использовать asm volatile("" ::: "memory")
,
int c(int *d, int *e) {
int r;
d[0] += 1;
r = e[0];
asm volatile("" ::: "memory");
d[1] += 1;
return r;
}
Таким образом, вы получите последовательность инструкций так, как вы хотите:
00000000 <c>:
0: 6802 ldr r2, [r0, #0]
2: 4603 mov r3, r0
4: 3201 adds r2, #1
6: 6002 str r2, [r0, #0]
8: 6808 ldr r0, [r1, #0]
a: 685a ldr r2, [r3, #4]
c: 3201 adds r2, #1
e: 605a str r2, [r3, #4]
10: 4770 bx lr
12: bf00 nop
Следует отметить, что это всего лишь барьер памяти во время компиляции, чтобы избежать переупорядочения обращений к памяти компилятором, поскольку он не содержит никаких дополнительных инструкций на уровне оборудования для очистки памяти или ожидания завершения загрузки или сохранения. Процессоры все еще могут изменить порядок доступа к памяти, если у них есть архитектурные возможности и адреса памяти включены normal
тип вместо strongly ordered
или же device
( ссылка)
Эта последовательность является барьером планирования доступа к памяти компилятора, как отмечено в статье, на которую ссылается Удо. Этот специфичен для GCC - у других компиляторов есть другие способы их описания, некоторые из них с более явными (и менее эзотерическими) утверждениями.
__asm__
является расширением gcc, позволяющим вводить операторы языка ассемблера, вложенные в ваш код C - здесь оно используется как свойство, позволяющее задавать побочные эффекты, которые не позволяют компилятору выполнять определенные типы оптимизаций (что в этом случае может привести к неправильной генерации). код).
__volatile__
требуется, чтобы сам оператор asm не переупорядочивался с любым другим изменчивым доступом (гарантия на языке Си).
memory
это инструкция для GCC, которая (вроде) говорит, что встроенная последовательность asm имеет побочные эффекты на глобальную память, и, следовательно, необходимо учитывать не только влияние на локальные переменные.
Смысл объясняется здесь:
http://en.wikipedia.org/wiki/Memory_ordering
В основном это означает, что ассемблерный код будет выполняться там, где вы этого ожидаете. Это говорит компилятору не переупорядочивать инструкции вокруг него. Это то, что кодируется до того, как этот фрагмент кода будет выполняться до, а то, что кодируется после, будет выполняться после.
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_save\n"
" cpsid i" //disabled irq
: "=r" (flags) : : "memory", "cc");
return flags;
}