Archive for mars 2011

Partager le temps-réel ?

Embarqué, Linux, Temps-réel | Publié par cpb
mar 25 2011

(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
Partager le temps-réel ? - 01

 

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.

 

partager-le-temps-reel-02

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…

[ACTU] Linux 2.6.38

Actualité, Embarqué, Formations, Linux | Publié par cpb
mar 16 2011

Le nouveau noyau Linux 2.6.38 est disponible depuis ce matin.

Je l’ai testé sur la carte IGEPv2 (processeur Arm) que j’utilise pour mes formations « Linux temps-réel et embarqué ». Il fonctionne parfaitement (fichier de configuration kernel). Voici un exemple de session :

[tr-a-1 ]# ssh root@192.168.3.152
root@192.168.3.152's password:
[IGEPv2] # cat /proc/version
Linux version 2.6.38-cpb (root@tr-a-1) (gcc version 4.3.5 (Buildroot 2010.08) ) #2 SMP Wed Mar 16 00:49:18 CET 2011
[IGEPv2] # cat /proc/cpuinfo
Processor	: ARMv7 Processor rev 3 (v7l)
processor	: 0
BogoMIPS	: 497.82

Features	: swp half thumb fastmult vfp edsp thumbee neon vfpv3
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x1
CPU part	: 0xc08
CPU revision	: 3

Hardware	: IGEP v2 board
Revision	: 0020
Serial		: 0000000000000000
[IGEPv2]  #

J’ai vérifié sur ce noyau 2.6.38 les exemples de modules kernel que j’emploie lors des formations « Écriture de drivers pour Linux », ils se compilent sans erreur. Je ne les ai pas encore testés tous intensivement, ca viendra dans les prochains jours…

Formation « Programmation Système sous Linux »

Formations, Linux | Publié par cpb
mar 12 2011

J’animerai une session de formation « Programmation Système sous Linux » chez Logilin du 2 au 5 mai prochain. Pour plus de détails, et les formalités d’inscription, rendez-vous sur cette page.

Temps-réel et économie d’énergie (2)

Embarqué, Linux, Temps-réel | Publié par cpb
mar 11 2011

(Cet article est un extrait de la version préparatoire de mon livre « Applications temps-réel avec Linux » en cours d’écriture)

 

Économie ou performance : arbitrage pour le temps-réel embarqué (Partie 2)

Nous avons examiné dans le précédent billet le fonctionnement des governors, les modules qui permettent au noyau Linux de régler la fréquence du processeur, en tenant compte de certaines heuristiques (privilégier les économies d’énergie, la performance du calcul, ou s’adapter automatiquement).

Pour un système embarqué, il est habituellement important d’économiser l’énergie électrique, que ce soit pour préserver la batterie ou pour éviter d’incorporer un système de refroidissement encombrant. Durant certaines phases de fonctionnement (attente de commande utilisateur, entrées-sorties sur des fichiers, communication réseau, etc.) la réactivité et la vitesse de traitement du processeur importent peu, et il est possible d’envisager de ralentir le CPU. À d’autres moments (attente d’interruption, phase de calcul urgente…) on préférera pousser le processeur au maximum de sa puissance de traitement.

Pourquoi utiliser un governor (« powersave » ou « performance » par exemple) plutôt que de fixer directement soi-même les fréquences de fonctionnement (en remplissant /sys/devices/system/cpu/numéro de cpu/cpufreq/scaling_cur_freq) ? Tout simplement pour améliorer la portabilité de notre code, qui pourra ainsi évoluer sur de nouveaux processeurs sans être dépendant des fréquences absolues supportées par tel ou tel CPU.

Nous devons auparavant vérifier la réactivité du système lors d’une demande de basculement entre un governor (par exemple « powersave ») et un autre (disons « performance »). Pour cela, on peut utiliser le programme test-changement-governor.c, qui se trouve dans l’archive suivante : http://www.blaess.fr/christophe/files/test-governors-2.tar.bz2. Il permet de lancer deux threads : le premier active, sur le processeur où l’autre thread s’exécute, un governor indiqué en argument sur la ligne de commande. Le second démarre une série de boucles actives au bout d’une seconde. Arrivé à la moitié de sa série, il notifie le premier thread qui bascule alors sur un second governor (indiqué également sur la ligne de commande). Nous pourrons ainsi observer le temps de transition.

Voici un exemple d’exécution (à faire fonctionner avec les droits root) :

# ./test-changement-governor
usage: ./test-changement-governor governor_1 governor_2
# ./test-changement-governor powersave performance
   0   1788
   1   1778
   2   1789
 [...]
  48   1770
  49   1795
  50    914
  51    761
 [...]
  97    760
  98    754
  99    760
#

Ce que l’on peut représenter par le graphique suivant (en abcisse les boucles actives, en ordonnées les durées) :

Le temps de basculement est plutôt rapide, dès que le premier thread a écrit le nom du governor dans le fichier /sys/devices/system/cpu/<numero de cpu>/cpufreq/scaling_governor, la fréquence du cpu augmente immédiatement ce qui réduit le temps d’exécution d’une boucle active.

On peut donc très bien imaginer, dans une application temps-réel d’augmenter la fréquence CPU avant d’entrer dans une portion de code relativement critique en terme de temps d’exécution avec un :

fd = open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor, O_WRONLY);
if (fd < 0) {
  perror("cpufreq");
  exit(1);
}
write(fd, "performance", 11);  /* 11 = strlen("performance") */

Puis, une fois le besoin en puissance CPU amoindri :

 write(fd, "powersave", 9); /* 9 = strlen("powersave") */

NB : il faut penser à adapter le numéro de processeur dans le chemin fourni à open().

Avant d’employer réellement cette technique dans une application temps-réel, il est nécessaire de mesure le temps pris par la modification du governor. Pour cela nous pouvons utiliser le programme mesure-changement-governor.c, présent dans l’archive mentionnée plus haut.

# ./mesure-changement-governor powersave performance
Duree de modification : 51us
#

Sur cette plateforme (Intel dual-core 2×1.86 GHz), le temps nécessaire pour modifier le governor est d’une cinquantaine de micro-secondes. Si les contraintes temporelles sont de l’ordre de la milliseconde, nous pouvons tout à fait intégrer cette variation dynamique, contrôlée par notre application. Il est important de vérifier cette durée (et l’ensemble des autres éléments que nous avons mesuré dans ces deux articles) sur la plateforme cible, pour s’assurer que l’économie d’énergie obtenue en exécutant les tâches de moindre importance à faible vitesse est conciliable avec la réactivité rapide nécessaire pour les parties critiques du système temps-réel.

 

Temps-réel et économie d’énergie (1)

Embarqué, Linux, Temps-réel | Publié par cpb
mar 04 2011

(Cet article est un extrait de la version préparatoire de mon livre « Applications temps-réel avec Linux » en cours d’écriture)

 

Économie ou performance : arbitrage pour le temps-réel embarqué (Partie 1)

Sur la plupart des systèmes interactifs, la charge du processeur varie en permanence entre des états d’intense activité (calcul, compilation, compression, encodage, etc.) et des périodes de repos durant lesquelles aucune opération n’a lieu, jusqu’à la prochaine sollicitation provenant de l’utilisateur.

En fait, sur un système de type bureautique, la charge est généralement très basse, et la puissance complète du processeur n’est nécessaire que pendant des pics d’activité ponctuels. Voici par exemple la charge de la station Linux sur laquelle je rédige ces lignes :

$ uptime
11:44:07 up 29 days,  2:26,  9 users,  load average: 0.11, 0.10, 0.08
$

La commande uptime(1) permet de voir la charge du système (le nombre moyen de tâches en exécution ou en attente d’exécution) pour la dernière minute (0.11 ici), les cinq (0.10) et quinze (0.08) dernières minutes. Nous pouvons estimer que sur cet exemple le processeur n’est utilisé en moyenne que 10% du temps environ. Naturellement, il y a d’autres moments où la charge peut-être beaucoup plus élevée (compilation d’un noyau Linux par exemple).

Un processeur qui fonctionne à plein rendement consomme de l’énergie et s’échauffe sensiblement (ce qui nécessite la mise en marche d’un ventilateur consommant à son tour de l’énergie électrique). Pour limiter la consommation électrique et retarder le vieillissement prématuré du processeur surchauffé, plusieurs techniques sont utilisées de nos jours.

Variation de fréquence d’horloge

Pour les processeurs récents, il est possible de contrôler la fréquence de fonctionnement du CPU (par exemple grâce à la technologie SpeedStep d’Intel ou Cool’n'Quiet d’AMD). Lorsque la charge est faible on se contentera d’une vitesse d’exécution du code basse. Si la charge augmente, on pourra élever (suivant divers critères) la fréquence d’interprétation des instructions-machine. La consommation électrique du processeur est proportionnelle à sa fréquence. Le noyau Linux est tout à fait capable de prendre en charge cette variation de fréquence, en utilisant un algorithme dont l’heuristique (le governor) est configurable par l’administrateur (par le biais du pseudo-fichier /sys/devices/system/cpu/(numéro de cpu)/cpufreq/scaling_governor). La liste des heuristiques disponibles se trouvent dans le pseudo-fichier /sys/devices/system/cpu/(numéro de cpu)/cpufreq/scaling_available_governors. Il existe par exemple les heuristiques suivantes :

  • powersave : utiliser toujours la fréquence la plus basse pour limiter la consommation (utile pour un ordinateur portable sur batterie) ;
  • performance : utiliser toujours la fréquence la plus haute pour optimiser les temps de traitement (serveurs de calcul, d’applications, etc.) ;
  • ondemand : faire varier automatiquement la fréquence en fonction de la charge système pour ajuster la puissance disponible aux demandes de l’utilisateur (poste de travail) ;
  • conservative : comme ondemand, mais en minimisant les changements de fréquence ;
  • userspace : laisser l’administrateur paramétrer la fréquence depuis l’espace utilisateur (en fixant dans /sys/devices/system/cpu/(numéro de cpu)/cpufreq/scaling_cur_freq une des valeurs proposées dans /sys/devices/system/cpu/(numéro de cpu)/cpufreq/scaling_available_frequencies).

 

Ainsi, un « echo powersave > /sys/devices/system/cpu/(numéro de cpu)/cpufreq/scaling_governor » permettra-t’il de basculer le processeur sur une fréquence réduite économisant l’énergie de la batterie.

Dans le cadre d’un système devant répondre à des contraintes temps-réel, ce comportement devra bien évidemment être pris en considération. Plusieurs approches sont possibles :

  • Bloquer en permanence le processeur à sa fréquence la plus élevée en basculant dès le boot du système sur le governor « performance ». Cette solution est la plus coûteuse en énergie électrique (donc en autonomie sur batterie), et nous perdons l’intérêt de la variabilité de la f’réquence du processeur.
  • Utiliser dès le boot le governor « powersave » qui basculera le processeur sur sa fréquence la plus faible et la plus économique. Bien qu’économique en énergie et en usure du processeur, cette approche perd tout l’intérêt de la variation de fréquence, et des vitesses d’exécution plus rapides disponibles.
  • Laisser le governor « ondemand » choisir pour nous la fréquence adéquate. Cette méthode est a priori séduisante, mais nous n’avons aucune garantie de la réactivité du système lors d’une demande importante de charge CPU. Nous allons devoir effectuer quelques expériences pour vérifier le comportement du noyau Linux avec cette heuristique.

 

On trouvera dans l’archive suivante : http://www.blaess.fr/christophe/files/test-governors-1.tar.bz2 quelques fichiers permettant de vérifier le comportement du système. Commençons par le programme test-governor.c : il attend en argument une chaîne de caractères représentant le nom de l’heuristique à tester, qu’il active sur un CPU donné. Ensuite il démarre un thread temps-réel sur ce CPU, qui va effectuer un nombre donné de boucles actives consommant du temps CPU en mesurant la durée des boucles. En fin de programme la durée des boucles est affichée sur la sortie standard. Nous nous en servirons pour produire des graphes. Le numéro du CPU, la priorité temps-réel, le nombre et la longueur des boucles sont configurables en début de fichier.

Heuristique « performance »

Commençons par lancer notre programme avec le governor « performance ». Cela va nous permettre de calibrer le nombre et la longueur des boucles actives en fonction du processeur. Disons qu’une durée de quelques centaines de micro-secondes représente une valeur convenable. Le programme doit être lancé depuis l’identité root pour modifier le governor et accéder à une priorité temps-réel.

# ./test-governor performance
    0    753
    1    765
    2    761
    3    761
[...]
   96    754
   97    760
   98    761
   99    760
#

Sur ce poste, la durée des boucles à fréquence maximale est de 763 micro-secondes environ.

Heuristique « powersave »

Essayons à présent avec le governor économique afin de voir la durée des boucles avec la fréquence minimale :

# ./test-governor powersave
    0   1785
    1   1768
    2   1777
    3   1777
[...]
   96   1777
   97   1779
   98   1768
   99   1777
#

Dans ce cas, la durée moyenne des boucles est d’environ 1777 micro-secondes, un peu plus du double de la durée en mode « performance ».

Heuristique « On Demand »

A la première exécution, le test avec l’heuristique « On Demand » (qui devrait accélérer dynamiquement afin de répondre à la demande soutenu de temps CPU) paraît bien décevant :

# ./test-governor ondemand
    0   1781
    1   1768
    2   1777
    3   1776
[...]
   96   1777
   97   1766
   98   1777
   99   1777
#

Le résultat n’est pas meilleur qu’avec le governor économique « Powersave » !

Après investigation, il apparaît que dans le noyau Linux (2.6.34.7 pour cet essai), le governor « On Demand » est implémenté sous forme d’un « kernel thread », c’est à dire d’une tâche, exécutée dans l’espace mémoire du noyau et possédant les privilèges du noyau, ordonnancée toutefois au même titre que tous les autres processus et threads de l’espace utilisateur. Il existe un thread kondemand par CPU (ou par coeur). Nous les voyons avec la commande ps(1).

# ps aux
[...]
root      1364  0.0  0.0      0     0 ?        S    20:33   0:00 [kondemand/0]
root      1365  0.0  0.0      0     0 ?        S    20:33   0:00 [kondemand/1]
[...]
#

Nous exécutons notre programme de test sur le CPU 1, aussi pouvons-nous nous interroger sur le type et la priorité de l’ordonnancement du thread kondemand/1. Utilisons la commande chrt(1) pour obtenir ces informations :

# chrt -p 1365
stratégie de planification d'exécution pour pid 1365 actuel :SCHED_OTHER
priorité de planification d'exécution pour le pid 1365 actuel : 0
#

Le thread est exécuté suivant l’ordonnancement OTHER, autrement dit en temps partagé ! Naturellement, nos boucles étant exécutées en temps réel (avec la priorité 10) le préemptent et l’empêchent d’agir. Nous allons augmenter la priorité de ce kernel thread :

# chrt -pf 20 1365
# chrt -p 1365
stratégie de planification d'exécution pour pid 1365 actuel :SCHED_FIFO
priorité de planification d'exécution pour le pid 1365 actuel : 20
#

Cette fois, la fréquence va bien être modifiée :

# ./test-governor ondemand
    0   1797
    1   1782
    2   1779
    3   1779
[...]
   96    761
   97    753
   98    767
   99    761
#

En redirigeant les sorties des programmes de test dans des fichiers, et en utilisant Gnuplot pour tracer les courbes, nous obtenons la figure suivante.

echo 'set terminal gif giant size 640,480;
        set output "resultats.gif";
        set multiplot;
        set yrange [0:] ;
        plot "resultat-powersave" with lines title "Powersave" lw 3;
        replot "resultat-performance" with lines title "Performance" lw 3;
        replot "resultat-ondemand" with lines title "On Demand" lw 3'  |  gnuplot
#

Sur ce graphique, on trouve en abcisse les numéros des boucles actives, et en ordonnée leur durées. Plus le tracé est haut, plus le temps d’exécution est long, donc la fréquence processeur faible. Nous retrouvons nos durées de boucle de 765 et 1770 approximativement avec de légères ondulations dues probablement aux déclenchement du timer système. La petite pointe pour l’itération 79 du test « Performance » est causée sans doute par l’arrivée d’une interruption matérielle (disque, réseau, etc.).

Nous voyons bien que le governor « On Demand » bascule bien d’un comportement privilégiant l’économie à un comportement privilégiant les performances. Toutefois, sur cette machine cela se produit au bout de six boucles durant chacune 1,8 milli-secondes, ce qui conduit à une latence d’environ 10 millisecondes avant d’atteindre une fréquence processeur optimale.

Pour certaines applications temps-réel, c’est beaucoup trop ! Nous devrons donc gérer nous-même le basculement d’un governor à l’autre.

(À suivre…)