Samedi 18 novembre 2006
6
18
/11
/2006
14:39
Introduction
Souvent mal connu, mal utilisé voir ignoré des programmeurs, l'attribut " volatile " en langage C/C++. Ce n'est pas une surprise, car les ouvrages de programmation et / ou de référence décrivent cet attribut en une ou deux phrases sans vraiment décrire le pourquoi du comment.
Tous les programmeurs d'applications embarquées en langage C / C++ ont déjà eu une expérience plus ou moins malheureuse du type :
- L'application fonctionne parfaitement quand l'optimisation du compilateur est validée.
- L'application fonctionne parfaitement tant que les interruptions sont dé-validées.
- Dans une application multitâches, les tâches fonctionnent bien séparément et l'application se "plante" dès qu'une nouvelle tâche est activée. Si ces mésaventures arrivent cela est dû à la non utilisation de l'attribut volatile.
Volatile est un attribut appliqué à une variable lors de sa déclaration : cela indique au compilateur que le contenu de la variable est susceptible de changer à n'importe quel moment, de manière asynchrone, et force ainsi le compilateur à ne pas réaliser d'optimisation indésirables sur tout le code qui touche de près ou de loin aux variables déclarées avec l'attribut volatile.
Syntaxe
Pour déclarer une variable de type volatile, il faut introduire le mot clé avant ou après le type dans la définition de la variable. Les deux exemples suivant déclarent les variables Var1 et Var2 en int volatile :
volatile int Var1;
int volatile Var2;
Déclaration de pointeurs sur des variables volatiles :
volatile int * Var1;
int volatile * Var2;
Plus rare, la déclaration d'un pointeur volatile sur une variable non volatile : int * volatile Var2;
Déclaration d'un pointeur volatile sur une variable volatile : int volatile * volatile Var2;
Il est préférable de placer l'attribut volatile après le type de la variable, comme dans les deux derniers exemples ; ce n'est pas une règle, c'est avant tout une question de goût.
Pour finir avec la syntaxe, si l'on applique l'attribut volatile à une structure "struct" ou une union, l'intégralité de la structure / union est volatile. Il est bien sur possible de qualifier de volatile chaque membre de la structure ou de l'union, indépendamment des autres.
Utilisation
Une variable doit être déclarée volatile quand son contenu est susceptible de changer de façon aléatoire par rapport au déroulement général du programme. En pratique, il y a seulement trois type de variables qui entrent dans ce cadre :
- Les registres des interfaces de périphériques ( projetés en mémoire ).
- Les variables globales modifiées par une routine d'interruption.
- Les variables globales dans une applications multitâches. Ce type de variables, si elles ne sont déclarées avec l'attribut volatile, vont générées un programme qui ne sera pas fonctionnel : le compilateur vas mal interpréter les actions à réaliser, voir supprimer du code, qui pourtant est nécessaire.
On peut faire comme certains qui déclarent toutes les variables en volatile ou indiquent au compilateur de traiter toutes les variables comme telles : bien que générant un code fonctionnel, cette méthode conduit à un code non-optimisé en terme de place et de performances
Registres des interfaces de périphériques
Les applications embarquées communiquent avec le mode extérieur à travers des périphériques sophistiqués. Les interfaces de périphériques contiennent des registres dont le contenu change de manière asynchrone par rapport au déroulement du programme.
La plupart des compilateurs disposent de l'attribut " @ " qui permet de déclarer une adresse mémoire spécifique ou un registre projeté en mémoire : la déclaration prends la forme : @0x8000;
Bien que fonctionnelle cette déclaration n'est pas conforme au standard C ANSI et n'est pas forcément portable : mieux vaut utiliser une déclaration standard sous forme de pseudo-variables.
Base de travail : ( unsigned char *) 0x8000
Cette déclaration pointe le contenu d'un registre 8 bits à l'adresse 0x8000, mais il n'est pas possible d'écrire ou de lire une donnée ; il faut pour cela dé-référencer le pointeur ( spécifier que la valeur pointée n'est pas le pointeur lui-même ) : * ( unsigned char * ) 0x8000.
La forme définitive et développée, avec l'attribut volatile, prends la forme :
#define PORT_PERIF ( * ( volatile unsigned char * ) ( 0x8000 + OFFSET_REG ) )
Routine d'interruption
Les routines d'interruptions utilisent souvent des variables testées dans le programme principal. Par exemple, l'interruption d'un port série en réception : le programme d'interruption stocke et test les caractères reçus, signalant la fin du message par une variable globale au programme principal :
int End_Message = FALSE;
void main ( void )
{
....
while (! End_Message)
#define RX_CHAR ( * ( volatile unsigned char * ) ( 0x8000 + 0 ) )
interrupt void Isr_Receive ( void )
{
.....
if ( RX_CHAR == ETX ) { End_Message = TRUE;}
.....
.....
}
Si l'optimisation du compilateur est dé-validée, ce programme fonctionne correctement. Par contre n'importe quel compilateur "décent" avec l'optimisation validée, va supprimer le code après la boucle while dans le main(), la condition ( ! End_Message) étant toujours vraie pour lui : en effet le compilateur n'a pas idée que la variable End_Message est modifiée par la routine d'interruption !
Dans ce cas là et pour que tout rentre dans l'ordre il suffit de déclarer la variable End_Message avec l'attribut volatile.
Applications multitâches
Dans les applications multitâches, les tâches communiquent entre elles par mécanismes ( pipes, data box, etc ... ) en utilisant un accès à un emplacement mémoire partagé ( idem variable globale ). La gestion est confié à un gestionnaire de tâches ( scheduler ), dont le fonctionnement dynamique et asynchrone, ne laisse aucune indication au compilateur lors de la génération du code. En d'autres termes, une tâche modifiant une variable globale partagée par une autre tâche ou une autre portion du programme, et on se retrouve dans la même problématique que les interruptions.
On peut donc en tirer une règle de base pour les application multitâches :
Toutes les variables globales partagées doivent être déclarées avec l'attribut volatile.
A lire aussi : optimisation des systèmes embarqués [ épisode 1 ] [ épisode 2 ] [ épisode 3 ] [ épisode 4 ]
Souvent mal connu, mal utilisé voir ignoré des programmeurs, l'attribut " volatile " en langage C/C++. Ce n'est pas une surprise, car les ouvrages de programmation et / ou de référence décrivent cet attribut en une ou deux phrases sans vraiment décrire le pourquoi du comment.
Tous les programmeurs d'applications embarquées en langage C / C++ ont déjà eu une expérience plus ou moins malheureuse du type :
- L'application fonctionne parfaitement quand l'optimisation du compilateur est validée.
- L'application fonctionne parfaitement tant que les interruptions sont dé-validées.
- Dans une application multitâches, les tâches fonctionnent bien séparément et l'application se "plante" dès qu'une nouvelle tâche est activée. Si ces mésaventures arrivent cela est dû à la non utilisation de l'attribut volatile.
Volatile est un attribut appliqué à une variable lors de sa déclaration : cela indique au compilateur que le contenu de la variable est susceptible de changer à n'importe quel moment, de manière asynchrone, et force ainsi le compilateur à ne pas réaliser d'optimisation indésirables sur tout le code qui touche de près ou de loin aux variables déclarées avec l'attribut volatile.
Syntaxe
Pour déclarer une variable de type volatile, il faut introduire le mot clé avant ou après le type dans la définition de la variable. Les deux exemples suivant déclarent les variables Var1 et Var2 en int volatile :
volatile int Var1;
int volatile Var2;
Déclaration de pointeurs sur des variables volatiles :
volatile int * Var1;
int volatile * Var2;
Plus rare, la déclaration d'un pointeur volatile sur une variable non volatile : int * volatile Var2;
Déclaration d'un pointeur volatile sur une variable volatile : int volatile * volatile Var2;
Il est préférable de placer l'attribut volatile après le type de la variable, comme dans les deux derniers exemples ; ce n'est pas une règle, c'est avant tout une question de goût.
Pour finir avec la syntaxe, si l'on applique l'attribut volatile à une structure "struct" ou une union, l'intégralité de la structure / union est volatile. Il est bien sur possible de qualifier de volatile chaque membre de la structure ou de l'union, indépendamment des autres.
Utilisation
Une variable doit être déclarée volatile quand son contenu est susceptible de changer de façon aléatoire par rapport au déroulement général du programme. En pratique, il y a seulement trois type de variables qui entrent dans ce cadre :
- Les registres des interfaces de périphériques ( projetés en mémoire ).
- Les variables globales modifiées par une routine d'interruption.
- Les variables globales dans une applications multitâches. Ce type de variables, si elles ne sont déclarées avec l'attribut volatile, vont générées un programme qui ne sera pas fonctionnel : le compilateur vas mal interpréter les actions à réaliser, voir supprimer du code, qui pourtant est nécessaire.
On peut faire comme certains qui déclarent toutes les variables en volatile ou indiquent au compilateur de traiter toutes les variables comme telles : bien que générant un code fonctionnel, cette méthode conduit à un code non-optimisé en terme de place et de performances
Registres des interfaces de périphériques
Les applications embarquées communiquent avec le mode extérieur à travers des périphériques sophistiqués. Les interfaces de périphériques contiennent des registres dont le contenu change de manière asynchrone par rapport au déroulement du programme.
La plupart des compilateurs disposent de l'attribut " @ " qui permet de déclarer une adresse mémoire spécifique ou un registre projeté en mémoire : la déclaration prends la forme : @0x8000;
Bien que fonctionnelle cette déclaration n'est pas conforme au standard C ANSI et n'est pas forcément portable : mieux vaut utiliser une déclaration standard sous forme de pseudo-variables.
Base de travail : ( unsigned char *) 0x8000
Cette déclaration pointe le contenu d'un registre 8 bits à l'adresse 0x8000, mais il n'est pas possible d'écrire ou de lire une donnée ; il faut pour cela dé-référencer le pointeur ( spécifier que la valeur pointée n'est pas le pointeur lui-même ) : * ( unsigned char * ) 0x8000.
La forme définitive et développée, avec l'attribut volatile, prends la forme :
#define PORT_PERIF ( * ( volatile unsigned char * ) ( 0x8000 + OFFSET_REG ) )
Routine d'interruption
Les routines d'interruptions utilisent souvent des variables testées dans le programme principal. Par exemple, l'interruption d'un port série en réception : le programme d'interruption stocke et test les caractères reçus, signalant la fin du message par une variable globale au programme principal :
int End_Message = FALSE;
void main ( void )
{
....
while (! End_Message)
#define RX_CHAR ( * ( volatile unsigned char * ) ( 0x8000 + 0 ) )
interrupt void Isr_Receive ( void )
{
.....
if ( RX_CHAR == ETX ) { End_Message = TRUE;}
.....
.....
}
Si l'optimisation du compilateur est dé-validée, ce programme fonctionne correctement. Par contre n'importe quel compilateur "décent" avec l'optimisation validée, va supprimer le code après la boucle while dans le main(), la condition ( ! End_Message) étant toujours vraie pour lui : en effet le compilateur n'a pas idée que la variable End_Message est modifiée par la routine d'interruption !
Dans ce cas là et pour que tout rentre dans l'ordre il suffit de déclarer la variable End_Message avec l'attribut volatile.
Applications multitâches
Dans les applications multitâches, les tâches communiquent entre elles par mécanismes ( pipes, data box, etc ... ) en utilisant un accès à un emplacement mémoire partagé ( idem variable globale ). La gestion est confié à un gestionnaire de tâches ( scheduler ), dont le fonctionnement dynamique et asynchrone, ne laisse aucune indication au compilateur lors de la génération du code. En d'autres termes, une tâche modifiant une variable globale partagée par une autre tâche ou une autre portion du programme, et on se retrouve dans la même problématique que les interruptions.
On peut donc en tirer une règle de base pour les application multitâches :
Toutes les variables globales partagées doivent être déclarées avec l'attribut volatile.
A lire aussi : optimisation des systèmes embarqués [ épisode 1 ] [ épisode 2 ] [ épisode 3 ] [ épisode 4 ]
Par BigEndian
-
Publié dans : Programmation
Ecrire un commentaire - Voir les commentaires - Recommander
Ecrire un commentaire - Voir les commentaires - Recommander
