Voici les types de données sur les microcontrôleurs STM32: http://www.keil.com/support/man/docs/armcc/armcc_chr1359125009502.htm .
Ces microcontrôleurs utilisent des processeurs de base 32 bits ARM.
Quels types de données ont un accès automatique en lecture atomique et en écriture atomique?
Je suis presque sûr que tous les types de données 32 bits le font (puisque le processeur est 32 bits), et que tous les types de données 64 bits ne le font PAS (car il faudrait au moins 2 opérations de processeur pour lire ou écrire un mot 64 bits ), mais qu'en est-il de bool
(1 octet) et uint16_t
/int16_t
(2 octets)?
Contexte: je partage des variables entre plusieurs threads (un seul noyau, mais plusieurs threads, ou "tâches" comme on les appelle, dans FreeRTOS ) sur le STM32 et j'ai besoin de savoir si j'ai besoin d'appliquer atomic accès en désactivant les interruptions, en utilisant des mutex, etc.
METTRE À JOUR:
En se référant à cet exemple de code:
volatile bool shared_bool;
volatile uint8_t shared u8;
volatile uint16_t shared_u16;
volatile uint32_t shared_u32;
volatile uint64_t shared_u64;
volatile float shared_f; // 32-bits
volatile double shared_d; // 64-bits
// Task (thread) 1
while (1)
{
// Write to the values in this thread.
// What I write to each variable will vary. Since other threads
// are reading these values, I need to ensure my *writes* are atomic, or else
// I must use a mutex to prevent another thread from reading a variable in the middle
// of this thread's writing.
shared_bool = true;
shared_u8 = 129;
shared_u16 = 10108;
shared_u32 = 130890;
shared_f = 1083.108;
shared_d = 382.10830;
}
// Task (thread) 2
while (1)
{
// Read from the values in this thread.
// What thread 1 writes into these values can change at any time, so I need to ensure
// my *reads* are atomic, or else I'll need to use a mutex to prevent the other
// thread from writing to a variable in the midst of reading
// it in this thread.
if (shared_bool == whatever)
{
// do something
}
if (shared_u8 == whatever)
{
// do something
}
if (shared_u16 == whatever)
{
// do something
}
if (shared_u32 == whatever)
{
// do something
}
if (shared_u64 == whatever)
{
// do something
}
if (shared_f == whatever)
{
// do something
}
if (shared_d == whatever)
{
// do something
}
}
Dans le code ci-dessus, pour quelles variables puis-je le faire sans utiliser de mutex? Mes soupçons sont les suivants:
volatile bool
: sûr - aucun mutex requisvolatile uint8_t
: sûr - aucun mutex requisvolatile uint16_t
: sûr - aucun mutex requisvolatile uint32_t
: sûr - aucun mutex requisvolatile uint64_t
: NON SÉCURITAIRE - VOUS DEVEZ UTILISER UNE section critique ou MUTEX!volatile float
: sûr - aucun mutex requisvolatile double
: NON SÉCURITAIRE - VOUS DEVEZ UTILISER UNE section critique ou MUTEX!Exemple de section critique avec FreeRTOS:
- https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html
// Force atomic access with these critical section atomic access guards.
taskENTER_CRITICAL();
// do the (now guaranteed to be safe) read or write here
taskEXIT_CRITICAL();
Pour la réponse définitive et définitive à cette question, passez directement à la section ci-dessous intitulée " Réponse finale à ma question ".
MISE À JOUR 30 oct. 2018: Je faisais accidentellement référence aux documents (légèrement) erronés (mais qui disaient exactement la même chose), je les ai donc corrigés dans ma réponse ici. Voir "Notes sur les modifications du 30 octobre 2018" au bas de cette réponse pour plus de détails.
Je ne comprends certainement pas tous les mots ici, mais le Manuel de référence de l'architecture ARM v7-M ( Source en ligne ; - téléchargement direct du fichier PDF ) (PAS le manuel de référence technique [TRM], car il ne traite pas de l'atomicité) valide mes hypothèses:
Donc ... Je pense que mes 7 hypothèses au bas de ma question sont toutes correctes. [30 octobre 2018: Oui, c'est exact. Voir ci-dessous pour plus de détails.]
MISE À JOUR DU 29 octobre 2018:
Richard Barry, fondateur, expert et développeur principal de FreeRTOS, déclare dans tasks.c
...
/ * Une section critique n'est pas requise car les variables sont de type BaseType_t. * /
... lors de la lecture d'une variable volatile "non signé long" (4 octets) sur STM32. Cela signifie qu'il est au moins sûr à 100% que les lectures et les écritures à 4 octets sont atomiques sur STM32. Il ne mentionne pas les lectures à plus petits octets , mais pour les lectures sur 4 octets, il est absolument sûr. Je dois supposer que les variables de 4 octets étant la largeur du processeur natif, et aussi, aligné sur Word , est critique pour que cela soit vrai.
De tasks.c
, lignes 2173-2178 dans FreeRTOS v9.0.0, par exemple:
UBaseType_t uxTaskGetNumberOfTasks( void )
{
/* A critical section is not required because the variables are of type
BaseType_t. */
return uxCurrentNumberOfTasks;
}
Il utilise cette expression exacte de ...
/ * Une section critique n'est pas requise car les variables sont de type BaseType_t. * /
... à deux endroits différents dans ce fichier.
De plus, en examinant de plus près le TRM sur p141 comme indiqué dans ma capture d'écran ci-dessus, les phrases clés que je voudrais souligner sont:
Dans ARMv7-M, les accès au processeur atomique à copie unique sont:
• tous les accès octets .
• tous les demi-mots accèdent aux emplacements alignés sur les demi-mots.
• tous Word accède aux emplacements alignés sur Word.
Et, par ce lien , ce qui suit est vrai pour les "types de données de base implémentés dans ARM C et C++" (c'est-à-dire sur STM32):
bool
/_Bool
est "aligné sur les octets" (aligné sur 1 octet) int8_t
/uint8_t
est "aligné sur les octets" (aligné sur 1 octet) int16_t
/uint16_t
est "aligné sur un demi-mot" (aligné sur 2 octets) int32_t
/uint32_t
est "aligné sur un mot" (aligné sur 4 octets) int64_t
/uint64_t
est "aligné sur deux mots" (aligné sur 8 octets) <- ATOMIQUE NON GARANTIfloat
est "aligné sur Word" (aligné sur 4 octets) double
est "aligné sur deux mots" (aligné sur 8 octets) <- ATOMIQUE NON GARANTIlong double
est "aligné sur deux mots" (aligné sur 8 octets) <- ATOMIQUE NON GARANTICela signifie que j'ai maintenant et comprends les preuves dont j'ai besoin pour déclarer de manière concluante que toutes les lignes en gras juste au-dessus ont un accès atomique automatique en lecture et en écriture (mais PAS incrémenter/décrément bien sûr, ce qui est de multiples opérations). Ceci est la réponse finale à ma question. La seule exception à cette atomicité pourrait être dans les structures emballées je pense, auquel cas celles-ci autrement -les types de données naturellement alignés peuvent ne pas être naturellement alignés.
Notez également que lors de la lecture du Manuel de référence technique, "atomicité à copie unique" signifie apparemment simplement "atomicité à processeur unique" ou "atomicité sur une architecture à processeur unique". Ceci est en contraste avec "l'atomicité multi-copie", qui se réfère à un "système de multi-traitement", ou une architecture multi-core-CPU. Wikipedia déclare que "le multi-traitement est l'utilisation de deux ou plusieurs unités centrales de traitement (CPU) au sein d'un même système informatique" ( https://en.wikipedia.org/wiki/Multiprocessing ).
Mon architecture en question, STM32F767ZI (avec ARM Cortex-M7 core), est une architecture monocœur, donc apparemment " atomicité en copie unique ", comme je l'ai cité ci-dessus du TRM, s'applique.
Selon ce que vous entendez par atomique.
Si ce n'est pas la simple opération de chargement ou de stockage comme
a += 1;
alors tous les types ne sont pas atomiques.
S'il s'agit d'une simple opération de stockage ou de chargement 32 bits, les types de données 16 bits et 8 bits sont atomiques. Si la valeur dans le registre doit être normalisée, la mémoire 8 et 16 bits et la charge peuvent ne pas être atomiques.
Si votre matériel prend en charge le bitbanding, si le bitbanding est utilisé, les opérations sur les bits (définir et réinitialiser) dans les zones de mémoire prenant en charge le bitbanding sont atomiques.
si votre code n'autorise pas les opérations non alignées, les opérations 8 et 16 bits peuvent ne pas être atomiques.
L '"arithmétique" atomique peut être traitée par les registres Core du CPU!
Il peut être de n'importe quel type n ou quatre octets dépendent de l'architecture et du jeu d'instructions
MAIS modification de n'importe quelle variable située dans la mémoire prenez au moins 3 étapes système: RMW = lecture de la mémoire à enregistrer, modification du registre et écriture du registre dans la mémoire.
Par conséquent, la modification atomique ne peut être possible que si vous contrôlez l'utilisation des registres CPU, cela signifie qu'il faut utiliser un assembleur pur et ne pas utiliser le compilateur C ou Cpp.
Lorsque vous utilisez le compilateur C\Cpp, il a placé une variable statique globale ou globale en mémoire afin C\Cpp ne fournit aucune action ni aucun type atomique
Remarque: vous pouvez utiliser par exemple des "registres FPU" pour la modification atomique (si vous en avez vraiment besoin), mais vous devez cacher au compilateur et RTOS cette architecture a FPU.