Portage des systèmes d'exploitation vers de nouvelles architectures de puces
Ce que les responsables de RT-Thread ont appris en portant le système d'exploitation des systèmes embarqués sur différentes architectures de puces.
On m’a demandé un jour pourquoi les ordinateurs sont appelés « ordinateurs » alors qu’ils font bien plus que calculer des nombres. Un PC moderne navigue sur Internet, lit de l'audio et de la vidéo, génère de superbes graphismes pour les jeux vidéo et les films, simule et prédit des conditions météorologiques complexes et des risques épidémiologiques, donne vie à des plans d'architecture et d'ingénierie, et bien plus encore.
La raison pour laquelle les ordinateurs peuvent faire tout cela est que tous ces problèmes peuvent être exprimés sous forme d'équations numériques et que le processeur de l'ordinateur, son unité centrale, n'est en réalité qu'un simple calculateur.
Pour qu'un processeur envoie des signaux à un disque dur pour écrire des données ou à un moniteur pour afficher une image, il doit recevoir des instructions. Ces instructions se présentent sous la forme de « code », ce qui est une façon laconique de dire que quelqu'un doit écrire un programme qui « parle » le même langage que le processeur. Un processeur comprend le langage machine, un ensemble de bits pour la plupart incompréhensible que la plupart des humains ne prennent pas la peine d'écrire manuellement. Au lieu de cela, nous utilisons des langages de programmation comme C, C++, Java, Python, etc. Ces langages sont analysés et compilés en langage machine, qui est transmis au processeur.
Si vous essayez d'instruire un processeur dans une langue qu'il ne comprend pas, le processeur ne saura pas quoi faire. Vous pouvez constater les résultats plutôt peu spectaculaires d’une telle tentative de mauvaise communication en essayant de démarrer un Raspberry Pi à partir d’une image RHEL x86_64. Ce serait bien si cela pouvait fonctionner, mais ce n'est pas le cas.
Portage d'un OS vers une nouvelle architecture
Le projet RT-Thread propose un système d'exploitation (OS) open source pour les programmeurs de systèmes embarqués. L’espace embarqué est extrêmement diversifié, avec de nombreux appareils Internet des objets (IoT), industriels personnalisés et amateurs. L'objectif de RT-Thread est de rendre la programmation embarquée facile pour tout le monde, quel que soit l'appareil que vous utilisez. Parfois, cela signifie porter un système d'exploitation sur une nouvelle architecture, que ce soit pour une puce de la même architecture mais avec des jeux d'instructions légèrement différents ou pour de nouvelles architectures.
Aborder ce problème peut être un peu intimidant au début : vous ne savez peut-être pas par où ni comment commencer. Cet article rassemble les leçons que les responsables de RT-Thread ont apprises lors du portage de RTOS sur de nouvelles architectures de puces.
Ce qu'il faut savoir avant de commencer
Voici une vue d’ensemble d’un processus apparemment insurmontable. Cela peut différer pour votre projet, mais sur le plan conceptuel, cela est relativement universel, même si certains détails sont différents :
- Préparer un environnement d'exécution en langage C
- Confirmez que les caractères peuvent être envoyés et reçus via un port série
- Confirmez que le code de changement de contexte fonctionne
- Obtenez les minuteries matérielles prises en charge
- Confirmez que la routine d'interruption peut recevoir et analyser les données via le port série
Le modèle d'exécution
Pour les architectures les plus avancées, le système d'exploitation et les applications utilisateur s'exécutent avec différents niveaux de privilèges. Cela empêche un code défectueux d'affecter l'intégration et la sécurité du système d'exploitation. Par exemple, dans l'architecture ARMv7-A, le système d'exploitation s'exécute généralement en mode Système, tandis que dans ARMv8-A, un système d'exploitation peut s'exécuter au niveau de privilège EL2 ou EL3.
Habituellement, une puce exécute le code de démarrage avec le niveau de privilège le plus élevé lorsqu'elle est allumée. Après cela, cependant, le système d’exploitation bascule le niveau de privilège sur son mode cible.
1. Exécuter le code C
L'action clé de cette étape consiste à définir la section du symbole de début de bloc (.bss) sur zéro et à configurer les pointeurs de pile.
Dans les implémentations en langage C, les variables globales non initialisées et les variables statiques sont généralement stockées dans la section .bss, qui n'occupe aucun espace sur le périphérique de stockage. Lorsque le programme est chargé, l'espace correspondant est alloué en mémoire et initialisé à zéro. Lorsque le système d’exploitation démarre, il doit effectuer ce travail tout seul.
D'un autre côté, le système d'exploitation doit initialiser l'espace de pile et configurer le pointeur de pile. Étant donné que les programmes en langage C enregistrent et restaurent les variables locales sur la pile lors de l'entrée et de la sortie d'une fonction, le pointeur de pile doit être défini avant d'appeler une fonction C. RT-Thread doit effectuer cette étape pour chaque thread nouvellement créé.
2. Utilisez au moins un lecteur série
RT-Thread génère des informations et des journaux via le port série, ce qui aide également à déboguer le code pendant le processus de transplantation. À ce stade, il n'est pas nécessaire de recevoir des données via les ports série. Nous savions que nous étions sur la bonne voie lorsque nous avons vu pour la première fois notre logo RT-Thread convivial et familier sur le port série !
3. Confirmer la logique de changement de contexte
Le contexte d'une tâche est l'ensemble de son environnement d'exécution, qui contient les registres génériques, le compteur du programme, l'emplacement du frame de pile, etc. Lorsqu'un nouveau thread est créé, RT-Thread doit allouer et configurer son contexte manuellement afin que le planificateur puisse basculer vers le nouveau thread, comme il le fait avec les autres.
Il y a trois choses auxquelles il faut prêter attention :
- Premièrement, au démarrage de RT-Thread, les interruptions sont désactivées par défaut. Ils sont activés lorsque le planificateur de tâches est activé pour la première fois ; ce processus est implémenté en langage assembleur pendant la période de changement de contexte.
- Deuxièmement, la planification suivante commencera à la sortie d'un thread, c'est-à-dire lorsque les ressources détenues seront récupérées par le thread inactif.
- Troisièmement, l’ordre dans lequel les données sont poussées dans la pile doit être cohérent avec l’ordre dans lequel les données sont extraites de la pile.
Généralement, vous souhaitez accéder normalement à la fonction principale et à la console msh. Cependant, le contrôle des entrées ne peut pas être réalisé à ce stade car les interruptions d'entrée série ne sont pas implémentées. Lorsque des interruptions série sont implémentées, des entrées msh peuvent être effectuées.
4. Réglez la minuterie
RT-Thread nécessite une minuterie pour générer périodiquement des interruptions ; ceci est utilisé pour compter les ticks qui se sont écoulés depuis le démarrage du système. Le numéro de tick est utilisé pour fournir des fonctions d'interruption logicielle et indiquer au noyau quand commencer à planifier une tâche.
Définir la valeur d’une tranche de temps peut être une tâche délicate. C'est généralement 10 ms à 1 ms. Si vous choisissez une petite tranche de temps sur un processeur lent, la plupart du temps est consacré au changement de tâche, au détriment de faire autre chose.
5. Confirmez que le port série fonctionne correctement
Dans cette étape, nous avons interagi avec RT-Thread msh via le port série. Nous avons envoyé des commandes, appuyé sur Entrée et regardé msh exécuter la commande et afficher les résultats.
Ce processus n'est généralement pas difficile à mettre en œuvre. Un mot d'avertissement cependant : n'oubliez pas d'effacer l'indicateur d'interruption sur certaines plates-formes une fois l'interruption du port série traitée.
Une fois que le port série fonctionne correctement, le processus de portage est essentiellement terminé !
Être occupé
Pour porter votre projet sur différentes architectures de puces, vous devez être très clair sur l'architecture de la puce que vous ciblez. Familiarisez-vous avec le code sous-jacent dans les points les plus critiques de votre projet. En croisant les références au manuel de la puce combinées à une grande expérience de travail pratique, vous apprendrez le mode privilège de la puce, l'enregistrement et la méthode de compilation.
Si vous n'avez pas de projet à porter sur une nouvelle puce, rejoignez-nous ; le projet RT-Thread peut toujours utiliser de l'aide pour porter RTOS sur de nouvelles puces ! En tant que projet open source, RT-Thread change le paysage de la programmation embarquée open source. Veuillez vous présenter et demander de l'aide au RT-Thread Club !
Cet article est basé sur Comment porter le système d'exploitation vers une architecture de puce différente ? sur la communauté DEV et est republié avec autorisation.