C variables volátiles y memoria caché

12 minutos de lectura

avatar de usuario
micronúcleo

El caché está controlado por el hardware de caché de forma transparente al procesador, por lo que si usamos variables volátiles en el programa C, ¿cómo se garantiza que mi programa lea datos cada vez desde la dirección de memoria real especificada pero no desde el caché?

Mi entendimiento es que,

  1. La palabra clave volátil le dice al compilador que las referencias variables no deben optimizarse y deben leerse como están programadas en el código.

  2. El caché está controlado por el hardware de caché de manera transparente, por lo tanto, cuando el procesador emite una dirección, no sabe si los datos provienen del caché o de la memoria.

Entonces, si tengo el requisito de tener que leer una dirección de memoria cada vez que sea necesario, ¿cómo puedo asegurarme de que no se remita desde el caché sino desde la dirección requerida?

De alguna manera, estos dos conceptos no encajan bien. Por favor, aclare cómo se hace.

(Imaginando que tenemos una política de reescritura en caché (si es necesario para analizar el problema))

Gracias Microkernel 🙂

avatar de usuario
andres cottrell

Desarrollador de firmware aquí. Este es un problema estándar en la programación integrada, y uno que hace tropezar a muchos desarrolladores (incluso a los más experimentados).

Mi suposición es que está intentando acceder a un registro de hardware y que el valor del registro puede cambiar con el tiempo (ya sea el estado de interrupción, el temporizador, las indicaciones de GPIO, etc.).

los volatile La palabra clave es solo una parte de la solución y, en muchos casos, puede no ser necesaria. Esto hace que la variable se vuelva a leer desde memoria cada vez que se usa (en lugar de ser optimizado por el compilador o almacenado en un registro de procesador a través de múltiples usos), pero si el “memoria” lo que se lee es un registro de hardware real frente a una ubicación en caché que es desconocida para su código y no se ve afectada por el volatile palabra clave. Si su función solo lee el registro una vez, entonces probablemente pueda dejarlo volatilepero como regla general, sugeriré que la mayoría de los registros de hardware se definan como volatile.

El problema más grande es el almacenamiento en caché y la coherencia de caché. El enfoque más fácil aquí es asegurarse de que su registro esté en un espacio de direcciones no almacenado en caché. Eso significa que cada vez que acceda al registro, tiene la garantía de leer/escribir el registro de hardware real y no la memoria caché. Un enfoque más complejo pero potencialmente mejor rendimiento es usar el espacio de direcciones en caché y hacer que su código fuerce manualmente las actualizaciones de caché para situaciones específicas como esta. Para ambos enfoques, cómo se logra esto depende de la arquitectura y está más allá del alcance de la pregunta. Podría involucrar MTRR (para x86), MMU, modificaciones de la tabla de páginas, etc.

Espero que ayude. Si me he perdido algo, házmelo saber y ampliaré mi respuesta.

  • El propósito de volatile, al usar un buen compilador, debe ser para asegurarse de que el código generado informe al procesador sobre todo lo que debe escribirse antes de cierto punto, y no le pida al procesador que lea la información hasta después. Un programador también puede necesitar usar intrínsecos u otros medios para forzar vaciados de caché de hardware, pero forzar un vaciado de caché de hardware sería inútil si un compilador estuviera registrando cosas en caché de formas que el hardware no conocía.

    – Super gato

    18 de febrero de 2017 a las 23:33

De su pregunta hay un error de concepto de su parte.
Volatile La palabra clave no está relacionada con el caché como usted describe.

Cuando la palabra clave volatile se especifica para una variable, le da una pista al compilador para que no haga ciertas optimizaciones ya que esta variable puede cambiar de otras partes del programa inesperadamente.

Lo que se quiere decir aquí es que el compilador no debe reutilizar el valor ya cargado en un registropero acceda a la memoria nuevamente ya que no se garantiza que el valor en el registro sea el mismo que el valor almacenado en la memoria.

El resto de la memoria caché no está directamente relacionado con el programador.

Me refiero a que la sincronización de cualquier memoria caché de la CPU con la RAM es un tema completamente diferente.

  • Entonces, si hubiera tomado un caso en el que una variable es actualizada por otro subproceso o controlador que lee desde el dispositivo de entrada, ¿cuál es la garantía de que estoy leyendo el valor correcto, no algo almacenado en caché? ¿Cómo se evita tal escenario en un código?

    – Micronúcleo

    24 de octubre de 2011 a las 8:14

  • Si utiliza volatile está garantizado que siempre leerá la última actualización que se realizó en la memoria desde otro hilo. Pero tengo la sensación de que su preocupación es más a nivel del sistema operativo, es decir, caché frente a sincronización de memoria.

    – Crátilo

    24 de octubre de 2011 a las 8:28


  • @Cratylus Si usa subprocesos, “más reciente”, “pasado” … no están claramente definidos entre subprocesos que se ejecutan en núcleos diferentes.

    – chico curioso

    13 de noviembre de 2019 a las 3:24

Mi sugerencia es marcar la página como no almacenada en caché por el administrador de memoria virtual.
En Windows, esto se hace configurando PAGE_NOCACHE al llamar VirtualProtect.

Con un propósito algo diferente, el Instrucciones SSE 2 tener el _mm_stream_xyz instrucciones para evitar la contaminación del caché, aunque no creo que se apliquen a su caso aquí.

En cualquier caso, no hay portátil forma de hacer lo que quieras en C; tienes que usar la funcionalidad del sistema operativo.

  • Entonces, ¿depende de la plataforma? Por lo tanto, ¿Caché no está controlado por hardware de caché? (si el hardware administrara el caché por completo, entonces no buscaría el indicador PAGE_NOCACHE, ¿verdad?)

    – Micronúcleo

    24 de octubre de 2011 a las 7:02

  • @Microkernel: Es es gestionado por el hardware. Pero el sistema operativo le dice al hardware qué hacer (después de todo, el hardware no tiene idea de cómo el sistema operativo quiere administrar la memoria), y le está solicitando al sistema operativo que haga lo que quiere. Y toda esta información se almacena en… ¿adivina dónde? — la memoria misma. Sin embargo, es un proceso pasivo: el sistema operativo solo interviene si algo sale mal (por ejemplo, falla de página). Aparte de eso, el hardware simplemente continúa haciendo lo que el sistema operativo le pidió que hiciera, sin la intervención del sistema operativo.

    – usuario541686

    24 de octubre de 2011 a las 7:03


  • Hmm, está bien… Parece que mi entendimiento está mal en alguna parte, ¡siempre creí que la memoria caché de la CPU es transparente para todos, excepto para el hardware de la memoria caché! ¿Alguna referencia que deba leer para acertar mis conceptos? ! Muchas gracias por la aclaración 🙂

    – Micronúcleo

    24 de octubre de 2011 a las 7:11


  • @Microkernel: ¡Claro! 🙂 Básicamente, el sistema operativo almacena toda su información de administración de memoria dentro de “tablas de página” en la memoria y le dice a la CPU dónde buscar la información. Luego, la CPU administra todo y le pide “ayuda” al sistema operativo cuando no puede decidir qué hacer. Puede leer acerca de la paginación aquí y sobre el almacenamiento en caché aquí; déjame saber si todavía tienes alguna pregunta. (Es por eso que dicen que el sistema operativo se encuentra entre el hardware y el software, ¡realmente lo hace!)

    – usuario541686

    24 de octubre de 2011 a las 7:14


Wikipedia tiene un artículo bastante bueno sobre MTRR (Registros de rango de tipo de memoria) que se aplican a la familia x86 de CPU.

Para resumirlo, comenzando con el Pentium Pro Intel (y AMD copiado) tenía estos registros MTR que podían establecer atributos no almacenados en caché, escritura simultánea, combinación de escritura, protección contra escritura o escritura diferida en rangos de memoria.

Comenzando con el Pentium III, pero hasta donde yo sé, solo es realmente útil con los procesadores de 64 bits, respetan los MTRR pero pueden ser anulados por las Tablas de atributos de página que permiten que la CPU establezca un tipo de memoria para cada página de memoria.

Un uso importante de los MTRR que conozco es la RAM de gráficos. Es mucho más eficiente marcarlo como combinación de escritura. Esto permite que la memoria caché almacene las escrituras y relaja todas las reglas de ordenación de escritura de la memoria para permitir escrituras en ráfagas de muy alta velocidad en una tarjeta gráfica.

Pero para sus propósitos, querrá una configuración de MTRR o PAT sin almacenar en caché o de escritura simultánea.

Como dices, el caché es transparente para el programador. El sistema garantiza que siempre vea el valor que se escribió por última vez si accede a un objeto a través de su dirección. Lo “único” en lo que puede incurrir si un valor obsoleto está en su caché es una penalización de tiempo de ejecución.

  • Solo si la máquina solo tiene una CPU.

    – JeremyP

    24 de octubre de 2011 a las 7:53

  • @JeremyP, creo que la pregunta aquí se hizo más allá del alcance del acceso simultáneo a la memoria compartida. Si tienes eso además, sí, todo se complica mucho más. A continuación, tendría que aplicar las herramientas adecuadas para garantizar la coherencia de los datos. Pero entonces, este es un problema más general, verlo a través del ángulo de los cachés probablemente tampoco sea la vista correcta.

    – Jens Gusted

    24 de octubre de 2011 a las 7:58

  • No creo que estuviera más allá del alcance del acceso concurrente a la memoria. La premisa de la pregunta es que hay es acceso concurrente a la memoria, de lo contrario, como señala, el caché es transparente.

    – JeremyP

    24 de octubre de 2011 a las 8:05

  • La máquina no necesita tener más de una CPU. Los registros de control de dispositivos mapeados en memoria pueden tener el mismo efecto (para MCU duras, el diseñador puede tener cuidado de no almacenar en caché ese espacio de direcciones, para softcores en FPGA/PLD, no necesariamente). Consulte la página 4 de altera.com/ja_JP/pdfs/literature/hb/nios2/n2sw_nii52007.pdf

    – Dmitri

    8 oct 2015 a las 15:37

  • @JeremyP “Solo si la máquina solo tiene una CPUEso no siempre es falso, pero es extremadamente engañoso. Debería decir: solo si la máquina no tiene múltiples unidades de procesamiento que no están destinadas a soportes de subprocesos. Si la CPU está diseñada para admitir subprocesos, está garantizado.

    – chico curioso

    13 de noviembre de 2019 a las 3:10

avatar de usuario
muviciel

volatile se asegura de que los datos se lean cada vez que se necesitan sin molestarse con ningún caché entre la CPU y la memoria. Pero si necesita leer datos reales de la memoria y no datos almacenados en caché, tiene dos opciones:

  • Cree un tablero donde dichos datos no se almacenen en caché. Este ya puede ser el caso si se dirige a algún dispositivo de E/S,
  • Use instrucciones de CPU específicas que omitan el caché. Esto se usa cuando necesita limpiar la memoria para activar posibles errores SEU.

Los detalles de la segunda opción dependen del sistema operativo y/o la CPU.

  • Solo si la máquina solo tiene una CPU.

    – JeremyP

    24 de octubre de 2011 a las 7:53

  • @JeremyP, creo que la pregunta aquí se hizo más allá del alcance del acceso simultáneo a la memoria compartida. Si tienes eso además, sí, todo se complica mucho más. A continuación, tendría que aplicar las herramientas adecuadas para garantizar la coherencia de los datos. Pero entonces, este es un problema más general, verlo a través del ángulo de los cachés probablemente tampoco sea la vista correcta.

    – Jens Gusted

    24 de octubre de 2011 a las 7:58

  • No creo que estuviera más allá del alcance del acceso concurrente a la memoria. La premisa de la pregunta es que hay es acceso concurrente a la memoria, de lo contrario, como señala, el caché es transparente.

    – JeremyP

    24 de octubre de 2011 a las 8:05

  • La máquina no necesita tener más de una CPU. Los registros de control de dispositivos mapeados en memoria pueden tener el mismo efecto (para MCU duras, el diseñador puede tener cuidado de no almacenar en caché ese espacio de direcciones, para softcores en FPGA/PLD, no necesariamente). Consulte la página 4 de altera.com/ja_JP/pdfs/literature/hb/nios2/n2sw_nii52007.pdf

    – Dmitri

    8 oct 2015 a las 15:37

  • @JeremyP “Solo si la máquina solo tiene una CPUEso no siempre es falso, pero es extremadamente engañoso. Debería decir: solo si la máquina no tiene múltiples unidades de procesamiento que no están destinadas a soportes de subprocesos. Si la CPU está diseñada para admitir subprocesos, está garantizado.

    – chico curioso

    13 de noviembre de 2019 a las 3:10

avatar de usuario
scott roepnack

usar la palabra clave _Uncached puede ayudar en el sistema operativo integrado, como MQX

#define MEM_READ(addr)       (*((volatile _Uncached unsigned int *)(addr)))
#define MEM_WRITE(addr,data) (*((volatile _Uncached unsigned int *)(addr)) = data)

  • El botón de código está ahí por una razón. Por favor, no abuse del formato.

    – A–C

    17 de diciembre de 2012 a las 22:19

  • ¿Qué compilador admite el _Uncached ¿palabra clave? Buscar en Google “_Uncached” da su respuesta como primer resultado.

    –Manuel Jacob

    24 mayo 2016 a las 20:16

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad