Después de hacer esta pregunta, he entendido que la instrucción atómica, como test-and-set, no involucraría al núcleo. Solo si un proceso necesita ser suspendido (para esperar para adquirir el bloqueo) o despertado (porque no pudo adquirir el bloqueo pero ahora sí), entonces el núcleo debe estar involucrado para realizar las operaciones de programación.

Si es así, ¿significa que la cerca de memoria, como std::atomic_thread_fence en c ++ 11, no involucrará también al núcleo?

4
Yves 12 feb. 2020 a las 11:35

2 respuestas

La mejor respuesta

1

En casi todas las CPU normales (del tipo que programamos en la vida real), las instrucciones de barrera de memoria no tienen privilegios y el compilador las utiliza directamente. De la misma manera que los compiladores saben cómo emitir instrucciones como x86 {{X0 }} para fetch_add (o lock xadd si usa el valor de retorno). O en otros ISA, literalmente las mismas instrucciones de barrera que usan antes / después de las cargas, tiendas y RMW para dar el pedido requerido. https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

En algún hardware o compilador hipotético arbitrario, cualquier cosa es posible, por supuesto, incluso si fuera catastróficamente malo para el rendimiento.

En asm, una barrera solo hace que este núcleo espere hasta que algunas operaciones anteriores (orden de programa) sean visibles para otros núcleos. Es una operación puramente local. (Al menos, así es como se diseñan las CPU de palabras reales, de modo que la coherencia secuencial sea recuperable con solo barreras locales para controlar el orden local de las operaciones de carga y / o almacenamiento. Todos los núcleos comparten una vista coherente de caché, mantenida a través de un protocolo como MESI: existen sistemas de memoria compartida no coherentes, pero las implementaciones no ejecutan C ++ std :: thread entre ellos y, por lo general, no ejecutan un núcleo de imagen de sistema único).

Nota 1: (incluso los atómicos sin bloqueo usualmente usan un bloqueo liviano).

Además, ARM antes de ARMv7 aparentemente no tenía instrucciones adecuadas de barrera de memoria . En ARMv6, GCC usa mcr p15, 0, r0, c7, c10, 5 como barrera.
Antes de eso ( g++ -march=armv5 y anteriores), GCC no sabe qué hacer y llama a __sync_synchronize (una función auxiliar libatómica de GCC) que se implementa de alguna manera para cualquier máquina El código se está ejecutando realmente. Esto puede involucrar una llamada al sistema en un hipotético sistema ARMv5 multinúcleo, pero lo más probable es que el binario se ejecute en un sistema ARMv7 o v8 donde la función de biblioteca puede ejecutar un dmb ish. O si se trata de un sistema de un solo núcleo, entonces podría ser un no operativo, creo. (El pedido de memoria de C ++ se preocupa por otros subprocesos de C ++, no por el orden de la memoria visto por los posibles dispositivos de hardware / DMA. Normalmente, las implementaciones suponen un sistema multinúcleo, pero esta función de biblioteca podría ser un caso en el que podría usarse una implementación de un solo núcleo .)


En x86, por ejemplo, std::atomic_thread_fence(std::memory_order_seq_cst) compila a mfence . Las barreras más débiles como std::atomic_thread_fence(std::memory_order_release) solo tienen que bloquear el reordenamiento en tiempo de compilación; El modelo de memoria de hardware en tiempo de ejecución de x86 ya es acq / rel (seq-cst + a store buffer). Por lo tanto, no hay instrucciones asm correspondientes a la barrera. (Una posible implementación para una biblioteca C ++ sería GNU C asm("" ::: "memory");, pero GCC / clang tiene barreras incorporadas).

std::atomic_signal_fence solo tiene que bloquear el reordenamiento en tiempo de compilación , incluso en ISA de orden débil, porque todas las ISA del mundo real garantizan que la ejecución dentro de un solo hilo vea su propia operaciones como sucede en el orden del programa. (El hardware implementa esto haciendo que las cargas inspeccionen el búfer de almacenamiento del núcleo actual). VLIW e IA-64 EPIC, u otros mecanismos ISA de paralelismo explícito (como Mill con sus cargas de visibilidad retardada), aún hacen posible que el compilador genere código que respete cualquier garantía de pedido de C ++ que involucre la barrera si hay una señal asíncrona (o interrupción para el código del núcleo) llega después de cualquier instrucción.


Usted puede mirar en el código de generación a sí mismo en el Godbolt compilador explorador:

#include <atomic>
void barrier_sc(void) {
    std::atomic_thread_fence(std::memory_order_seq_cst);
}

X86: mfence.
POTENCIA: sync.
AArch64: dmb ish (barrera completa en el dominio de coherencia "compartible interno").
BRAZO con gcc -mcpu=cortex-a15 (o -march=armv7): dmb ish
RISC-V: fence iorw,iorw

void barrier_acq_rel(void) {
    std::atomic_thread_fence(std::memory_order_acq_rel);
}

X86: nada
POTENCIA: lwsync (sincronización ligera).
AArch64: todavía dmb ish
BRAZO: todavía dmb ish
RISC-V: todavía fence iorw,iorw

void barrier_acq(void) {
    std::atomic_thread_fence(std::memory_order_acquire);
}

X86: nada
POTENCIA: lwsync (sincronización ligera).
AArch64: dmb ishld (barrera de carga, no tiene que drenar el búfer de la tienda)
ARM: aún dmb ish, incluso con -mcpu=cortex-a53 (un ARMv8): /
RISC-V: todavía fence iorw,iorw

6
Peter Cordes 12 feb. 2020 a las 15:01

Tanto en esta pregunta como en la referencia que está mezclando:

  • primitivas de sincronización, en el ámbito del ensamblador, como cmpxchg y vallas
  • sincronizaciones de procesos / hilos, como futexes

¿Qué significa "involucra al núcleo"? Supongo que te refieres a "(p) sincronizaciones de subprocesos": el subproceso se pone en suspensión y se despertará tan pronto como otro proceso / subproceso cumpla la condición dada.

Sin embargo, las primitivas de prueba y configuración como cmpxchg y las vallas de memoria son funcionalidades proporcionadas por el ensamblador del microprocesador . Las primitivas de sincronización del kernel finalmente se basan en ellas para proporcionar sincronizaciones de sistemas y procesos, utilizando el estado compartido en el espacio del kernel oculto detrás de las llamadas del kernel.

Puede consultar la fuente futex para obtener evidencia de ello. .

Pero no, las vallas de memoria no involucran al núcleo: se traducen a operaciones simples de ensamblador. Como lo mismo que cmpxchg.

1
Sigismondo 12 feb. 2020 a las 09:22