Pourquoi Read-Modify-Write est-il nécessaire pour les registres sur les systèmes embarqués?

Je lisais http://embeddedgurus.com/embedded-bridge/2010/03/different-bit-types-in-different-registers/ , qui disait:

Avec les bits de lecture / écriture, le micrologiciel définit et efface les bits si nécessaire. Généralement, il lit d’abord le registre, modifie le bit souhaité, puis réécrit la valeur modifiée.

et je me suis heurté à ce sous-produit tout en conservant un code de production codé par de vieux gars intégrant du sel ici. Je ne comprends pas pourquoi c’est nécessaire.

Quand je veux définir / effacer un peu, je toujours juste ou / nand avec un masque de bit. À mon sens, cela résout tous les problèmes de threadsafe, car je suppose que la configuration (par affectation ou oring avec un masque) un registre ne prend qu’un cycle. Par ailleurs, si vous lisez d’abord le registre, puis modifiez puis écrivez, une interruption entre la lecture et l’écriture peut entraîner l’écriture d’une ancienne valeur dans le registre.

Alors pourquoi lire-modifier-écrire? Est-ce encore nécessaire?

Cela dépend un peu de l’architecture de votre périphérique intégré. Je vais donner trois exemples qui couvrent les cas courants. Cependant, l’essentiel est que le cœur de la CPU ne peut pas fonctionner directement sur les registres des périphériques d’E / S, sauf pour les lire et les écrire sous forme d’octets ou même de mots.

1) Série 68HC08, un microcontrôleur autonome à 8 bits.

Ceci inclut un “jeu de bits” et une instruction “bit clair”. Ceux-ci, si vous lisez attentivement le manuel, effectuent eux-mêmes en interne un cycle de lecture-modification-écriture. Elles ont l’avantage d’être des opérations atomiques, car elles ne peuvent être interrompues en tant qu’instructions simples.

Vous remarquerez également qu’elles prennent plus de temps que des instructions individuelles de lecture ou d’écriture, mais moins de temps que l’utilisation de trois instructions pour le travail (voir ci-dessous).

2) les processeurs RISC 32 bits classiques ARM ou PowerPC (que l’on trouve souvent dans les microcontrôleurs haut de gamme également).

Celles-ci n’incluent aucune instruction pouvant à la fois accéder à la mémoire et effectuer un calcul (le et / ou) à la fois. Si vous écrivez en C:

*register |= 0x40;

il se transforme en l’assemblage suivant (pour cet exemple de PowerPC, r8 contient l’adresse du registre):

 LBZ r4,r8 ORI r4,r4,#0x40 STB r4,r8 

Comme il s’agit d’instructions multiples, ce n’est PAS atomique et cela peut être interrompu. Le rendre atomique ou même sûr pour SMP dépasse le cadre de cette réponse – il existe des instructions et des techniques spéciales.

3) IA32 (x86) et AMD64. Je ne comprends pas pourquoi vous les utiliseriez comme “incorporés”, mais ils sont à mi-chemin entre les deux autres exemples.

J’oublie s’il existe un jeu de bits en mémoire à une seule instruction et un nettoyage de bits sur x86. Sinon, voir la section RISC ci-dessus, il ne prend que deux instructions au lieu de trois car x86 peut charger et modifier une seule instruction.

En supposant qu’il existe de telles instructions, ils doivent également charger et stocker le registre en interne, ainsi que le modifier. Les versions modernes vont explicitement diviser l’instruction en trois opérations de type RISC en interne.

La particularité est que x86 (contrairement au HC08) peut être interrompu sur le bus mémoire en cours de transaction par un maître du bus, et pas uniquement par une interruption de processeur classique. Ainsi, vous pouvez append manuellement un préfixe LOCK à une instruction devant effectuer plusieurs cycles de mémoire, comme dans ce cas. Vous n’obtiendrez pas cela du simple C cependant.

Le fait est que si vous ne voulez pas modifier les autres bits du registre, vous devez savoir ce qu’ils sont avant de l’écrire. D’où le read / modiy / write. Notez que si vous utilisez une instruction C comme:

 *pRegister |= SOME_BIT; 

Bien que cela puisse ressembler à une simple opération d’écriture, le compilateur doit d’abord effectuer une lecture afin de conserver les autres bits de la valeur (ceci est généralement vrai, même si vous ne parlez pas de registres matériels, à moins que le paramètre le compilateur est capable d’utiliser d’autres connaissances sur la valeur pour optimiser la lecture).

Notez que les registres matériels mappés en mémoire sont généralement marqués comme volatile sorte que ces optimisations ne peuvent pas avoir lieu (sinon, de nombreuses routines de registres matériels ne fonctionneraient pas correctement).

Enfin, il existe parfois une prise en charge matérielle pour les registres qui définissent ou effacent spécifiquement des bits dans le matériel sans nécessiter une séquence de lecture / modification / écriture. Certains microcontrôleurs Atmel ARM avec lesquels j’ai travaillé ont des registres spécifiques qui effacent ou définissent dans le matériel uniquement les bits définis lorsque vous écrivez dans le registre (en laissant tout bit non défini unique). La CPU ARM Cortex M3 prend également en charge l’access à un seul bit (pour la lecture ou l’écriture) en mémoire ou dans des registres matériels en accédant à un espace adresse spécifique avec une technique appelée «bande de bits» . L’algorithme de bande de bits semble complexe à première vue, mais c’est vraiment une simple arithmétique qui permet de mapper le décalage d’un bit d’une adresse à une autre adresse «spécifique à un bit».

Quoi qu’il en soit, il existe certains processeurs où vous pouvez vous échapper sans une série de lectures / modifications / écritures, mais ce n’est en aucun cas une vérité universelle.

Si vous devez modifier un sous-ensemble de bits dans un mot et que l’architecture prend uniquement en charge la lecture / écriture au niveau mot, vous devez lire les bits qui ne doivent pas changer pour savoir ce qu’il faut écrire pour qu’ils ne soient pas modifiés.

Certaines architectures prennent en charge l’access mémoire au niveau des bits, globalement ou pour des régions spécifiques de la mémoire. Mais même dans ce cas, lors de la modification de plusieurs bits, lire / modifier / écrire plusieurs entraîne moins d’instructions. Dans les systèmes multi-threadés, il faut veiller à ce que deux threads ne puissent pas exécuter cette action non atomique sur le même mot simultanément.

Les processeurs modernes peuvent définir ou effacer des bits avec une seule instruction. Cependant, ces instructions ne peuvent pas être définies et effacées en même temps. Il y a des cas où certains des bits d’un port d’E / S doivent tous changer ensemble et ne pas affecter les autres bits. Tant que la séquence lecture-modification-écriture ne peut pas être interrompue, il n’y a pas de problème.

La situation dans laquelle le problème peut devenir un problème nécessite trois conditions.

  1. La variable doit être accessible de manière globale, telle qu’un port d’E / S, un registre de fonctions spéciales ou une variable définie globalement.

  2. La variable globale peut être modifiée dans une fonction pouvant être préemptée.

  3. La même variable globale est modifiée lors du traitement d’une préemption.

Le seul moyen de résoudre les modifications de plusieurs bits à l’aide d’une séquence non atomique rmw consiste à protéger la séquence d’instructions en désactivant l’interruption pour la routine de service d’interruption pouvant également modifier la variable ou le registre. Ceci est similaire à l’access exclusif digine à des ressources telles que les ports LCD ou série.

Quand je veux définir / effacer un peu, je toujours juste ou / nand avec un masque de bit.

Pour certains registres, cela suffit. Dans un tel cas, le microprogramme de la CPU continuera à effectuer une lecture-modification-écriture.

À mon sens, cela résout tous les problèmes de threadsafe, car je suppose que la configuration (par affectation ou oring avec un masque) un registre ne prend qu’un cycle.

Si vous laissez le microprogramme de la CPU effectuer la lecture-modification-écriture pour vous, cela inclura évidemment au moins un cycle de lecture et un cycle d’écriture. Maintenant, la plupart des processeurs n’interrompent pas cette instruction au milieu. Votre thread exécutera donc cette instruction dans son intégralité avant que le processeur de votre thread ne vérifie les interruptions. Toutefois, si vous n’avez pas verrouillé le bus, les autres processeurs peuvent modifier le même registre. Votre fil et les autres threads peuvent toujours se chevaucher.