Volatile et sig_atomic_t

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 21/08/2001 - Dernière mise à jour : 24/09/2001

Sommaire

Introduction
Qualifieur volatile du C/C++
Type sig_atomic_t du C/C++
Atomicité et volatilité en Ada

Introduction

    Ces deux notions sont importantes dans le cadre du développement d'applications pouvant interagir soit avec d'autres processus fonctionnant sur la même  machine ou sur des machines différentes, soit avec des mécanismes hardware. L'interaction envisagée est ici l'accès (lecture/modification) concurrent à certaines variables. Sont donc exclus ici les variables partagées classiquement au sein d'un même processus (tâches en Ada ou threads en C/C++), qui restent dans le même espace de mémoire virtuelle, ou les données implémentées en mémoire partagée, problème traité par ailleurs. En revanche, les variables concernées sont celles qui peuvent être modifiées par le système d'exploitation, un dispositif matériel, etc., à l'insu de l'application.

Qualifieur volatile

    Le programme ci-dessous (écrit en C) est extrêmement simple : la fonction main() tourne dans une boucle jusqu'à ce que le booléen Continuer, initialisé à vrai dès le démarrage, devienne faux. C'est à réception du signal SIGINT qu'il change de valeur. En d'autres termes, le signal SIGINT doit faire sortir le programme de la boucle.
 
bool Continuer = true;
void Derout (int NumSig) { Continuer = false; }

int main (int argc, char * argv [])

    signal (SIGINT, Derout);

    for (; Continuer; );

    cout << "Fini" << endl;

    return 0;

} // main() 

    Compilé simplement par le compilateur g++ (et sans doute la plupart des compilateurs), ce programme  semble fonctionner comme prévu. On dirait "qu'il est juste"... Cependant, si on ajoute l'option -O, qui demande une compilation optimisée, le programme ne réagit plus au signal SIGINT. La raison en est très simple : la variable Continuer est une variable globale, donc stockée dans la zone statique de la mémoire virtuelle réservée aux variables globales initialisées. Le compilateur s'aperçoit que cette variable Continuer est fréquemment utilisée dans une boucle, sans être modifiée, et considère qu'il est plus rapide d'en faire une copie stockée dans un registre du processeur, d'accès beaucoup plus rapide que la mémoire principale.

    Dès lors, le traitant du signal et la fonction main() n'utilisent plus le même objet physique et le programme est faux.

    Pour éviter cet inconvénient, le langage C a prévu le qualifieur volatile qui interdit au compilateur de dupliquer une variable lors d'une éventuelle optimisation de la compilation. Il garantit ainsi l'unicité de la variable. La première ligne de code doit donc être réécrite :
 
volatile bool Continuer = true;

    Ce qualifieur doit être utilisé pour toute variable dont la valeur risque d'être modifiée par quelque dispositif que ce soit externe au programme : système d'exploitation, traitant d'interruption ou de signal, etc.

Type sig_atomic_t

    Ce type a été introduit dans la norme ISO/ANSI du langage C de 1989. Il est défini dans le fichier signal.h comme :
 
"le type entier d'un objet qui peut être accédé comme une entité atomique, même en présence d'interruptions asynchrones."

    La norme ajoute :
 
"Il est difficile de spécifier les signaux indépendamment du système d'exploitation. Le comité de normalisation a conclu que, pour être strictement conforme à la norme, la seule chose que peut faire un programme dans un traitant de signal est d'affecter une valeur à une variable volatile static qui peut être écrite de façon atomique (c-à-d. non interruptible) et de retourner aussitôt."

    Seul le type sig_atomic_t garantit l'atomicité de l'accès ŕ l'objet. Par accès, il faut seulement entendre le transfert mémoire <--> processeur, et non une opération plus complexe comme une incrémentation par exemple. Il est généralement défini comme équivalent au type de base int.

    En d'autres termes, lorsque des informations doivent être échangées entre le programme et le traitant de signal, comme le fait la stratégie E présentée dans le TP sur les signaux, seule l'utilisation du type sig_atomic_t assure une portabilité générale. Le programme précédent doit donc être réécrit :
 
volatile sig_atomic_t Continuer = 1;

void Derout (int NumSig) { Continuer = 0; }

int main (int argc, char * argv [])

    signal (SIGINT, Derout);

    for (; Continuer; );

    cout << "Fini" << endl;

    return 0;

} // main() 

Atomicité et volatilité en Ada

    L'annexe C.6 du manuel de référence Ada95 : "Shared Variable Control" est consacré à la résolution de ces problèmes. Quatre pragmas sont proposés, qui ont exactement le même effet que les mots réservés volatile et sigatomic_t du C/C++ :
 
pragma Atomic (local_name);
pragma Atomic_Components (array_local_name);
pragma Volatile (local_name);
pragma Volatile_Components (array_local_name);

    Il faut remarquer que, contrairement à C/C++, Ada permet de rendre atomique l'accès à un objet non élémentaire. En fait, alors que le C/C++ met à disposition seulement un mécanisme matériel, donc très simple, le pragma Ada doit mettre en place un dispositif logiciel, donc beaucoup plus lourd.

    Le manuel de référence indique aussi, qu'une constante dans un programme Ada peut cependant être modifiée par un dispositif externe.

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique