web-dev-qa-db-fra.com

Variables volatiles C et mémoire cache

Le cache est contrôlé par le matériel du cache de manière transparente pour le processeur, donc si nous utilisons des variables volatiles dans le programme C, comment est-il garanti que mon programme lit les données à chaque fois à partir de l'adresse mémoire spécifiée mais pas du cache.

Ma compréhension est que,

  1. Le mot clé volatile indique au compilateur que les références de variable ne doivent pas être optimisées et doivent être lues comme programmées dans le code.

  2. Le cache est contrôlé par le matériel du cache de manière transparente, donc lorsque le processeur émet une adresse, il ne sait pas si les données proviennent du cache ou de la mémoire.

Donc, si j'ai besoin de lire une adresse mémoire à chaque fois que je le souhaite, comment puis-je m'assurer qu'elle ne provient pas du cache mais de l'adresse requise?

Quelque part, ces deux concepts ne s'accordent pas bien. Veuillez clarifier comment cela se fait.

(Imaginer que nous avons une politique de réécriture dans le cache (si nécessaire pour analyser le problème))

Merci, Microkernel :)

34
Microkernel

Développeur de firmware ici. Il s'agit d'un problème standard dans la programmation intégrée, et qui déclenche de nombreux développeurs (même très expérimentés).

Mon hypothèse est que vous tentez d'accéder à un registre matériel et que la valeur du registre peut changer au fil du temps (que ce soit l'état d'interruption, la minuterie, les indications GPIO, etc.).

Le mot clé volatile n'est qu'une partie de la solution et, dans de nombreux cas, peut ne pas être nécessaire. Cela entraîne la relecture de la variable depuis mémoire chaque fois qu'elle est utilisée (au lieu d'être optimisée par le compilateur ou stockée dans un registre de processeur pour plusieurs utilisations), mais si la "memory" la lecture est un registre matériel réel par rapport à un emplacement mis en cache inconnu de votre code et non affecté par le mot clé volatile. Si votre fonction ne lit le registre qu'une seule fois, vous pouvez probablement laisser de côté volatile, mais en règle générale, je proposerai que la plupart des registres matériels soient définis comme volatile.

Le plus gros problème est la mise en cache et la cohérence du cache. L'approche la plus simple consiste à vous assurer que votre registre se trouve dans l'espace d'adressage non mis en cache. Cela signifie que chaque fois que vous accédez au registre, vous êtes assuré de lire/écrire le registre matériel réel et non la mémoire cache. Une approche plus complexe mais potentiellement plus performante consiste à utiliser l'espace d'adressage mis en cache et à forcer manuellement votre code à mettre à jour le cache pour des situations spécifiques comme celle-ci. Pour les deux approches, la façon dont cela est accompli dépend de l'architecture et dépasse la portée de la question. Cela peut impliquer des MTRR (pour x86), des MMU, des modifications de table de page, etc.

J'espère que cela pourra aider. Si j'ai raté quelque chose, faites-le moi savoir et je développerai ma réponse.

32
Andrew Cottrell

De votre question, il y a une idée fausse de votre part.
Le mot clé Volatile n'est pas lié au cache comme vous le décrivez.

Lorsque le mot clé volatile est spécifié pour une variable, il donne une indication au compilateur de ne pas effectuer certaines optimisations car cette variable peut changer d'autres parties du programme de manière inattendue.

Ce qui signifie ici, c'est que le compilateur ne doit pas réutiliser la valeur déjà chargé dans un registre, mais accéder à nouveau à la mémoire car la valeur dans le registre n'est pas garantie d'être la même que la valeur stockée en mémoire .

Le reste concernant la mémoire cache n'est pas directement lié au programmeur.

Je veux dire la synchronisation de toute mémoire cache du CPU avec le RAM est un sujet entièrement différent.

7
Cratylus

Ma suggestion est de marquer la page comme non mise en cache par le gestionnaire de mémoire virtuelle.
Sous Windows, cela se fait en définissant PAGE_NOCACHE lors de l'appel VirtualProtect .

Dans un but quelque peu différent, les instructions SSE 2 ont le _mm_stream_xyz des instructions pour éviter la pollution du cache, bien que je ne pense pas qu'elles s'appliquent à votre cas ici.

Dans les deux cas, il n'y a pas portable moyen de faire ce que vous voulez en C; vous devez utiliser la fonctionnalité du système d'exploitation.

7
user541686

Wikipedia a un assez bon article sur MTRR (Memory Type Range Registers) qui s'appliquent à la famille de processeurs x86.

Pour résumer, en commençant par le Pentium Pro, Intel (et AMD copié) avait ces registres MTR qui pouvaient définir des attributs non mis en cache, en écriture, en combinaison d'écriture, en protection en écriture ou en écriture sur des plages de mémoire.

À partir du Pentium III, mais pour autant que je sache, vraiment utile uniquement avec les processeurs 64 bits, ils honorent les MTRR mais ils peuvent être remplacés par les tableaux d'attributs de page qui permettent au CPU de définir un type de mémoire pour chaque page de mémoire.

Une utilisation majeure des MTRR que je connais est la RAM graphique. Il est beaucoup plus efficace de le marquer comme combinaison d'écriture. Cela permet au cache de stocker les écritures et assouplit toutes les règles d'ordre d'écriture en mémoire pour permettre des écritures en rafale à très haute vitesse sur une carte graphique.

Mais pour vos besoins, vous voudriez un paramètre MTRR ou PAT non mis en cache ou en écriture.

2
Zan Lynx

l'utilisation du mot clé _Uncached peut aider dans le système d'exploitation intégré, comme MQX

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

volatile s'assure que les données sont lues à chaque fois qu'elles sont nécessaires sans se soucier du cache entre le CPU et la mémoire. Mais si vous devez lire les données réelles de la mémoire et non les données mises en cache, vous avez deux options:

  • Créez un tableau où lesdites données ne sont pas mises en cache. Cela peut déjà être le cas si vous adressez un périphérique d'E/S,
  • Utilisez des instructions CPU spécifiques qui contournent le cache. Ceci est utilisé lorsque vous devez nettoyer la mémoire pour activer d'éventuelles erreurs SEU.

Les détails de la deuxième option dépendent du système d'exploitation et/ou du processeur.

0
mouviciel

Comme vous le dites, le cache est transparent pour le programmeur. Le système garantit que vous voyez toujours la dernière valeur sur laquelle vous avez écrit si vous accédez à un objet via son adresse. La "seule" chose que vous pourriez subir si une valeur obsolète se trouve dans votre cache est une pénalité d'exécution.

0
Jens Gustedt