Atomicité de l’incrémentation d’un mot processeur (ex : int pour x86)

Bonjour,

Un collègue implémente une mesure de charge système sur un uC ARM7 comme suivant :

Dans la tâche IDLE de plus faible priorité :
while(true) ++cpt ;

Dans une IT qui claque toute les secondes :
val = cpt
cpt = 0 ;
conditionnement(cpt) : fournit une valeur de charge CPU [0-100] %

A l’exécution, quelque soit le niveau d’optimisation du compilateur, il lui arrivait d’avoir des valeurs incohérentes de charge ! Curieux car il divise la valeur val par une valeur de référence obtenue avant le lancement des tâches, donc le résultat est forcément inférieure à 1 .

Je regarde le code ASM généré (dieu merci facile à comprendre) :

Tâche IDLE incrémente une variable avec l’opération ‘i++’ :
R0 <- RAM (en plusieurs instructions mais résumé ainsi)
R0 + 1
R0 -> RAM

La Routine IT réinitalise la variable (i=0) :
R0 <- 0
R1 <- @
R0 -> [R1]

Si l’incrémentation par la tâche est interrompue par la routine, la réinitialisation est manquée.
Conclusion : on ne peut être déterministe sur des accès concurrentiels en écriture, et cela concorde avec la norme ANSI C qui n’oblige pas l’atomicité de l’opération ‘++’.
Cela ne signifie pas qu’il faut systématiquement synchroniser des accès sur un mot processeur. Dans certains cas cela n’aurait pas de sens : si par exemple vous utilisez un flag pour contrôler l’exécution d’une boucle, synchroniser ses accès avec un sémaphore n’aurait pas de sens.
Une solution au problème énoncé ici serait de déporter l’écriture dans la tâche IDLE, via l’écriture de deux boucles imbriquées : la boucle d’incrémentation est contrôlée par un flag de type booléen.

Le comportement est le même sur x86 : selon ce billet c’est effectivement le cas avec GCC. Au passage l’auteur du billet est l’auteur du livre « programmation système en C sous Linux ».

Mais on crée la confusion avec l’amalgame entre ce que peut réaliser le processeur, et la norme.

En réalité des solutions existent pour garantir l’atomicité de l’opération, mais elle dépendent du compilateur ou de l’artchitecture de la CPU.

Ce cours en assembleur 80386 explique comment implémenter l’addition atomique d’une constante à une variable en mémoire. En effet l’instruction assembleur « add » supporte l’opération avec un opérande  » @ mémoire » plutôt qu’un registre. Reste à savoir pourquoi GCC ne génère pas ce code pour du x86.

La documentation GNU fournit des fonctions avec le compilateur assurant des accès atomique.

Pour une CPU ARM des instructions dédiées peuvent être utilisées cette opération (SWP, LDEX/STREX). Mais comme l’article l’indique le compilateur ne générera jamais ce code, il faut l’implémenter manuellement.

À propos de Selso

Salut ! Je m'appelle Selso. Je vis à Saint-Etienne depuis 2005. Je suis ingénieur en informatique embarquée en poste chez CIO Systèmes Embarqués.
Cette entrée a été publiée dans Non classé. Vous pouvez la mettre en favoris avec ce permalien.

Laisser un commentaire