
Dans un
article précédent, nous avons examiné les différents types de planification pris en charge par le RTOS et les capacités associées dans Nucleus SE. Dans cet article, nous examinerons les options de planification supplémentaires dans Nucleus SE et le processus d'enregistrement et de restauration du contexte.
Articles précédents de la série:
Article # 9. Scheduler: implémentationArticle # 8. Nucleus SE: conception interne et déploiementArticle # 7. Nucleus SE: IntroductionArticle # 6. Autres services RTOSArticle # 5. Interaction et synchronisation des tâchesArticle # 4. Tâches, changement de contexte et interruptionsArticle # 3. Tâches et planificationArticle # 2. RTOS: Structure et mode temps réel
Article # 1. RTOS: introduction.
Fonctionnalités optionnelles
Pendant le développement de Nucleus SE, j'ai rendu facultatif le nombre maximum de fonctions, ce qui économise de la mémoire et / ou du temps.
Suspendre les tâches
Comme mentionné précédemment dans
l'article Scheduler: Implementation , Nucleus SE prend en charge diverses options pour suspendre les tâches, mais cette fonctionnalité est facultative et est incluse par le symbole
NUSE_SUSPEND_ENABLE dans
nuse_config.h . Si la
valeur est TRUE , la structure de données est définie comme
NUSE_Task_Status [] . Ce type de suspension s'applique à toutes les tâches. Le tableau est de type
U8 , où 2 quartets sont utilisés séparément. Les 4 bits inférieurs contiennent l'état de la tâche:
NUSE_READY, NUSE_PURE_SUSPEND ,
NUSE_SLEEP_SUSPEND ,
NUSE_MAILBOX_SUSPEND , etc. Si une tâche est suspendue par un appel d'API (par exemple,
NUSE_MAILBOX_SUSPEND ), les 4 bits de poids fort contiennent l'index de l'objet sur lequel la tâche est suspendue. Ces informations sont utilisées lorsque la ressource devient disponible et pour appeler l'API, vous devez savoir laquelle des tâches suspendues doit être reprise.
Pour effectuer la suspension de tâche, une paire de fonctions de planificateur sont utilisées:
NUSE_Suspend_Task () et
NUSE_Wake_Task () .
Le code
NUSE_Suspend_Task () est le suivant:

La fonction enregistre le nouvel état de la tâche (tous les 8 bits), obtenu en tant que paramètre suspend_code. Lorsque vous activez le verrouillage (voir «Verrouiller les appels API» ci-dessous), le code retour
NUSE_SUCCESS est
enregistré . Ensuite,
NUSE_Reschedule () est appelé pour transférer le contrôle à la tâche suivante.
Le code
NUSE_Wake_Task () est assez simple:

Le statut de la tâche est défini sur
NUSE_READY . Si le planificateur de priorité n'est pas utilisé, la tâche en cours continue d'occuper le processeur jusqu'à ce que le moment soit venu de libérer la ressource. Si le planificateur de priorité est utilisé,
NUSE_Reschedule () est appelé avec l'index de tâche comme indication de l'achèvement, car la tâche peut avoir une priorité plus élevée et doit être immédiatement exécutée.
Verrouiller les appels API
Nucleus RTOS prend en charge un certain nombre d'appels d'API avec lesquels un développeur peut suspendre (bloquer) une tâche si les ressources ne sont pas disponibles. La tâche reprendra lorsque les ressources seront à nouveau disponibles. Ce mécanisme est également implémenté dans Nucleus SE et est applicable à un certain nombre d'objets du noyau: une tâche peut être bloquée dans une section mémoire, dans un groupe d'événements, une boîte aux lettres, une file d'attente, un canal ou un sémaphore. Mais, comme la plupart des outils de Nucleus SE, il est facultatif et est défini par le symbole
NUSE_BLOCKING_ENABLE dans
nuse_config.h . S'il est défini sur
TRUE , le tableau
NUSE_Task_Blocking_Return [] est défini, qui contient le code retour pour chaque tâche; il peut s'agir de
NUSE_SUCCESS ou du code
NUSE_MAILBOX_WAS_RESET , indiquant que l'objet a été réinitialisé lorsque la tâche a été verrouillée. Lorsque le verrouillage est activé, le code correspondant est inclus dans les fonctions API à l'aide de la compilation conditionnelle.
Compteur du planificateur
Nucleus RTOS calcule le nombre de fois qu'une tâche a été planifiée depuis sa création et sa dernière réinitialisation. Cette fonctionnalité est également implémentée dans Nucleus SE, mais elle est facultative et est définie par le symbole
NUSE_SCHEDULE_COUNT_SUPPORT dans
nuse_config.h . S'il est défini sur
TRUE , un tableau de
NUSE_Task_Schedule_Count [] de type
U16 est créé , qui stocke le compteur de chaque tâche dans l'application.
État initial de la tâche
Lorsqu'une tâche est créée dans Nucleus RTOS, vous pouvez sélectionner son état: prêt ou en pause. Dans Nucleus SE, par défaut, toutes les tâches sont prêtes au démarrage. L'option sélectionnée avec le symbole
NUSE_INITIAL_TASK_STATE_SUPPORT dans
nuse_config.h vous permet de sélectionner l'état de démarrage. Le
tableau NUSE_Task_Initial_State [] est défini dans
nuse_config.c et nécessite l'initialisation de
NUSE_READY ou
NUSE_PURE_SUSPEND pour chaque tâche de l'application.
Enregistrement du contexte
L'idée de maintenir le contexte d'une tâche avec n'importe quel type de planificateur, à l'exception de RTC (Run to Completion), a été présentée dans l'article # 3 «Tâches et planification». Comme déjà mentionné, il existe plusieurs façons de maintenir le contexte. Étant donné que Nucleus SE n'est pas conçu pour les processeurs 32 bits, j'ai choisi d'utiliser des tables, pas des piles, pour maintenir le contexte.
Un tableau Ă deux dimensions du type
ADDR NUSE_Task_Context [] [] est utilisé pour enregistrer le contexte de toutes les tâches dans l'application. Les lignes sont
NUSE_TASK_NUMBER (le nombre de tâches dans l'application), les colonnes sont
NUSE_REGISTERS (le nombre de registres qui doivent être enregistrés; dépend du processeur et est défini dans
nuse_types.h) .
Bien sûr, le maintien du contexte et la restauration du code dépendent du processeur. Et c'est le seul code Nucleus SE lié à un appareil spécifique (et à un environnement de développement). Je vais donner un exemple du code de sauvegarde / restauration du processeur ColdFire. Bien que ce choix puisse sembler étrange en raison d'un processeur obsolète, son assembleur est plus facile à lire que les assembleurs de la plupart des processeurs modernes. Le code est assez simple à utiliser comme base pour créer un changement de contexte pour d'autres processeurs:

Lorsque le changement de contexte est requis, ce code est appelé dans NUSE_Context_Swap. Deux variables sont utilisées:
NUSE_Task_Active , l'index de la tâche en cours, dont le contexte doit être conservé;
NUSE_Task_Next , l'index de la tâche dont vous souhaitez charger le contexte (voir la section Global Data).
Le processus de préservation du contexte fonctionne comme suit:
- Les registres A0 et D0 sont temporairement stockés sur la pile;
- A0 est configuré pour pointer vers un tableau de blocs de contexte NUSE_Task_Context [] [] ;
- D0 est chargé à l'aide de NUSE_Task_Active et multiplié par 72 (ColdFire a 18 registres, nécessitant 72 octets pour le stockage);
- puis D0 est ajouté à A0 , qui pointe désormais vers un bloc de contexte pour la tâche en cours;
- puis les registres sont stockés dans le bloc de contexte; d'abord A0 et D0 (à partir de la pile), puis D1-D7 et A1-A6 , puis SR et PC (à partir de la pile, nous examinerons le changement de contexte rapidement initié), et à la fin, le pointeur de pile est enregistré.
Le processus de chargement de contexte est la même séquence d'actions dans l'ordre inverse:
- A0 est configuré pour pointer vers un tableau de blocs de contexte NUSE_Task_Context [] [] ;
- D0 est chargé à l'aide de NUSE_Task_Active , incrémenté et multiplié par 72;
- puis D0 est ajouté à A0 , qui pointe maintenant vers le bloc de contexte pour la nouvelle tâche (puisque le chargement du contexte doit être effectué dans le processus inverse de sauvegarde de la séquence, le pointeur de pile est d'abord requis);
- puis les registres sont restaurés à partir du bloc de contexte; d'abord, le pointeur de pile, puis PC et SR sont poussés sur la pile, puis D1-D7 et A1-A6 sont chargés, et à la fin de D0 et A0 .
La difficulté de mise en œuvre du changement de contexte est que l'accès au registre d'état est difficile pour de nombreux processeurs (pour ColdFire, il s'agit de
SR ). Une solution courante est l'interruption, c'est-Ă -dire l'interruption de programme ou l'interruption de branchement conditionnelle, qui provoque le chargement du
SR sur la pile avec le
PC . C'est ainsi que Nucleus SE fonctionne sur ColdFire. La macro
NUSE_CONTEXT_SWAP () est définie dans
nuse_types.h , qui s'étend à :
asm ("piège # 0");Voici le code d'initialisation (
NUSE_Init_Task () dans
nuse_init.c ) pour les blocs de contexte:

C'est ainsi que se produit l'initialisation du pointeur de pile,
PC et
SR . Les deux premiers ont des valeurs définies par l'utilisateur dans
nuse_config.c . La valeur de
SR est définie comme le caractère
NUSE_STATUS_REGISTER dans
nuse_types.h . Pour ColdFire, cette valeur est
0x40002000 .
Données globales
Le planificateur Nucleus SE nécessite très peu de mémoire pour stocker les données, mais, bien sûr, utilise des structures de données associées aux tâches, qui seront discutées en détail dans les articles suivants.
Données RAM
Le planificateur n'utilise pas les données situées dans la ROM et de 2 à 5 variables globales sont placées dans la RAM (toutes sont définies dans
nuse_globals.c ), selon le planificateur utilisé:
- NUSE_Task_Active - une variable de type U8 contenant l'index de la tâche en cours;
- NUSE_Task_State - une variable de type U8 contenant une valeur qui indique l'état du code en cours d'exécution, qui peut être une tâche, un gestionnaire d'interruption ou un code de démarrage; les valeurs possibles sont: NUSE_TASK_CONTEXT , NUSE_STARTUP_CONTEXT , NUSE_NISR_CONTEXT et NUSE_MISR_CONTEXT ;
- NUSE_Task_Saved_State - une variable de type U8 utilisée pour protéger la valeur de NUSE_Task_State dans une interruption gérée;
- NUSE_Task_Next - une variable de type U8 contenant l'index de la tâche suivante, qui doit être planifiée pour tous les planificateurs sauf RTC;
- NUSE_Time_Slice_Ticks - une variable de type U16 contenant un compteur de tranches de temps; utilisé uniquement avec le planificateur TS.
Empreinte des données du planificateur
Le planificateur Nucleus SE n'utilise pas de données ROM. La quantité exacte de données RAM varie en fonction du planificateur utilisé:
- pour RTC - 2 octets ( NUSE_Task_Active et NUSE_Task_State );
- pour RR et priorité - 4 octets ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State et NUSE_Task_Next );
- pour TS - 6 octets ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State , NUSE_Task_Next et NUSE_Time_Slice_Ticks ).
Mise en place d'autres mécanismes de planification
Malgré le fait que Nucleus SE propose un choix de 4 planificateurs, couvrant la plupart des cas, l'architecture ouverte vous permet de mettre en œuvre des opportunités pour d'autres cas.
Tranche de temps avec tâche en arrière-plan
Comme déjà décrit dans l'
article # 3, «Tâches et planification», le simple Time Slice Scheduler a des limites car il limite la durée maximale pendant laquelle un processeur peut exécuter une tâche. Une option plus sophistiquée consisterait à ajouter la prise en charge de la tâche d'arrière-plan. Une telle tâche peut être planifiée sur n'importe quel emplacement alloué pour les tâches suspendues et exécutée lorsque l'emplacement est partiellement libéré. Cette approche vous permet de planifier des tâches à intervalles réguliers et avec un pourcentage prévu du temps de cœur du processeur à terminer.
Priorité et Round Robin (RR)
Dans la plupart des cœurs en temps réel, le planificateur de priorités prend en charge plusieurs tâches à chaque niveau de priorité, contrairement à Nucleus SE, où chaque tâche a un niveau unique. J'ai donné la préférence à cette dernière option, car elle simplifie considérablement les structures de données et, par conséquent, le code du planificateur. Pour prendre en charge des architectures plus complexes, de nombreuses tables ROM et RAM seraient nécessaires.
À 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.
À propos de la traduction: cette série d'articles semblait intéressante en ce que, malgré les approches décrites obsolètes à certains endroits, l'auteur présente au lecteur mal formé les caractéristiques du système d'exploitation en temps réel. J'appartiens moi-même à l'équipe de créateurs du
RTOS russe , que nous avons l'
intention de rendre gratuit , et j'espère que le cycle sera utile aux développeurs novices.