Барьер памяти и atomic_t на linux
В последнее время я читаю некоторые коды пространства ядра Linux, я вижу это
uint64_t used;
uint64_t blocked;
used = atomic64_read(&g_variable->used); //#1
barrier(); //#2
blocked = atomic64_read(&g_variable->blocked); //#3
Какова семантика этого фрагмента кода? Обеспечивает ли он выполнение # 1 перед # 3 на # 2.
Но я немного помешал, потому что
#A В 64-битной платформе макрос Atom64_read расширяется до
used = (&g_variable->used)->counter // where counter is volatile.
В 32-битной платформе она была преобразована для использования блокировки cmpxchg8b. Я предполагаю, что эти два имеют одинаковую семантику, а для 64-битной версии, я думаю, это означает:
- all-or-nothing, мы можем исключить случай, когда адрес не выравнивается и размер слова больше, чем размер родного слова процессора.
- не оптимизировать, заставить процессор считывать данные из памяти.
atomic64_read не имеет семантики для сохранения порядка чтения!!! см. this
#B макрос барьера определяется как
/* Optimization barrier */
/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")
Из wiki this просто запрещает gcc компилятор переупорядочивать чтение и запись.
Что меня путают, так это то, как отключить оптимизацию переупорядочения для CPU? Кроме того, могу ли я думать, что барьерный макрос является полным забором?
Ответы
Ответ 1
32-разрядные процессоры x86 не обеспечивают простые операции чтения атома для 64-битных типов. Единственная атомная операция на 64-битных типах на таких процессорах, которая имеет дело с "нормальными" регистрами, - это LOCK CMPXCHG8B
, поэтому она используется здесь. Альтернативой является использование регистров MOVQ
и MMX/XMM, но это требует знания состояния/регистров FPU и требует, чтобы все операции над этим значением выполнялись с инструкциями MMX/XMM.
В 64-разрядных процессорах x86_64 выровненные чтения 64-битных типов являются атомарными и могут выполняться с помощью инструкции MOV
, поэтому требуется только обычное чтение --- использование volatile
- это просто убедитесь, что компилятор действительно выполняет чтение и не кэширует предыдущее значение.
Что касается упорядочения чтения, встроенный ассемблер, который вы указываете, гарантирует, что компилятор выдает команды в правильном порядке, и это все, что требуется для процессоров x86/x86_64, при условии, что записи правильно упорядочены. LOCK
ed записи на x86 имеют полное упорядочение; plain MOV
write обеспечивает "причинную согласованность", поэтому, если поток A делает x=1
, а затем y=2
, если поток B читает y==2
, то последующее чтение x
будет видеть x==1
.
В IA-64, PowerPC, SPARC и других процессорах с более расслабленной моделью памяти может быть больше atomic64_read()
и barrier()
.
Ответ 2
Процессоры x86 не выполняют переупорядочение после чтения, поэтому достаточно предотвратить компилятор от какого-либо переупорядочения. На других платформах, таких как PowerPC, все будет выглядеть по-другому.