Principes fondamentaux du noyau FreeRTOS - FreeRTOS

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Principes fondamentaux du noyau FreeRTOS

Le noyau FreeRTOS est un système d'exploitation en temps réel qui prend en charge de nombreuses architectures. Il est idéal pour le développement d'applications de microcontrôleur embarquées. Il fournit :

  • Un planificateur multitâche.

  • Plusieurs options d'allocation mémoire (y compris la possibilité de créer des systèmes complètement alloués de façon statique).

  • Des primitives de coordination inter-tâches, y compris les notifications de tâche, les files d'attente de messages, les différents types de sémaphores, et les tampons mémoire de flux et de messages.

  • Support du multitraitement symétrique (SMP) sur des microcontrôleurs multicœurs.

Le noyau FreeRTOS n'exécute jamais d'opérations non déterministes, comme parcourir une liste liée, à l'intérieur d'une interruption ou section critique. Le noyau FreeRTOS inclut une implémentation efficace d'un minuteur logiciel qui n'utilise pas de temps UC, sauf en cas de besoin d'un minuteur. Les tâches bloquées ne nécessitent pas un traitement régulier chronophage. irect-to-task Les notifications D permettent une signalisation rapide des tâches, avec pratiquement aucune surcharge de RAM. Ils peuvent être utilisés dans la plupart des scénarios intertâches et interrupt-to-task de signalisation.

Le noyau FreeRTOS est conçu pour être petit, simple et facile à utiliser. L'image binaire d'un noyau RTOS typique est de l'ordre de 4 000 à 9 000 octets.

Pour obtenir la up-to-date documentation la plus complète sur le noyau FreeRTOS, consultez FreeRTOS.org. FreeRTOS.org propose un certain nombre de didacticiels et de guides détaillés sur l'utilisation du noyau FreeRTOS, y compris un Guide de démarrage rapide et le document plus approfondi Mastering the FreeRTOS Real Time Kernel.

Planificateur du noyau FreeRTOS

Une application embarquée qui utilise un noyau RTOS peut être structurée sous la forme d'un ensemble de tâches indépendantes. Chaque tâche s'exécute dans son propre contexte, sans dépendance à l'égard d'autres tâches. Une seule tâche de l'application est en cours d'exécution à un moment donné dans le temps. Le planificateur RTOS en temps réel détermine à quel moment chaque tâche doit être exécutée. Chaque tâche est fournie avec sa propre pile. Lorsqu'une tâche est échangée afin qu'une autre tâche puisse s'exécuter, le contexte d'exécution de la tâche est enregistré dans la pile des tâches afin de pouvoir être restauré lorsque la même tâche sera à nouveau échangée ultérieurement pour reprendre son exécution.

Pour fournir un comportement déterministe en temps réel, le planificateur de tâche FreeRTOS permet d'attribuer des priorités strictes. RTOS garantit que la tâche à la priorité la plus haute en mesure de s'exécuter reçoit le temps de traitement. Cette attribution nécessite que le temps de traitement soit partagé entre les tâches de priorité égale, si elles sont prêtes à être exécutées simultanément. FreeRTOS crée aussi une tâche inactive qui s'exécute uniquement lorsqu'aucune autre tâche n'est prête à être exécutée.

Gestion de mémoire

Cette section fournit des informations sur l'allocation mémoire du noyau et sur la gestion de la mémoire de l'application.

Allocation mémoire du noyau

Le noyau RTOS a besoin de RAM chaque fois qu'une tâche, une file d'attente ou un autre objet RTOS est créé. La RAM peut être allouée :

  • Statiquement au moment de la compilation.

  • Dynamiquement depuis le segment RTOS par les fonctions de création d'objet de l'API RTOS.

Lorsque les objets RTOS sont créés de manière dynamique, l'utilisation des fonctions malloc() et free() de la bibliothèque standard C n'est pas toujours appropriée pour un certain nombre de raisons :

  • Ils peuvent ne pas être disponibles sur les systèmes intégrés.

  • Ils occupent un espace code précieux.

  • Ils ne sont généralement pas thread-safe.

  • Ils ne sont pas déterministes.

Pour ces raisons, FreeRTOS conserve l'API d'allocation mémoire dans sa couche portable. La couche portable est en dehors des fichiers source qui implémentent les principales fonctionnalités RTOS. Par conséquent, vous pouvez fournir une implémentation spécifique à l'application adaptée au système en temps réel que vous développez. Lorsque le noyau RTOS nécessite de la mémoire RAM, il appelle pvPortMalloc() au lieu de malloc(). Lorsque la mémoire RAM est libérée, le noyau RTOS appelle vPortFree() au lieu de free().

Gestion de la mémoire de l'application

Lorsque les applications ont besoin de mémoire, elles peuvent l'allouer depuis le segment FreeRTOS. FreeRTOS offre plusieurs modèles de gestion de segment qui varient en complexité et en fonctionnalités. Vous pouvez également fournir votre propre implémentation du segment.

Le noyau FreeRTOS comprend cinq implémentations de segment :

heap_1

Est l'implémentation la plus simple. Ne permet pas que la mémoire soit libérée.

heap_2

Permet que la mémoire soit libérée, mais ne fusionne pas les blocs libres adjacents.

heap_3

Encapsule les fonctions standard free() et malloc() pour la sécurité des threads.

heap_4

Fusionne les blocs libres adjacents afin d'éviter la fragmentation. Inclut une option de placement à adresse absolue.

heap_5

Est similaire à heap_4. Peut étendre le segment sur plusieurs zones mémoire non adjacentes.

Coordination inter-tâches

Cette section contient des informations sur les primitives FreeRTOS.

Files d'attente

Les files d'attente constituent la forme principale de la communication inter-tâches. Elles peuvent être utilisées pour envoyer des messages entre les tâches et entre les interruptions et les tâches. Dans la plupart des cas, elles sont utilisées comme buffers de type FIFO, les nouvelles données étant envoyées à la fin de la file d'attente. (Les données peuvent également être envoyées au début de la file d'attente.) Les messages sont envoyés par copie via les files d'attente, ce qui signifie que les données (qui peuvent être un pointeur vers des tampons mémoire de plus grande taille) sont elles-mêmes copiées dans la file d'attente au lieu de simplement stocker une référence aux données.

Les API de file d'attente permettent de spécifier une durée de blocage. Lorsqu'une tâche tente de lire à partir d'une file d'attente vide, la tâche est placée dans l'état Blocked jusqu'à ce que les données deviennent disponibles dans la file d'attente ou que la durée de blocage se soit écoulée. Les tâches de l'état Blocked ne consomment pas de temps UC, ce qui permet à d'autres tâches de s'exécuter. De même, quand une tâche tente d'écrire sur une file d'attente complète, la tâche est placée dans l'état Blocked jusqu'à ce que l'espace devienne disponible dans la file d'attente ou que la durée de blocage se soit écoulée. Si plusieurs tâches sont bloquées sur la même file d'attente, la tâche avec la priorité la plus haute est débloquée en premier.

D'autres primitives FreeRTOS, telles que direct-to-task les notifications et les buffers de flux et de messages, offrent des alternatives légères aux files d'attente dans de nombreux scénarios de conception courants.

Sémaphores et mutex

Le noyau FreeRTOS fournit des sémaphores binaires, des sémaphores de comptabilisation et des mutex, à des fins d'exclusion mutuelle et de synchronisation.

Les sémaphores binaires ne peuvent avoir que deux valeurs. Ils sont un bon choix pour la mise en œuvre de la synchronisation (soit entre les tâches, soit entre les tâches et une interruption). Les sémaphores de comptabilisation acceptent plus de deux valeurs. Ils permettent à plusieurs tâches de partager des ressources ou d'effectuer des opérations de synchronisation plus complexes.

Les mutex sont des sémaphores binaires qui incluent un mécanisme d'héritage de priorité. Cela signifie que si une tâche à la priorité élevée est bloquée lors de la tentative d'obtention d'un mutex détenu par une tâche de moindre priorité, la priorité de la tâche contenant le jeton est temporairement élevée à celle de la tâche de blocage. Ce mécanisme est conçu pour s'assurer que la tâche à la priorité la plus élevée est conservée dans l'état Blocked le moins de temps possible, afin de minimiser l'inversion de priorité qui s'est produite.

irect-to-task Notifications 3D

Les notifications aux tâches permettent d'interagir avec d'autres tâches et de se synchroniser avec les routines ISR (Interrupt Service Routine), sans qu'un objet de communication distinct comme un sémaphore soit nécessaire. Chaque tâche RTOS a une valeur de notification 32 bits utilisée pour stocker le contenu de la notification, le cas échéant. Une notification de tâche RTOS est un événement envoyé directement à une tâche qui peut débloquer la tâche de réception et, éventuellement, mettre à jour la valeur de notification de la tâche de réception.

Les notifications aux tâches RTOS peuvent être utilisées comme une alternative légère et plus rapide aux sémaphores binaires et aux sémaphores de comptabilisation, ainsi que, dans certains cas, aux files d'attente. Les notifications aux tâches présentent des avantages de vitesse et de plan RAM par rapport à d'autres fonctions qui peuvent être utilisées pour exécuter des fonctionnalités équivalentes. Cependant, les notifications de tâche ne peuvent être utilisées que lorsqu'il n'y a qu'une seule tâche qui puisse être le destinataire de l'événement.

Tampons mémoire de flux

Les tampons mémoire de flux autorisent qu'un flux d'octets soit transmis d'une routine ISR à une tâche, ou d'une tâche à une autre. Un flux d'octets peut avoir une longueur arbitraire et n'a pas nécessairement de début ou de fin. N'importe quel nombre d'octets peut être écrit en une seule fois et n'importe quel nombre d'octets être lu en une seule fois. Vous activez la fonctionnalité de tampon de flux en incluant le fichier source stream_buffer.c dans votre projet.

Les tampons mémoire de flux présument qu'il n'y a qu'une seule tâche ou interruption qui écrit dans le tampon mémoire (l'auteur) et qu'une seule tâche ou interruption lit à partir du tampon mémoire (le lecteur). Le fait que l'auteur et le lecteur soient des tâches ou des routines ISR différentes présente toutes les conditions de sécurité, contrairement au fait d'avoir plusieurs auteurs ou lecteurs.

L'implémentation de la mémoire tampon de flux utilise direct-to-task des notifications. Par conséquent, l'appel d'une API de tampon mémoire de flux qui place la tâche appelante dans l'état Blocked peut changer l'état de notification et la valeur de la tâche appelante.

Envoi de données

xStreamBufferSend() est utilisée pour envoyer les données au tampon mémoire de flux d'une tâche. xStreamBufferSendFromISR() est utilisée pour envoyer les données à un tampon mémoire de flux d'une routine ISR.

xStreamBufferSend() autorise la spécification d'une durée de blocage. Si la fonction xStreamBufferSend() est appelée avec une durée de blocage différente de zéro pour écrire sur un tampon mémoire de flux et que le tampon mémoire est plein, la tâche est placée dans l'état Blocked jusqu'à ce que l'espace devienne disponible ou que la durée de blocage expire.

sbSEND_COMPLETED() et sbSEND_COMPLETED_FROM_ISR() sont des macros qui sont appelées (en interne par l'API FreeRTOS) lorsque les données sont écrites dans un tampon mémoire de flux. Cela permet d’accepter le handle du tampon mémoire de flux qui a été mis à jour. Ces deux macros vérifient s'il existe une tâche bloquée dans le tampon mémoire de flux en attente de données et, si tel est le cas, suppriment la tâche de l'état Blocked.

Vous pouvez modifier ce comportement par défaut en fournissant votre propre implémentation de sbSEND_COMPLETED() dans FreeRTOSConfig.h. C'est utile quand un tampon mémoire de flux est utilisé pour transmettre les données entre les noyaux sur un processeur multicœur. Dans ce scénario, sbSEND_COMPLETED() peut être mis en œuvre pour générer une interruption dans l'autre cœur de l'UC et la routine ISR peut ensuite utiliser l'API xStreamBufferSendCompletedFromISR()pour contrôler et, si nécessaire débloquer, une tâche en attente de données.

Réception de données

xStreamBufferReceive() permet de lire les données à partir du tampon mémoire de flux d'une tâche. xStreamBufferReceiveFromISR() permet de lire les données à partir du tampon mémoire de flux d'une routine ISR.

xStreamBufferReceive() autorise la spécification d'une durée de blocage. Si la fonction xStreamBufferReceive() est appelée avec une durée de blocage différente de zéro pour lire depuis un tampon mémoire de flux et que le tampon est vide, la tâche est placée dans l'état Blocked jusqu'à ce qu'une quantité de données spécifiée devienne disponible dans le tampon mémoire de flux ou que la durée de blocage expire.

La quantité de données qui doivent être dans le tampon mémoire de flux avant qu'une tâche ne soit débloquée est appelée niveau de déclenchement du tampon mémoire de flux. Une tâche bloquée avec un niveau déclencheur égal à 10 est débloquée lorsqu'au moins 10 octets sont écrits dans le tampon mémoire ou que la durée de blocage de la tâche expire. Si la durée de blocage d'une tâche en lecture expire avant que le niveau déclencheur ne soit atteint, la tâche reçoit toutes les données écrites sur le tampon mémoire. Le niveau déclencheur d'une tâche doit être défini sur une valeur comprise entre 1 et la taille du tampon mémoire de flux. Le niveau déclencheur d'un tampon mémoire de flux est défini quand xStreamBufferCreate() est appelé. Il peut être modifié en appelant xStreamBufferSetTriggerLevel().

sbRECEIVE_COMPLETED() et sbRECEIVE_COMPLETED_FROM_ISR() sont des macros qui sont appelées (en interne par l'API FreeRTOS) lorsque les données sont lues à partir d'un tampon de flux. Les macros vérifient s'il existe une tâche bloquée sur le tampon de flux en attente d'espace disponible au sein du tampon mémoire et, si tel est le cas, suppriment la tâche de l'état Blocked. Vous pouvez modifier le comportement par défaut de sbRECEIVE_COMPLETED() en fournissant une autre implémentation dans FreeRTOSConfig.h.

Tampons de messages

Les tampons mémoire de messages permettent que des messages discrets de longueur variable soient transmis d'une routine ISR à une tâche, ou d'une tâche à une autre. Par exemple, les messages de longueur 10, 20 et 123 octets peuvent être tous écrits dans le même tampon mémoire de messages, ainsi qu'y être lus. Un message de 10 octets peut uniquement être lu sous la forme d'un message de 10 octets, et pas par octets individuels. Les tampons mémoire de messages sont conçus sur l’implémentation de tampons mémoire du flux. Vous pouvez activer la fonctionnalité de tampons mémoire de messages en incluant la source stream_buffer.c dans votre projet.

Les tampons mémoire de messages présument qu'il n'y a qu'une seule tâche ou interruption qui écrit dans le tampon (l'auteur) et qu'une seule tâche ou interruption lit à partir de la mémoire tampon (le lecteur). Le fait que l'auteur et le lecteur soient des tâches ou des routines ISR différentes présente toutes les conditions de sécurité, contrairement au fait d'avoir plusieurs auteurs ou lecteurs.

L'implémentation de la mémoire tampon de messages utilise direct-to-task des notifications. Par conséquent, l'appel d'une API de tampon mémoire de flux qui place la tâche appelante dans l'état Blocked peut changer l'état de notification et la valeur de la tâche appelante.

Pour permettre aux tampons mémoire de messages de gérer les messages de taille variable, la longueur de chaque message est écrite dans le tampon mémoire de messages avant le message lui-même. La longueur est stockée dans une variable de type size_t, soit généralement 4 octets sur une architecture 32 bits. Par conséquent, écrire un message de 10 octets dans un tampon de messages consomme réellement 14 octets d'espace tampon. De même, écrire un message de 100 octets dans un tampon mémoire de messages consomme réellement 104 octets d'espace tampon.

Envoi de données

xMessageBufferSend() permet d'envoyer des données à un tampon mémoire de messages depuis une tâche. xMessageBufferSendFromISR() permet d'envoyer les données à un tampon de messages depuis une routine ISR.

xMessageBufferSend() autorise la spécification d'une durée de blocage. Si la fonction xMessageBufferSend() est appelée avec une durée de blocage différente de zéro pour écrire sur un tampon mémoire de messages et que le tampon mémoire est plein, la tâche est placée dans l'état Blocked jusqu'à ce que l'espace devienne disponible dans le tampon mémoire de messages ou que la durée de blocage expire.

sbSEND_COMPLETED() et sbSEND_COMPLETED_FROM_ISR() sont des macros qui sont appelées (en interne par l'API FreeRTOS) lorsque les données sont écrites dans un tampon mémoire de flux. Cela permet d’accepter un paramètre unique, qui est le handle du tampon mémoire de flux qui a été mis à jour. Ces deux macros vérifient s'il existe une tâche bloquée dans le tampon mémoire de flux en attente de données et, si tel est le cas, suppriment la tâche de l'état Blocked.

Vous pouvez modifier ce comportement par défaut en fournissant votre propre implémentation de sbSEND_COMPLETED() dans FreeRTOSConfig.h. C'est utile quand un tampon mémoire de flux est utilisé pour transmettre les données entre les noyaux sur un processeur multicœur. Dans ce scénario, sbSEND_COMPLETED() peut être mis en œuvre pour générer une interruption dans l'autre cœur de l'UC et la routine ISR peut ensuite utiliser l'API xStreamBufferSendCompletedFromISR()pour contrôler et, si nécessaire débloquer, une tâche qui était en attente de données.

Réception de données

xMessageBufferReceive() permet de lire les données à partir d'un tampon mémoire de messages d'une tâche. xMessageBufferReceiveFromISR() permet de lire les données à partir d'un tampon mémoire de messages d'une routine ISR. xMessageBufferReceive() autorise la spécification d'une durée de blocage. Si la fonction xMessageBufferReceive() est appelée avec une durée de blocage différente de zéro pour lire depuis un tampon mémoire de messages et que le tampon est vide, la tâche est placée dans l'état Blocked jusqu'à ce que les données deviennent disponibles ou que la durée de blocage expire.

sbRECEIVE_COMPLETED() et sbRECEIVE_COMPLETED_FROM_ISR() sont des macros qui sont appelées (en interne par l'API FreeRTOS) lorsque les données sont lues à partir d'un tampon de flux. Les macros vérifient s'il existe une tâche bloquée sur le tampon de flux en attente d'espace disponible au sein du tampon mémoire et, si tel est le cas, suppriment la tâche de l'état Blocked. Vous pouvez modifier le comportement par défaut de sbRECEIVE_COMPLETED() en fournissant une autre implémentation dans FreeRTOSConfig.h.

Support du multitraitement symétrique (SMP)

La prise en charge du protocole SMP dans le noyau FreeRTOS permet à une instance du noyau FreeRTOS de planifier des tâches sur plusieurs cœurs de processeur identiques. Les architectures de base doivent être identiques et partager la même mémoire.

Modification d'applications pour utiliser le noyau FreeRTOS-SMP

L'API FreeRTOS reste sensiblement la même entre les versions monocœur et SMP, à l'exception de ces API supplémentaires. Par conséquent, une application écrite pour la version monocœur de FreeRTOS doit être compilée avec la version SMP avec un minimum d'effort, voire aucun effort. Cependant, certains problèmes fonctionnels peuvent survenir, car certaines hypothèses qui étaient vraies pour les applications monocœurs peuvent ne plus être vraies pour les applications multicœurs.

Une hypothèse courante est qu'une tâche de moindre priorité ne peut pas être exécutée alors qu'une tâche de priorité plus élevée est en cours d'exécution. Cela était vrai sur un système monocœur, mais cela ne l'est plus pour les systèmes multicœurs, car plusieurs tâches peuvent être exécutées simultanément. Si l'application s'appuie sur des priorités de tâches relatives pour fournir une exclusion mutuelle, elle peut observer des résultats inattendus dans un environnement multicœur.

Une autre hypothèse courante est que les ISR ne peuvent pas s'exécuter simultanément les uns avec les autres ou avec d'autres tâches. Ce n'est plus le cas dans un environnement multicœur. Le rédacteur de l'application doit garantir une exclusion mutuelle appropriée lors de l'accès aux données partagées entre les tâches et les ISR.

Minuteurs de logiciels

Un minuteur logiciel permet qu'une fonction soit exécutée à un moment défini dans le futur. La fonction exécutée par le minuteur est appelée fonction de rappel du minuteur. Le temps entre le démarrage d'un minuteur et sa fonction de rappel en cours d'exécution est appelé période du minuteur. Le noyau FreeRTOS offre une implémentation efficace du minuteur logiciel pour les raisons suivantes :

  • Il n'exécute pas les fonctions de rappel à partir d'un contexte d'interruption.

  • Il ne consomme pas de temps de traitement, sauf si un minuteur a réellement expiré.

  • Il n'ajoute pas de surcharge de traitement à l'interruption du tic-tac.

  • Il ne parcourt aucune structure de liste de liens tandis que les interruptions sont désactivées.

Prise en charge d'une alimentation basse

Comme la plupart des systèmes d'exploitation embarqués, le noyau FreeRTOS utilise un minuteur matériel pour générer des interruptions régulières de tics, qui permettent de mesurer le temps. L'économie d'alimentation des implémentations de minuteur matériel classiques est limitée par la nécessité de quitter régulièrement l'état d'alimentation faible pour traiter les interruptions du tic-tac, et d'y accéder à nouveau. Si la fréquence des interruptions de tics est trop élevée, l'énergie et le temps nécessaires pour entrer dans un état d'alimentation basse et le quitter pour chaque tic-tac l'emportent sur tout gain potentiel d'économie d'alimentation, à l'exception des modes d'économie d'alimentation les plus légers.

Pour résoudre cette restriction, FreeRTOS inclut un mode minuteur sans tic-tac pour les applications à alimentation faible. Le mode inactif FreeRTOS sans tic-tac arrête l'interruption régulière du tic-tac pendant les périodes d'inactivité (périodes où il n'y a pas de tâches d'application capables de s'exécuter), puis apporte une correction à la valeur du nombre de tics-tacs RTOS quand l'interruption du tic-tac est redémarrée. L'arrêt de l'interruption du tic-tac permet au microcontrôleur de demeurer dans un état d'économie d'alimentation profonde jusqu'à ce qu'une interruption se produise ou qu'il soit temps pour le noyau RTOS de faire passer une tâche à l'état Ready.

Configuration du noyau

Vous pouvez configurer le noyau FreeRTOS pour une carte et une application spécifiques avec le fichier d'en-tête FreeRTOSConfig.h. Chaque application développée sur le noyau doit avoir un fichier d'en-tête FreeRTOSConfig.h dans son chemin d'inclusion de préprocesseur. FreeRTOSConfig.h est propre à l'application et doit être placé dans un répertoire d'applications, et non pas dans l'un des répertoires de code source du noyau FreeRTOS.

LesFreeRTOSConfig.h fichiers des applications de démonstration et de test de FreeRTOS se trouvent à l'adressefreertos/vendors/vendor/boards/board/aws_demos/config_files/FreeRTOSConfig.h etfreertos/vendors/vendor/boards/board/aws_tests/config_files/FreeRTOSConfig.h.

Pour obtenir la liste des paramètres de configuration disponibles à spécifier dans FreeRTOSConfig.h, consultez FreeRTOS.org.