(Cet article est un extrait de la version préparatoire de mon livre « Applications temps-réel avec Linux » en cours d’écriture)
Lorsque l’on teste le fonctionnement d’un système temps-réel Posix, en particulier les priorités entre les différentes tâches, il est fréquent d’utiliser un petit programme comme celui-ci :
boucle-15s.c : #include <unistd.h>: int main(void) { alarm(15); while(1) ; }
Ce programme, exécuté avec un ordonnancement temps-réel va réaliser une boucle active qui va monopoliser le processeur. Au bout de 15 secondes, l’alarme programmée avant la boucle va se déclencher et le noyau va envoyer un signal SIGALRM au processus. Celui-ci ne traite pas le signal et va donc être tué (ce qui nous permet de reprendre le contrôle).
On peut tester ce programme sur un système unicoeur simplement ainsi :
# chrt -f 80 ./boucle-15s #
Et voir que le système est totalement figé pendant quinze secondes. Sur un système multicoeur ou multiprocesseur, je conseillerai de regrouper les commandes dans un script qui répartit les boucles sur les différents CPU. Par exemple, on peut utiliser le script suivant.
boucles-paralleles.sh : #! /bin/sh [...] # Le nombre de boucles paralleles est precise en argument NB=$(($1)) [...] # Le script tourne sur le CPU 0 taskset -pc 0 $$ # Il lance les boucles sur les autres CPU i=1 while [ $i -lt "$NB" ] do taskset -c "$i" ./boucle-15s & i=$((i+1)) done # Puis sur son CPU taskset -c 0 ./boucle-15s
Il faut naturellement le démarrer sous un ordonnancement temps-réel pour que les boucles actives s’approprient tout le temps CPU disponible. Toutefois, avant de réaliser cette opération, je conseille de tester le programme en l’exécutant avec l’ordonnancement temps-partagé usuel, et de visualiser à l’aide d’un utilitaire système l’activité des différents CPU. Personnellement j’aime beaucoup l’application Gkrellm (existant sous forme de packages précompilés pour la plupart des distributions Linux). Sur la capture d’écran suivante, par exemple, nous voyons que seuls trois coeurs exécutent des boucles, le quatrième étant relativement au repos :
# ./boucles-paralleles.sh 3 pid 21810's current affinity list: 0-3 pid 21810's new affinity list: 0
Le lancement des quatre boucles en temps-réel s’effectue ainsi :
# chrt -f 80 ./boucles-paralleles.sh 4
J’utilise notamment cette méthode lors de sessions de formations sur Linux temps-réel depuis de nombreuses années. Imaginez ma surprise, au printemps 2008 en lançant un tel programme, de voir les applications tournant en ordonnancement temps-partagé (comme Gkrellm lui-même) continuer à s’exécuter lors des boucles actives temps-réel.
Depuis la version 2.6.25 de Linux, sortie en avril 2008, une petite partie (5 %) du temps CPU disponible est en effet réservée aux tâches temps-partagé, même lorsque des tâches temps-réel s’exécutent !
Ce comportement – qui avait été introduit sans grande publicité dans le nouveau noyau – viole bien entendu les principes mêmes des systèmes temps-réel. Il est conçu comme une sorte de garde-fou qui protège l’usager inconscient contre les boucles infinies qu’il aurait malencontreusement lancées sous un ordonnancement temps-réel. Bien sûr cela peut s’avérer très utile lors de la mise au point d’un système temps-réel, ou pour son débogage. Toutefois j’estime que c’est une option qui ne devrait pas être active par défaut. N’oublions pas que l’exécution d’une tâche sous un ordonnancement temps-réel doit être demandée explicitement et que l’utilisateur doit disposer des privilèges root. On peut considérer qu’il est alors responsable de ses actes et qu’il peut activer volontairement le garde-fou s’il le désire.
Les noyaux Linux depuis le 2.6.25 (jusqu’au 2.6.38 du moins) adoptent l’attitude inverse, préférant, par défaut, pénaliser le comportement temps-réel plutôt que de risquer un déni de service par une boucle infinie.
Heureusement il est possible de débrayer ce garde-fou, en écrivant dans un pseudo-fichier de /proc
– même si l’on peut regretter qu’il n’y ait pas une option pour désactiver ce comportement par défaut lors de la compilation du noyau. Le fichier /proc/sys/kernel/sched_rt_period_us
représente une période, exprimée en micro-secondes que partagent les tâches temps-réel et les tâches temps-partagé. Son contenu par défaut est 1.000.000, ce qui correspond à 1 seconde. Dans cette période, on réserve aux tâches temps-réel la durée indiquée dans /proc/sys/kernel/sched_rt_runtime_us
. Par défaut, il contient 950.000, ainsi chaque seconde, on réserve 50 millisecondes pour les tâches temps-partagé, et les tâches temps-réel peuvent consommer jusqu’à 950 millisecondes.
Afin de désactiver ce comportement, il suffit d’écrire, dans un script de boot du système par exemple :
echo -1 > /proc/sys/kernel/sched_rt_runtime_us
Ainsi les tâches temps-réel pourront consommer tout le temps CPU dont elles ont besoin sans jamais être préemptées par les tâches temps-partagé. Il est important d’y penser sur les systèmes à vocation temps-réel, sous peine de voir nos applications préemptées temporairement par des processus de moindre importance.
Les derniers noyaux ont étendu ce mécanisme en permettant de rassembler des tâches (temps-réel ou temps-partagé) dans des groupes, et de répartir le temps CPU disponible entre ces différents groupes. Dans le noyau 2.6.38, les groupes peuvent même se constituer à partir des TTY de contrôle. Ce système nécessite un soin méticuleux lors de la mise au point si des contraintes temps-réel entrent en jeu, mais il peut s’avérer très utile lorsqu’on doit garder une disponibilité absolue du processeur pour certaines tâches, alors que d’autres, aux comportements et aux nombres a priori inconnus, s’exécutent simultanément. Nous étudierons ce mécanisme dans un prochain article…