language agnostic - ¿Cómo se implementan las operaciones atómicas a nivel de hardware?
language-agnostic x86 (4)
Entiendo que en el lenguaje ensamblador las arquitecturas de conjuntos de instrucciones proporcionan operaciones de comparación e intercambio y operaciones similares. Sin embargo, no entiendo cómo el chip puede proporcionar estas garantías.
Como lo imagino, la ejecución de la instrucción debe
- Obtener un valor de la memoria
- Compare el valor
- Dependiendo de la comparación, posiblemente almacene otro valor en la memoria
¿Qué impide que otro núcleo acceda a la dirección de memoria después de que la primera la haya recuperado pero antes de que establezca el nuevo valor? ¿El controlador de memoria gestiona esto?
editar: si la implementación de x86 es secreta, me gustaría saber cómo la implementa cualquier familia de procesadores.
Aquí hay un article en software.intel.com que arroja poca luz sobre los bloqueos de nivel de usuario:
Los bloqueos de nivel de usuario implican utilizar las instrucciones atómicas del procesador para actualizar atómicamente un espacio de memoria. Las instrucciones atómicas implican utilizar un prefijo de bloqueo en la instrucción y tener el operando de destino asignado a una dirección de memoria. Las siguientes instrucciones pueden ejecutarse atómicamente con un prefijo de bloqueo en los procesadores Intel actuales: AGREGAR, ADC, Y, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NO, O, SBB, SUB, XOR, XADD y XCHG. [...] En la mayoría de las instrucciones, se debe usar explícitamente un prefijo de bloqueo, excepto para la instrucción xchg, donde el prefijo de bloqueo está implícito si la instrucción involucra una dirección de memoria.
En la época de los procesadores Intel 486, el prefijo de bloqueo solía reafirmar un bloqueo en el bus junto con un gran impacto en el rendimiento. Comenzando con la arquitectura Intel Pentium Pro, el bloqueo de bus se transforma en un bloqueo de caché. Aún se afirmará un bloqueo en el bus en las arquitecturas más modernas si el bloqueo reside en la memoria que no puede descartarse o si el bloqueo se extiende más allá de un límite de línea de caché que divide las líneas de caché. Ambos escenarios son improbables, por lo que la mayoría de los prefijos de bloqueo se transformarán en un bloqueo de caché que es mucho menos costoso.
Entonces, ¿qué impide que otro núcleo acceda a la dirección de memoria? El protocolo de coherencia de caché ya administra los derechos de acceso para las líneas de caché. Entonces, si un núcleo tiene derechos de acceso exclusivos (temporales) a una línea de caché, ningún otro núcleo puede acceder a esa línea de caché. Para acceder a esa línea de caché, el otro núcleo debe obtener primero los derechos de acceso, y el protocolo para obtener esos derechos involucra al propietario actual. En efecto, el protocolo de coherencia de caché evita que otros núcleos accedan a la línea de caché en silencio.
Si el acceso bloqueado no está vinculado a una sola línea de caché, las cosas se vuelven más complicadas. Hay todo tipo de casos de esquina desagradables, como accesos bloqueados sobre los límites de la página, etc. Intel no cuenta los detalles y es probable que utilicen todo tipo de trucos para hacer los bloqueos más rápidos.
El controlador de memoria solo se encarga de garantizar que la memoria y la memoria caché de los diferentes procesadores se mantengan consistentes: si escribe en la memoria en la CPU1, la CPU2 no podrá leer nada más desde su caché. No es su responsabilidad asegurarse de que ambos intenten manipular la misma información. Hay unas pocas instrucciones de bajo nivel que usan operaciones de bloqueo y atómicas. Estos se utilizan en el nivel del sistema operativo para manipular pequeños trozos de memoria para crear cosas como mutexes y semáforos, estos son literalmente uno o dos bytes de memoria que necesitan tener operaciones atómicas y sincronizadas realizadas en ellos. Luego, las aplicaciones se basan en esto para realizar operaciones en estructuras y recursos de datos más grandes.
El protocolo de coherencia de caché por sí solo no es suficiente para implementar operaciones atómicas. Digamos que quieres implementar un incremento atómico. A continuación están los pasos involucrados
- Cargue el valor de la memoria caché en un registro
- Incrementa el valor cargado en el registro
- Almacene el valor actualizado en la memoria caché
Entonces, para implementar las 3 instrucciones anteriores de forma atómica, primero debemos obtener acceso exclusivo a la línea de caché que contiene el valor requerido. Una vez que tengamos acceso exclusivo, no deberíamos renunciar al acceso exclusivo en esta línea de caché hasta que se complete la operación de "tienda". Esto significa que la CPU que ejecuta las instrucciones atómicas no debería responder a ningún mensaje de protocolo de coherencia de caché para esta línea de caché mientras tanto. Mientras que el diablo está en los detalles de cómo se implementa esto, al menos nos da un modelo mental
A continuación se muestra lo que Linus Torvalds mencionó sobre las instrucciones atómicas
Las instrucciones atómicas omiten el buffer de la tienda o al menos actúan como si lo hicieran, probablemente utilicen el buffer de la tienda, pero lo descargan y la tubería de instrucciones antes de la carga y esperan que se drene después, y tienen un bloqueo en la línea de caché que toman como parte de la carga, y se lanzan como parte de la tienda, todo para asegurarse de que la línea de caché no se interrumpa en el medio y que nadie más pueda ver los contenidos del buffer de la tienda mientras esto sucede.
Una implementación de ejemplo de esto es LL/SC donde un procesador realmente tendrá instrucciones adicionales que se utilizan para completar operaciones atómicas. Del lado de la memoria está la coherencia de la memoria caché. Uno de los protocolos de coherencia de caché más populares es el protocolo MESI . .