Toute la vérité sur RTOS. Article # 9. Scheduler: implémentation


Les principes de base du travail des planificateurs RTOS ont été examinés dans l'article «Tâches et planification». Dans cet article, nous examinerons plus en détail les fonctionnalités proposées par Nucleus RTOS, ainsi que celles fournies par Nucleus SE.


Articles précédents de la série:
Article # 8. Nucleus SE: conception interne et déploiement
Article # 7. Nucleus SE: Introduction
Article # 6. Autres services RTOS
Article # 5. Interaction et synchronisation des tâches
Article # 4. Tâches, changement de contexte et interruptions
Article # 3. Tâches et planification
Article # 2. RTOS: Structure et mode temps réel
Article # 1. RTOS: introduction.


Planification chez Nucleus RTOS


Étant donné que Nucleus RTOS est un RTOS commercial à part entière et bien établi, nous pouvons supposer en toute sécurité que l'ordonnanceur a été développé conformément aux exigences d'un tel produit. Ce système d'exploitation complexe et flexible offre au développeur un large éventail de capacités pour résoudre presque toutes les tâches de programmation en temps réel imaginables.


Le planificateur peut prendre en charge un nombre illimité de tâches (limité uniquement par les ressources disponibles) et travailler avec la gestion des priorités. La tâche peut être affectée d'une priorité de 0 à 255, où 0 est la priorité la plus élevée et 255 la plus faible. Une tâche a une priorité dynamique, c'est-à-dire qu'elle peut être modifiée au moment de l'exécution par la tâche elle-même ou par une autre. Plusieurs tâches peuvent se voir attribuer le même niveau de priorité. Dans un cas extrême, toutes les tâches peuvent se voir attribuer la même priorité, ce qui permet de mettre en œuvre une politique de planification sur le principe du Round Robin et du Time-Slice.
S'il y a plusieurs tâches avec la même priorité, elles seront planifiées en utilisant l'algorithme Round Robin dans l'ordre dans lequel elles ont été préparées. La tâche doit suspendre ou transférer le contrôle pour que la tâche suivante démarre. Des tâches peuvent également être affectées à des intervalles de temps qui offrent une séparation plus contrôlée du temps processeur disponible.


La planification des tâches est déterministe à 100%, ce qui est attendu d'un noyau similaire. Les tâches peuvent également être créées et détruites dynamiquement, ce qui, grâce au planificateur, passe inaperçu par l'utilisateur.


Planification chez Nucleus SE


J'ai développé tous les aspects de Nucleus SE afin qu'ils soient généralement compatibles avec Nucleus RTOS, mais aussi plus simples et plus efficaces en termes de mémoire. Le planificateur ne fait pas exception. Il fournit de nombreuses fonctionnalités du planificateur Nucleus RTOS, mais est quelque peu limité. La flexibilité est obtenue grâce à la configuration lors de l'assemblage.
Une application Nucleus SE peut avoir un maximum de 16 tâches (et au moins une). Bien que théoriquement ce nombre puisse être augmenté, l'efficacité des algorithmes sera menacée; un certain nombre de structures de données reposent sur le stockage du numéro d'index de tâche (de 0 à 15) dans un quartet (quatre bits), et elles devront être traitées avec le code correspondant.


Pour atteindre un équilibre entre flexibilité et simplicité (et taille), au lieu d'avoir un planificateur avec plusieurs capacités, Nucleus SE propose l'un des quatre types de planificateurs au choix: Exécuter vers le composant (RTC), Round Robin (RR), Time-Slice ( TS) et Priority. L'ordonnanceur est sélectionné statiquement au moment de l'assemblage. Les détails sur chaque type de planificateur sont décrits ci-dessous dans la section "Types de planificateur".


Comme tout autre aspect de Nucleus SE, les tâches sont des objets statiques. Ils sont déterminés lors de la configuration et leur priorité (index) ne peut pas être modifiée.


Planificateurs Nucleus SE


Comme indiqué ci-dessus, Nucleus SE propose l'un des quatre types d'ordonnanceurs parmi lesquels choisir. Comme la plupart des aspects de la configuration de Nucleus SE, ce choix est déterminé en écrivant dans nuse_config.h, le paramètre NUSE_SCHEDULER_TYPE doit être défini en conséquence, comme indiqué dans ce fragment du fichier de configuration:



Quel que soit le planificateur sélectionné, son code de démarrage est appelé immédiatement après l'initialisation du système. Des informations complètes sur l'initialisation de Nucleus SE seront présentées dans le prochain article.


Run to Completion Scheduler


Le RTC Scheduler est la solution la plus simple et la plus appropriée s'il répond aux exigences de l'application. Chaque tâche doit terminer son travail avant d'exécuter la fonction de retour et de permettre au planificateur de terminer la tâche suivante.


Il n'est pas nécessaire d'avoir une pile distincte pour chaque tâche. Tout le code est écrit en C, le langage d'assemblage n'est pas requis. Vous trouverez ci-dessous l'intégralité du code du planificateur RTC.



Le code est juste une boucle sans fin qui à tour de rôle appelle chaque tâche. Le tableau NUSE_Task_Start_Address [] contient des pointeurs vers la fonction externe de chaque tâche. La macro PF0 est une simple conversion d'un pointeur void en un pointeur vers une fonction void sans paramètre. Il est conçu pour assurer la lisibilité du code.
La compilation conditionnelle est utilisée pour activer la prise en charge de fonctions supplémentaires: NUSE_SUSPEND_ENABLE détermine si les tâches peuvent être suspendues; NUSE_SCHEDULE_COUNT_SUPPORT détermine si une valeur de compteur est requise chaque fois qu'une tâche est planifiée. Vous trouverez plus d'informations à ce sujet dans le prochain article.


Scheduler Round Robin


Si un peu plus de flexibilité est requise que celle fournie par l'ordonnanceur RTC, l'ordonnanceur RR convient. Il permet à la tâche de transférer le contrôle ou la pause, puis de continuer à partir du même point. La surcharge supplémentaire, en plus de la complexité du code et de la non-portabilité, est que chaque tâche nécessite sa propre pile.
Le code du planificateur se compose de deux parties. Le composant de lancement est le suivant:



Si la prise en charge de l'état initial de la tâche est activée (à l'aide du paramètre NUSE_INITIAL_TASK_STATE_SUPPOR T, voir «Paramètres» dans l'article suivant), la planification commence à partir de la première tâche terminée; sinon, une tâche d'index 0 est utilisée. Le contexte de cette tâche est ensuite chargé à l'aide de NUSE_Context_Load () . Pour plus d'informations sur l'enregistrement et la restauration d'un contexte, consultez la section «Enregistrement du contexte» dans l'article suivant.


La deuxième partie de l'ordonnanceur est le composant «re-planning»:



Ce code est appelé lorsque la tâche libère le processeur central ou fait une pause.


Le code sélectionne la tâche avec l'index suivant pour démarrer et place la valeur dans NUSE_Task_Next, en tenant compte du fait que la suspension de la tâche est activée ou non. La macro NUSE_CONTEXT_SWAP () est ensuite utilisée pour appeler la commutation de contexte à l'aide d'une interruption logicielle. Pour plus d'informations sur l'enregistrement et la restauration d'un contexte, consultez la section «Enregistrement du contexte» dans l'article suivant.


Planificateur de priorités


Le planificateur de priorités de Nucleus SE, comme les autres options, est conçu pour fournir les fonctionnalités requises, tout en étant assez simple. Par conséquent, chaque tâche a une priorité unique, il est impossible d'avoir plusieurs tâches avec un niveau de priorité. La priorité est déterminée par l'index de la tâche, où 0 est le niveau de priorité le plus élevé. L'index de la tâche est déterminé par son emplacement dans le tableau NUSE_Task_Start_Address []. Le prochain article fournira des informations plus détaillées sur la configuration des tâches.


Comme les ordonnanceurs RR et TS, l'ordonnanceur prioritaire a deux composants. Le composant de démarrage du planificateur prioritaire est le même que les planificateurs RR et TS, comme illustré ci-dessus. La composante de rééchelonnement est légèrement différente:



Il n'y a pas de code conditionnel qui pourrait désactiver la suspension des tâches, car cette fonctionnalité est obligatoire pour le planificateur de priorité; toute alternative serait illogique. La fonction NUSE_Reschedule () accepte un paramètre qui «indique» quelle tâche peut être planifiée ensuite - new_task. Cette valeur est définie lorsque la replanification est invoquée car une autre tâche est invoquée. L'index de cette tâche est transmis en tant que paramètre. Le planificateur peut alors déterminer s'il faut effectuer un changement de contexte en comparant la valeur de new_task avec l'index de la tâche en cours (NUSE_Task_Active) . Si la replanification est le résultat d'une pause de tâche, le paramètre sera défini sur NUSE_NO_TASK et le planificateur recherchera la tâche avec la priorité la plus élevée.


États des tâches


En règle générale, tous les systèmes d'exploitation ont le concept de trouver des tâches dans un certain «état». Les détails varient en fonction du RTOS. Dans cet article, nous verrons comment Nucleus RTOS et Nucleus SE utilisent les états de tâche.


États des tâches Nucleus RTOS


Nucleus RTOS prend en charge 5 états de tâche.


  • Exécution: tâche qui gère actuellement le processeur. Évidemment, une seule tâche peut occuper cet état.
  • Préparation: tâche prête à être exécutée (ou à poursuivre) avant que le planificateur ne décide de la lancer. En règle générale, une tâche a une priorité inférieure à celle en cours d'exécution.
  • Suspension: la tâche "dormir". Il n'est pas pris en compte lors de la planification jusqu'à ce qu'il se réveille, et à ce moment il sera «prêt» et pourra continuer à s'exécuter plus tard. Habituellement, une tâche est dans un état de «sommeil» car elle attend quelque chose: lorsque la ressource devient disponible, lorsque la période de temps définie expire ou lorsqu'une autre tâche la réveille.
  • Annuler: la tâche a été «tuée». Il n'est pas pris en compte lors de la planification tant qu'il n'est pas réinitialisé, après quoi la tâche sera «prête» ou «suspendue».
  • Fin: la tâche est terminée et a quitté sa fonction externe en quittant simplement l'unité externe ou en exécutant l'instruction return. Il n'est pas pris en compte lors de la planification tant qu'il n'est pas réinitialisé, après quoi la tâche sera «prête» ou «suspendue».
Étant donné que Nucleus RTOS prend en charge la création et la destruction dynamiques d'objets, y compris des tâches, la tâche peut également être considérée dans un état «distant». Cependant, dès que la tâche est supprimée, toutes ses ressources système cessent d'exister et la tâche elle-même n'existe plus, elle ne peut pas avoir d'état. Le code de tâche peut être disponible, mais l'objet de tâche doit être recréé.

États des tâches dans Nucleus SE


Le modèle d'état de tâche dans Nucleus SE est un peu plus simple. Habituellement, il n'y a que 3 états: exécution, disponibilité et pause. Le statut de chaque tâche est stocké dans NUSE_Task_Status [] , qui a des valeurs de type NUSE_READY , bien qu'il n'ait jamais de valeur qui reflète le statut d'exécution. Si la suspension de tâche n'est pas activée (voir «Options» dans l'article suivant), seuls deux états de tâche sont possibles et ce tableau est absent.

Il existe plusieurs types de tâches de pause. Si une tâche est explicitement suspendue par elle-même ou par une autre tâche, cela s'appelle une «suspension pure» et est représenté par le statut NUSE_PURE_SUSPEND. Si l'état «veille» est activé et que la tâche est suspendue pendant un certain temps, elle a le statut
NUSE_SLEEP_SUSPEND . Si la fonction de blocage des appels d'API est activée (via NUSE_BLOCKING_ENABLE , voir «Paramètres» dans l'article suivant), la tâche peut être suspendue jusqu'à ce que la ressource devienne disponible. Chaque type d'objet a son propre état de suspension de tâche, par exemple sous la forme NUSE_MAILBOX_SUSPEND. Dans Nucleus SE, une tâche peut être verrouillée dans une partition de mémoire, un groupe d'événements, une boîte aux lettres, une file d'attente, un canal ou un sémaphore.

Statut du fil


Lorsque vous discutez du comportement d'une tâche, les mots «Statut» et «Statut» sont généralement utilisés assez librement. Il existe un facteur supplémentaire, que l'on peut conditionnellement appeler «l'état du flux». Il s'agit de la variable globale NUSE_Thread_State, qui contient une indication de la nature du code en cours d'exécution. Cela s'applique au comportement de nombreux appels d'API. Valeurs possibles:

  • NUSE_TASK_CONTEXT - L'appel d'API a été effectué à partir d'une tâche.
  • NUSE_STARTUP_CONTEXT - l'appel d'API a été effectué à partir du code de démarrage; le planificateur n'a pas encore été démarré.
  • NUSE_NISR_CONTEXT et NUSE_MISR_CONTEXT - l'appel d'API a été effectué à partir du gestionnaire d'interruption. Les interruptions dans Nucleus SE seront discutées dans le prochain article.

Le prochain article détaillera les fonctionnalités avancées du planificateur dans Nucleus SE, ainsi que le maintien du contexte.

À propos de l'auteur: Colin Walls travaille dans l'industrie électronique depuis plus de trente ans, consacrant la majeure partie de son temps au micrologiciel. Il est maintenant ingénieur firmware chez Mentor Embedded (une division de Mentor Graphics). Colin Walls intervient souvent lors de conférences et séminaires, auteur de nombreux articles techniques et de deux livres sur le firmware. Vit au Royaume-Uni. Blog professionnel de Colin , e-mail: colin_walls@mentor.com

Source: https://habr.com/ru/post/fr422615/


All Articles