Virtualisation et systèmes embarqués : l'exemple ADEOS

GNU/Linux Magazine n° 152 | septembre 2012 | Romain Naour - Olivier Viné
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
De la virtualisation dans l'embarqué ? Virtualiser, dans l'acception la plus commune, c'est créer un ou plusieurs environnements logiciels en faisant abstraction du support matériel. En d'autres termes, c'est la capacité à faire fonctionner une ou plusieurs applications - souvent aussi un ou plusieurs systèmes d'exploitation – sur une ou plusieurs machines. Au final, l'exploitant travaille sur des machines virtuelles avec une très grande souplesse pour allouer des ressources (mémoire, puissance CPU) et gérer les défaillances matérielles.

Ces technologies, qui se généralisent sur les parcs de serveurs, peuvent sembler bien éloignées du monde des systèmes embarqués. Car dans l'embarqué, on aime le matériel. On aime le toucher, l'alimenter, le connecter, parfois le souder et - surtout ! - on aime le programmer et titiller ses registres. De ce fait, ajouter une couche logicielle coupant le cordon avec le hardware est une idée qui peut sembler bien incongrue...

Pourtant, les raisons de s'intéresser aux principes de la virtualisation dans les systèmes embarqués sont nombreuses et, pour certaines, déjà anciennes. Citons ainsi :

- L'approche une fonction = un calculateur a entraîné une explosion du nombre de calculateurs embarqués dans les avions, les voitures, les bus, etc., provoquant par ailleurs une forte augmentation des coûts associés : câblage et maintenance sont deux aspects visibles de ces phénomènes. La tentation est donc grande de profiter des puissances de calcul disponibles pour faire cohabiter plusieurs fonctions sur le même calculateur physique. L'aéronautique a pris le problème à bras le corps très tôt puisque la première version de la norme ARINC 653 date de 1996. Ce standard permet de mettre en place l'architecture IMA (Integrated Modular Avionics) basée sur le partitionnement logiciel.

- La virtualisation est un outil efficace en phase de développement et de validation. Une fois développée, une machine virtuelle peut en effet être diffusée rapidement et sans surcoût à tous les développeurs de l'équipe projet. Par ailleurs, les possibilités d'instrumentation sont nombreuses et beaucoup plus simples de mise en œuvre que sur une plateforme matérielle réelle. Elles permettent ainsi de tester une application sans aucune modification de son code source. Àce sujet, on pourra consulter les travaux du projet Couverture [1] pour la certification de systèmes embarqués critiques.

- La cohabitation entre processus temps réel et services logiciels sans contrainte sur un même calculateur est fréquente sur des équipements tels que les bancs d'essais (acquisition temps réel et interface utilisateur sur la même machine), les set-top box, les robots, etc. Dans ce cas, la virtualisation est utilisée pour créer un ordonnanceur temps réel qui partage le temps processeur entre les tâches temps réel (prioritaires) et les autres applications. On retrouve cette approche dans les différentes solutions permettant de développer des applications temps réel sous Linux telles que RT-Linux, RTAI et XENOMAI.

C'est à cette dernière utilisation que nous allons nous intéresser dans cet article. Faire du temps réel dur sur un système Linux embarqué est donc possible et il existe de nombreux articles expliquant comment installer Xenomai et développer sa première application temps réel. Mais qui connaît vraiment Adeos et les mécanismes de virtualisation permettant de réaliser ce petit exploit ?

1. ADEOS : virtualiser pour permettre le temps réel sous Linux

Adeos (Adaptative Domain Environnement for Operating System) est une couche de virtualisation de ressources matérielles permettant de faire cohabiter plusieurs noyaux sur une même machine physique. Cette technique de virtualisation, présentée par Karim Yaghmour dans un article technique en 2001 [2] et développée par Philippe Gerum depuis 2002, repose sur l'utilisation d'un pipeline d’interruptions, nommé « I-pipe » pour « interrupt pipeline ».

Actuellement, Adeos existe sous la forme d'un patch à appliquer au noyau Linux et permettant de le faire cohabiter avec un RTOS [3] comme Xenomai.

Dans le cadre du projet Xenomai, l'utilisation du I-pipe a été étendue aux différents événements pouvant se produire sur le système. Le terme « événement » regroupe aussi bien les interruptions externes (asynchrones), les exceptions (synchrones) et les appels système.

1.1. Installation et configuration d'Adeos

La version d'Adeos étudiée est la version adeos-ipipe-2.6.38.8-arm-1.18-08, qui est disponible dans les sources de Xenomai ou sur le site d'Adeos (http://download.gna.org/adeos/patches/).

Cette version d'Adeos est développée spécialement pour optimiser les caractéristiques temps réel du système. Le patch apporte des modifications supplémentaires qui n'entrent pas en ligne de compte dans le fonctionnement du I-pipe. Nous pouvons citer l'utilisation de la technique dite « Fast Context Switch Extention » [5] qui permet de gagner plusieurs centaines de microsecondes (~200µs) lors du changement de processus exécuté sur un processeur ARM.

L'application du patch peut se faire manuellement :

$ patch -p1 < adeos-ipipe-2.6.38.8.arm-1.18-08.patch

ou lors de la configuration de Xenomai :

$ cd xenomai-2.6

$ ./script/prepare-kernel.sh --linux=/path/to/linux/kernel/sources --arch=arm --adeos=ksrc/arch/arm/patches/adeos-ipipe-2.6.38.8.arm-1.18-08.patch

Une fois le kernel patché, l'activation d'Adeos est réalisée lors de la configuration du kernel :

$ make ARCH=arm menuconfig

-> Kernel Features

 -> [*] Interrupt pipeline

 -> (4) Max domains

Si l'option FCSE est disponible, il est vivement conseillé de l'activer pour l'utilisation de Xenomai. Attention : si le mode « guaranteed » est sélectionné, le nombre de processus (PID) est limité à 95 !

-> System Type

 -> [*] Fast Context Switch Extension (EXPERIMENTAL)

 ->  FCSE mode (best effort) --->

 -> [ ] Preemptible cache flushes

 -> [*] help messages

 -> [ ] FCSE debug

1.2. Principe général du I-pipe

Au sein d'Adeos, chaque noyau est représenté par une entité appelée « domaine » qui surveille les événements circulant sur le I-pipe. Adeos ne peut pas démarrer le système seul, il a besoin d'un noyau hôte contenu dans un domaine spécifique : le domaine racine (root domain). Le noyau hôte se charge de l'initialisation critique (bas niveau) avant d'initialiser le I-pipe. Cela se traduit pour le kernel Linux par des modifications au niveau de la fonction de démarrage start_kernel(). À l’issue du démarrage du domaine racine, le système doit fonctionner normalement.

Une fois le domaine racine fonctionnel, chaque domaine supplémentaire peut être positionné sur le I-pipe selon un ordre de priorité (cet ordre est fixé pour Xenomai).

Ce principe est représenté à la figure 1.

Figure 1 : Pipeline d'interruption. Le domaine temps réel est servi en priorité à l'arrivée d'un événement. Le domaine racine assure le fonctionnement par défaut du système.

Chaque domaine du pipeline peut avoir besoin d'être notifié de ce qu'il se passe sur d'autres domaines. Pour cela, Adeos met en place un système de notification inter-domaines. Ces notifications sont utilisées, par exemple, pour avertir de chaque appel système ou de chaque événement déclenché par le kernel Linux lui-même. Cela comprend donc les syscall, le ré-ordonnancement, la création ou la destruction de processus, les différentes fautes d'exécution, etc. Plus généralement, ce sont des événements logiciels.

Dans le cadre d'une application temps réel, les contraintes de temps sont souvent liées à des événements extérieurs au processeur. Nous allons donc nous intéresser plus particulièrement à la gestion des interruptions matérielles par Adeos.

Lorsqu'une interruption est détectée, elle est envoyée au premier domaine sur le I-pipe, puis éventuellement transmise aux autres domaines moins prioritaires. Pour réaliser ce routage des interruptions sur le I-pipe, Adeos doit être le seul à accéder au contrôleur d'interruptions (PIC, APIC, ...). De ce fait, un noyau contenu dans un domaine qui tenterait d'accéder directement au contrôleur d'interruption pourrait bloquer définitivement le I-pipe. Pour remplacer la désactivation des interruptions au niveau matériel, Adeos implémente, pour chaque domaine, un masque d'interruption logiciel. Ainsi un noyau peut demander à son domaine de ne plus lui envoyer d’événement lors de ses sections critiques (par exemple un contexte d'interruption). Les interruptions arrivant au niveau du domaine bloqué sont mises en attente et seront envoyées au domaine dès qu'il sera débloqué.

1.3. Le timer logiciel TSC

La mise en place du pipeline d'interruption ne suffit pas pour garantir le respect des contraintes temporelles. Il faut aussi une base de temps ayant une résolution la plus précise et stable possible, ce qui n'est pas toujours possible en utilisant les timers matériels disponibles.

Par exemple, les processeurs ARM n'intègrent généralement pas de timers ayant une résolution suffisante pour les tâches temps réel. Adeos propose donc une émulation d'un timer de 64bits nommé timer TSC (Time Stamp Counter).

Au passage, notons que le portage de Xenomai sur un nouveau type de processeur ARM repose essentiellement sur l'émulation du timer TSC d'Adeos. Sur ARM, chaque fondeur choisit le type de timer implémenté, ce qui nécessite systématiquement l'adaptation du patch Adeos.

2. Comprendre l'implémentation du I-pipe

Le pipeline d'interruption prend la forme d'une liste chaînée dont les domaines sont les éléments.

Figure 2 : Chaînage des domaines

Un domaine se présente sous la forme de deux structures, la première ipipe_domain définit la configuration, la seconde ipipe_percpu_domain_data enregistre les informations liées à l'état courant.

Les champs principaux de la première structure sont listés dans le tableau suivant :

Champ Description
slot Le numéro de slot permet d'accéder à la seconde structure du domaine contenue dans le tableau ipipe_percpu_darray.
p_link Structure contenant les liens vers les domaines de priorité supérieure et inférieure.
evhand Tableau de gestionnaires d’événements logiciels (notifications).
irqs Tableau de descripteurs d'interruptions.
priority Détermine la priorité du domaine sur le pipeline.
domid Numéro d'identifiant du domaine.
name Chaîne de caractères pour enregistrer le nom du domaine.

Le tableau ipipe_domain->irqs est la base de la virtualisation des interruptions matérielles. Celui-ci contient une description de chaque interruption « virtualisée » par un domaine. Son contenu sera détaillé par la suite (cf. Principe de virtualisation des interruptions).

Les champs principaux de la seconde structure sont listés dans le tableau suivant :

Champ Description
status Définit l'état actuel du domaine. Par exemple, le bit 0 indique si le domaine est bloqué ou débloqué.

irqpend_himap

irqpend_mdmap[]

irqpend_lomap[]

Champ de bits représentant les IRQ reçues et sauvegardées lorsque le domaine est bloqué. Les interruptions sont mises temporairement en attente le temps que le domaine soit débloqué. On parle plus simplement du « i-log ».
irqheld_map[] Champ de bits représentant les IRQ verrouillées. Ces IRQ ne sont pas traitées par le domaine même si celui-ci n'est pas bloqué.
irqall[] Ce tableau enregistre l'activité sur les IRQ. Un compteur est incrémenté à chaque fois qu'une IRQ est traitée.
evsync Champ de bits indiquant si un événement logiciel est en cours de traitement par le domaine.

Le bit 0 du champ status est le masque d'interruption logicielle qui est inspiré de la technique de « protection optimiste contre les interruptions » (Optimistic interrupt protection) [4]. À l'origine, l'objectif de cette technique était de gagner des temps de cycle processeur lors de chaque entrée/sortie de section critique. En faisant l'hypothèse qu'il n'y aurait pas d'interruption pendant la section critique, il n'était donc pas nécessaire de masquer les interruptions au niveau matériel. Le masque d'interruption matériel était remplacé par une variable qui servait de masque d'interruption logicielle. Adeos reprend ce principe pour créer un espace de temps pendant lequel un domaine ne sera pas interrompu, sans pour autant désactiver les interruptions au niveau matériel. Nous allons détailler ce principe dans le paragraphe suivant.

3. Principe du blocage/déblocage d'un domaine

Un domaine doit pouvoir différer le traitement d'une interruption venant du I-pipe afin de ne pas interrompre son noyau. Le domaine est dit bloqué.

Le blocage du domaine est réalisé par la fonction ipipe_stall_pipeline_from() qui vient positionner à 1 le masque d'interruption logiciel (bit 0 du champ status).

Cela permet de ne plus avoir besoin de bloquer matériellement les interruptions arrivant sur le système. Ainsi le domaine racine peut être bloqué sans empêcher l'arrivée d'interruptions sur le domaine temps réel. Cependant, pour ne pas perturber l'exécution du noyau temps réel, les interruptions matérielles sont tout de même désactivées pendant le traitement par celui-ci.

Le patch Adeos ré-implémente les fonctions du noyau Linux qui servent normalement à désactiver ou réactiver les interruptions pour venir bloquer ou débloquer le domaine racine.

Figure 3 : Blocage du domaine racine

Lorsque le domaine racine est bloqué, les interruptions ne progressent plus sur le pipeline et sont stoppées à son niveau. Chaque interruption reçue est sauvegardée dans le i-log.

Par exemple, le noyau Linux bloque son domaine pendant le traitement d'une première interruption (contexte d'interruption). Une seconde interruption peut arriver sur le processeur car les interruptions ne sont pas désactivées sur le processeur. Cette interruption sera mise dans le i-log lorsqu'elle aura atteint le domaine racine.

Dès que le noyau Linux peut être à nouveau interrompu, il demande à Adeos de débloquer son domaine. Le déblocage du domaine est réalisé par la fonction ipipe_unstall_pipeline_from() qui ré-initialise le masque d'interruption. Toutes les interruptions en attente dans le i-log sont traitées, c'est la synchronisation __ipipe_sync_stage().

Figure 4 : Déblocage du domaine racine et traitement des interruptions sauvegardées dans le i-log

C'est grâce à la virtualisation qu'il est possible de différer le traitement de l'interruption. Dans le paragraphe suivant, nous allons voir le principe de virtualisation des interruptions.

4. Principe de virtualisation des interruptions

Une interruption est virtualisée lorsqu'un noyau a installé dans le tableau ipipe_domain->irqs de son domaine, un gestionnaire d'interruption, une fonction d'acquittement et initialisé un champ qui définit le comportement du domaine vis-à-vis de l'interruption. Le comportement détermine si l'interruption doit être gérée par le domaine, s'il faut la transmettre aux autres domaines moins prioritaires ou si elle doit être éliminée du pipeline.

Si une interruption n'est pas virtualisée, un domaine adopte un comportement par défaut qui fait suivre aux autres domaines chaque interruption reçue sans la traiter. Le noyau n'est donc pas averti du passage de l'interruption sur le pipeline.

Au lancement du système, toutes les IRQ (NR_IRQS) du noyau Linux sont virtualisées sur le domaine racine. La virtualisation de toutes les interruptions du noyau Linux est obligatoire puisqu'il n'est plus possible de recevoir les interruptions directement depuis le contrôleur d'interruption. Tant que le domaine temps réel n'est pas installé, le système doit fonctionner comme avec un noyau non patché par Adeos.

Le code ci-dessous est appelé par la fonction ipipe_init() qui est placée par le patch Adeos, dans start_kernel().(fichier kernel/ipipe/core.c)

/* First, virtualize all interrupts from the root domain. */

 for (irq = 0; irq < NR_IRQS; irq++)

  ipipe_virtualize_irq(ipipe_root_domain,

         irq,

                    (ipipe_irq_handler_t)__ipipe_mach_doirq(irq),

         NULL, __ipipe_mach_ackirq(irq),

         IPIPE_HANDLE_MASK | IPIPE_PASS_MASK);

La fonction __ipipe_mach_doirq() sert à retrouver le gestionnaire d'interruption de Linux de l'IRQ. De la même façon __ipipe_mach_ackirq() retrouve la fonction d'acquittement de l'IRQ.

Le dernier argument sert à définir le comportement du domaine vis-à-vis de l'interruption. On indique au domaine qu'il y a un gestionnaire d'interruption pour traiter l'interruption et qu'il faut transmettre celle-ci aux autres domaines sur le I-pipe.

Les différents comportements des interruptions seront étudiés au paragraphe suivant.

Les interruptions virtualisées et leur configuration peuvent être visualisées dans /proc/ipipe/Linux :

# cat /proc/ipipe/Linux

 +----- Handling ([A]ccepted, [G]rabbed, [W]ired, [D]iscarded)

 | +---- Sticky

 | | +--- Locked

 | | | +-- Exclusive

 | | | | +- Virtual

[IRQ] | | | | |

   0: A . . . .

   1: A . . . .

[...]

207: A . . . .

224: G . . . V

225: G . . . V

[Domain info]

id=0x00000000

priority=100

Le processeur utilisé pour l'exemple est un processeur ARM iMX25 (ARM926ej-s) possédant 208 IRQ matérielles virtualisées par Adeos. Les deux interruptions supplémentaires sont des interruptions virtuelles (V) créées par Adeos ou par Xenomai.

L'interruption n°224 correspond à l'interruption liée à la ré-implémentation de la fonction printk() par Adeos. L'interruption n°225 correspond à la gestion des APC (Application Procedure Call).

Lorsque le noyau temps réel est installé sur le I-pipe, on remarque qu'il traite uniquement quelques interruptions. Les autres interruptions ne font que traverser le domaine temps réel sans aucun traitement (Xenomai n'est pas notifié de leur passage). La priorité du domaine temps réel est dite « topmost », c'est-à-dire qu'Adeos fait l'hypothèse qu'il est toujours placé en tête du I-pipe.

# cat /proc/ipipe/Xenomai

 +----- Handling ([A]ccepted, [G]rabbed, [W]ired, [D]iscarded)

 | +---- Sticky

 | | +--- Locked

 | | | +-- Exclusive

 | | | | +- Virtual

[IRQ] | | | | |

  54: W . . X .

226: W . . . V

[Domain info]

id=0x58454e4f

priority=topmost

L'interruption n°54 est fondamentale pour Xenomai puisqu'il s'agit de l'interruption du timer matériel, de même que l'interruption n°226 qui permet de faire revenir une tâche temps réel sur l'ordonnanceur de Xenomai. Sur ARM, l'interruption du timer matériel est au centre de l'émulation du timer TSC.

Nous allons détailler les différents comportements des domaines.

5. Comportement des domaines vis-à-vis des événements

Nous avons vu précédemment qu'une interruption peut être gérée différemment par chaque domaine. Ce comportement est défini dans le champ control contenu dans le descripteur d'interruption (ipipe_irqdesc) du tableau ipipe_domain->irqs de chaque domaine.

domain->irqs[IRQ] Control Comportement du domaine pour cette interruption
  acknowledge Fonction d'acquittement
  handler Gestionnaire d'interruption
  cookie Variable opaque (void*) passée en argument au gestionnaire d'interruption lors de son exécution

Figure 5 : Configuration de la gestion d'interruption

Un domaine définit si une interruption est :

- Acceptée ([A]ccepted) : l'interruption est gérée par un gestionnaire d'interruption puis passée dans tous les cas au domaine suivant sur le pipeline. (bits handle et pass sont à 1).

- Attrapée ([G]rabbed) : l'interruption est gérée par un gestionnaire d'interruption qui peut choisir de passer l'interruption au domaine suivant ou de l'éliminer du pipeline. (bit handle est à 1).

- Attachée ([W]ired) : l'interruption doit être gérée immédiatement et uniquement par le gestionnaire d'interruption du premier domaine sur le pipeline (le plus prioritaire). (bits handle et wired sont à 1).

- Passée (pas de lettre, Passed) : l'interruption n'est pas gérée mais directement passée au domaine suivant sur le pipeline. C'est le comportement par défaut. (bit passe à 1).

- Jetée ([D]iscarded) : l'interruption n'est pas gérée et n'est pas passée au domaine suivant. Elle est considérée comme terminée et disparaît du pipeline. (aucun bit à 1).

- Verrouillée ([L]ocked) : l'interruption n'est pas traitée par le domaine même si celui-ci n'est pas bloqué. Le verrouillage sert à masquer individuellement les interruptions.

En plus du comportement, certaines interruptions ont des propriétés particulières :

Statut Définition
Exclusive (X) Les interruptions marquées « exclusives » apparaissent uniquement dans le domaine temps réel. Lorsqu'une interruption est marquée « exclusive », il devient impossible de changer le gestionnaire d'interruption en appelant successivement rthal_irq_request() sans avoir appelé rthal_irq_release() entre temps. (-EBUSY)
Virtuelle (V) Les interruptions virtuelles sont des interruptions logicielles gérées de la même manière que les interruptions matérielles au travers du I-pipe. Leur numéro d'IRQ est supérieur au nombre d'IRQ réelles du processeur (NR_IRQS). Elles servent à la communication inter-domaines (exemple : printk()). Ce type d'interruption est créé avec la fonction ipipe_alloc_virq().
Enable Indique si l'interruption est activée au niveau matériel (chip). L'activation ou la désactivation se fait avec la fonction ipipe_control_irq().

Pour des processeurs SMP, Adeos définit des comportements supplémentaires :

- Système : Les interruptions « système » regroupent les interruptions qui ne peuvent être gérées par Adeos. Il est impossible de changer le gestionnaire d'interruption sur une IRQ système, ni de modifier sa configuration avec ipipe_control_irq() (-EPERM). Elles sont toujours définies comme interruptions « collées » (bits sticky, system et handle sont toujours à 1 en même temps).

- Collée ([S]ticky) : Les interruptions « sticky » ont la caractéristique de ne pas passer par le I-pipe. Lorsqu'une interruption sticky arrive, elle est directement placée dans le i-log du domaine courant, avant de déclencher le traitement de toutes les interruptions en attente. Elle est donc quasiment traitée immédiatement si le domaine n'est pas bloqué. Sinon, elle sera traitée dès le déblocage du domaine. Si une interruption est collée sur un des domaines, il doit en être de même sur tous les autres domaines du I-pipe.

L'utilisation du mode SMP avec Adeos introduit plusieurs cas particuliers au niveau de la gestion des interruptions, notamment pour la gestion des VNMI (Virtual Non Maskable Interrupt). Nous ne les aborderons pas dans cet article.

5.1. Les interruptions attachées (wired)

Ce type d'interruption a été introduit dans Adeos pour optimiser le traitement d'une interruption par le domaine temps réel. Les interruptions « wired » sont destinées à être utilisées exclusivement par les tâches temps réel. Lorsqu'une interruption wired arrive, elle est inconditionnellement et immédiatement envoyée vers le domaine le plus prioritaire pour y être traitée par le gestionnaire d'interruption.

Par exemple, si le domaine racine est en cours d'exécution, il est préempté par le domaine le plus prioritaire pour un traitement quasi immédiat.

Les suppositions suivantes doivent être respectées par le domaine en tête du pipeline :

- Les interruptions wired sont purement dynamiques. La décision de les propager sur le pipeline doit être prise à partir de la routine d'interruption (ISR) du domaine en tête du pipeline. Il est parfois nécessaire qu'une interruption soit reçue par le domaine temps réel et par le domaine racine.

- Le domaine en tête du pipeline doit être toujours le même (priorité fixe entre les domaines).

- Les interruptions wired ne peuvent pas être définies comme des interruptions system ou sticky.

- Le domaine racine ne doit pas être en tête du pipeline.

- Les interruptions wired doivent avoir un gestionnaire d'acquittement installé sur le domaine temps réel. Cela est vrai uniquement pour les interruptions non virtuelles (irq < NR_IRQS). Les interruptions virtuelles sont des interruptions logicielles, il n'y a pas besoin de les acquitter.

Xenomai utilise au moins une interruption wired pour réactiver le domaine temps réel à partir du domaine racine. Dans l'exemple sur le principe de virtualisation, l'IRQ 226 (xnarch_escalation_virq) du domaine temps réel est une interruption virtuelle et wired (W+V). Elle est déclenchée depuis le domaine racine par la fonction xnarch_escalate() (voir include/asm-arm/bits/pod.h) :

if (rthal_current_domain == rthal_root_domain) {

 rthal_trigger_irq(xnarch_escalation_virq);

 return 1;

}

Le domaine temps réel est alors réactivé pour gérer l'interruption avec le gestionnaire xnpod_schedule_handler() qui provoque un ré-ordonnancement des tâches temps réel xnpod_schedule().

Cette interruption virtuelle est nécessaire puisqu'une tâche temps réel de Xenomai peut recourir à des appels système sur le noyau Linux. Dans ce cas, la tâche temps réel bascule vers l'ordonnanceur de Linux pour exécuter l'appel système, on dit que la tâche passe en mode secondaire. Dès que la tâche fait à nouveau appel à une fonction de l'API Xenomai, elle rebascule vers l'ordonnanceur de Xenomai (mode primaire) en déclenchant l'interruption xnarch_escalation_virq.

Pour les interruptions virtualisées par Xenomai, le scénario est pratiquement le même. Prenons l'exemple de l'interruption du timer matériel (IRQ 54) : nous supposons que le domaine actif est le domaine racine. À l'arrivée de l'interruption, celui-ci est préempté au profit du domaine temps réel. L'interruption est acquittée et gérée par le gestionnaire d'interruption de Xenomai. Une fois l'interruption traitée, elle est soit éliminée du pipeline, soit passée au domaine racine.

Figure 6 : Gestion d'une interruption « wired »

Nous détaillerons plus loin dans quelles conditions l'interruption du timer matériel est transmise au domaine racine.

6. Comment attraper les interruptions avant Linux ?

Pour attraper les interruptions avant le noyau Linux, il faut descendre au niveau du code assembleur.

L'arrivée d'une interruption matérielle provoque l'interruption du processeur, qui sauvegarde son contexte d'exécution avant d'appeler la fonction asm_do_IRQ(). Cette fonction a pour rôle de démarrer la gestion de l'interruption par Linux. Le patch Adeos remplace l'appel de asm_do_IRQ() par sa propre fonction __ipipe_grab_irq() (voir arch/arm/include/asm/entry-macro-multi.S) :

#ifdef CONFIG_IPIPE

 bne __ipipe_grab_irq

#else

 bne asm_do_IRQ

#endif

De ce fait, lorsqu'une interruption arrive, elle est directement envoyée sur le I-pipe. Son parcours sur le I-pipe est géré en fonction de sa configuration (Wired, Accepted...) par la fonction __ipipe_handle_irq().

Si l'interruption arrive sur le domaine racine sans être traitée par Xenomai, Adeos est chargé d'appeler la fonction asm_do_IRQ() pour démarrer son traitement par Linux. Ce surcoût d'exécution dans le traitement d'une IRQ reste minime, de l'ordre de la microseconde (voire de la dizaine de microseconde). Cependant, celle-ci est acquittée par le domaine racine. Le noyau Linux est par conséquent modifié au niveau de l'API générique [6] qui contrôle les interruptions pour ne pas l'acquitter une seconde fois.

7. Émulation du timer logiciel TSC

Sur l’architecture ARM, Adeos utilise une émulation d’un timer haute résolution de 64bits qui sert de base de temps pour le noyau temps réel de Xenomai. Ce timer émulé est appelé TSC pour Time Stamp Counter, en référence aux timers que l’on peut trouver sur l’architecture x86.

L’émulation de ce timer TSC permet d’avoir une base de temps pour Xenomai la plus précise possible, et en même temps, indépendante du type de timer matériel utilisé sur le processeur.

Plusieurs méthodes d’émulation sont disponibles suivant le type de timer matériel :

- Free-running counters 64 bits ;

- Free-running counters 32 bits ;

- Free-running counters 16 bits ;

- Free-running counters countdown 32 bits ;

- Décrémenter 16 bits.

Pour fonctionner, l'émulation du timer TSC nécessite la mise en place au niveau matériel, d'une horloge servant à mesurer l'écoulement du temps (timer clocksource) et d'une source d’événements (timer clockevent).

Pour disposer d'un timer clocksource, il faut que le timer matériel s'incrémente de façon monotone jusqu'à repasser par zéro. Celui-ci ne doit donc pas perdre de cycles d'horloge, ni évoluer de façon discontinue. L'idéal est d'avoir une résolution la plus grande possible.

Le timer clockevent est programmé pour générer une interruption à la date du prochain événement à venir sur le système. L'interruption peut être générée à partir d'un second timer matériel fonctionnant en mode monocoup (Figure 7) ou à partir de la comparaison entre une valeur de référence (CMP value) et de la valeur courante du timer clocksource (Figure 8).

Figure 7 : timer clockevent implémenté sur un timer monocoup. Le prochain événement est programmé sur timer matériel avec le temps restant avant l'interruption.

Figure 8 : Le prochain événement est programmé sur le timer matériel à la date calculée en fonction de la valeur courante du timer clocksource et du temps restant avant l'interruption.

Sur certains processeurs, le noyau Linux peut déjà utiliser ce système depuis l'intégration des timers haute résolution (hrtimer) et du mode tickless [7]. Dans ce cas, l'option GENERIC_CLOCKEVENT est automatiquement sélectionnée.

Si les timers haute résolution ne sont pas disponibles, cela indique que le timer matériel fonctionne en mode périodique pour générer une interruption à intervalle régulier (toutes les 5ms). L'option ARCH_USE_GETTIMEOFSET est alors sélectionnée. Dans ce cas, Adeos est obligé de modifier le mode de fonctionnement du timer matériel pour implémenter un timer clocksource et un timer clockevent.

Le tableau suivant donne un comparatif entre 4 modèles de processeur ARM :

Processeurs Résolution des timers Option sélectionnée en fonction du mode de fonctionnement du timer matériel Base de temps
s3c2440 16 bits ARCH_USE_GETTIMEOFSET Jiffies
s3c6410 32 bits ARCH_USE_GETTIMEOFSET Jiffies
iMX23 16 bits GENERIC_CLOCKEVENT 1ns
iMX25 32 bits GENERIC_CLOCKEVENT 1ns

Lorsque l'option GENERIC_CLOCKEVENT est présente, il est possible que l'émulation du timer TSC et le noyau Linux essayent d'utiliser le même timer clockevent. Dans ce cas, Adeos met en place un driver nommé ipipe_tick_device, afin de partager le timer matériel entre Linux et Xenomai.

Le noyau Linux prévoit de sélectionner la meilleure clocksource présente sur le système. Une fois le timer TSC opérationnel, Linux le sélectionne automatiquement :

$ dmesg

[..]

Switching to clocksource ipipe_tsc

[..]

$ cd /sys/devices/system/clocksource/clocksource0

$ cat available_clocksource

ipipe_tsc mxc_timer1

$ cat current_clocksource

ipipe_tsc

Si l'option ARCH_USE_GETTIMEOFSET est présente, le timer matériel est contrôlé exclusivement par Xenomai.

La figure suivante montre le fonctionnement du système lorsque Adeos a été activé sur le noyau Linux. On remarque que la mise à jour du timer TSC se fait lors de la programmation d'un nouvel événement sur le timer clockevent (__ipipe_tsc_update()).

Figure 9 : Tant que le domaine temps réel n'est pas installé sur le I-pipe, le timer clockevent est encore contrôlé par Linux.

Il faut maintenant un endroit où stocker la valeur du compteur TSC. Cette valeur doit être accessible à la fois à partir de l'espace noyau pour la mise à jour du timer TSC, et à la fois accessible pour les applications Xenomai depuis l'espace utilisateur. Cela permet de ne pas recourir à un appel système.

La solution adoptée sur l’architecture ARM est d’allouer une page mémoire (vectors page) partagée entre l’espace utilisateur et le kernel Linux. Cette page mémoire contient la valeur courante du timer TSC et une fonction de lecture __ipipe_tsc_get() placée à côté de la zone mémoire dédiée aux « kernel_user_helpers ». Cette zone mémoire a la particularité de ne pas pouvoir être mise en cache.

Figure 10 : Mapping mémoire où est enregistré le timer TSC

La fonction __ipipe_tsc_get() est chargée en mémoire au démarrage du système. Elle a pour rôle de fournir la valeur courante du timer TSC. C'est-à-dire la dernière valeur sauvegardée dans last_tsc (64bits), plus le temps écoulé depuis la dernière mise à jour du timer TSC. Ce temps est déterminé en lisant la valeur courante du timer clocksource. Il faut donc pouvoir accéder au timer matériel depuis l'espace utilisateur. L'adresse virtuelle du timer clocksource est donc enregistrée sur la page mémoire (__ipipe_tsc_addr).

L'émulation du timer TSC est maintenant opérationnelle, le domaine temps réel peut être maintenant installé sur le I-pipe.

La figure suivante présente l'organisation mise en place lors de l'installation du domaine temps réel :

- en rose, la partie dépendante de l'architecture (Timer matériel, Adeos, timer TSC) ;

- en vert, la partie Linux.

Figure 11 : Xenomai a pris le contrôle du timer clockevent. Les interruptions sont programmées par les applications Xenomai et le noyau Nucleus.

Nous pouvons remarquer que le timer matériel n'est plus programmé à partir du noyau Linux, il a été capturé par Xenomai (la variable __ipipe_mach_timerstolen est à 1). De ce fait, la mise à jour du timer TSC est effectuée uniquement depuis Xenomai, lors de chaque reprogrammation du timer matériel.

Pour continuer à faire fonctionner l’ordonnanceur de kernel Linux, Xenomai crée un timer périodique (htimer ou host timer) ayant une période de 5ms. À chaque fois que ce timer expire, l'interruption est transmise à Linux au travers d'Adeos afin de mettre à jour le timer système de Linux.

La réussite du portage d’Adeos/Xenomai sur un nouveau processeur ARM dépend essentiellement du bon fonctionnement de la base de temps, et donc de l’émulation du timer TSC. Le wiki de Xenomai propose un guide pour le portage d'Adeos sur ARM : http://www.xenomai.org/index.php/I-pipe:ArmPorting.

Conclusion

Cette première partie nous a permis de décrire les principes de fonctionnement d'Adeos/I-pipe. Dans une seconde partie, nous nous intéresserons au portage d'Adeos sur la carte Mini2440 utilisé pour le concours Linuxembedded.

Liens

[1] http://www.projet-couverture.com

[2] http://www.opersys.com/ftp/pub/Adeos/adeos.pdf

[3] http://www.opersys.com/ftp/pub/Adeos/rtosoveradeos.pdf

[4] http://www.usenix.org/publications/library/proceedings/micro93/full_papers/stodolsky.txt et

http://www.dtic.mil/cgi-bin/GetTRDoc?AD=ADA266638

[5] http://sisyphus.hd.free.fr/~gilles/pub/fcse/paper.pdf

[6] http://kernel.org/doc/htmldocs/genericirq.html

[7] http://lwn.net/Articles/415470/

http://lwn.net/Articles/223185/

http://kernel.org/doc/ols/2006/ols2006v1-pages-333-346.pdf

http://www.linuxsymposium.org/2005/linuxsymposium_procv1.pdf

http://ftp.sunet.se/pub/Linux/kernel.org/linux/kernel/people/tglx/hrtimers/ols2006-hrtimers.pdf

http://www.linuxembedded.fr/concours2012