Что в основном делает __asm__ __volatile__ ()
и что значение "memory"
для архитектуры ARM?
Работа __asm__ __volatile__ ( ":" memory")
Ответ 1
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
(ref).
Ответ 2
Эта последовательность представляет собой барьер планирования доступа к памяти компилятора, как указано в статье, на которую ссылается Udo. Этот тип специфичен для GCC - другие компиляторы имеют другие способы их описания, некоторые из них содержат более явные (и менее эзотерические) утверждения.
__asm__
является gcc-расширением разрешающих операторов языка ассемблера, вводимых в ваш C-код, используемых здесь для его свойства, чтобы иметь возможность указывать побочные эффекты, которые препятствуют компилятору выполнять определенные типы оптимизаций (которые в этом случай может привести к генерации неправильного кода).
__volatile__
требуется, чтобы оператор asm не переупорядочивался с любым другим изменчивым доступом к любому (гарантия на языке C).
memory
- это команда GCC, которая (сорт) говорит, что последовательность inm asm имеет побочные эффекты для глобальной памяти, и, следовательно, необходимо учитывать не только эффекты на локальные переменные.
Ответ 3
Смысл здесь поясняется:
http://en.wikipedia.org/wiki/Memory_ordering
В основном это означает, что код сборки будет выполнен там, где вы ожидаете. Он сообщает компилятору не изменять порядок инструкций. Это то, что закодировано до того, как этот кусок кода будет выполнен раньше и что будет закодировано после того, как будет выполнено после.
Ответ 4
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;
}