Vous êtes sur la page 1sur 31

SYSTEMES TEMPS REEL

Travaux Pratiques

SOMMAIRE

1. MODE COOPERATIF

1.1. Interface de communication


1.2. Mode coopératif
1.3. État bloqué
1.4. Tâche idle

2. MODE PREEMPTIF ET GESTION MEMOIRE

2.1. Mode préemptif


2.2. Gestion mémoire

3. QUEUE DE MESSAGE ET SEMAPHORE

3.1. Queue de Message


3.2. Timeout
3.3. Section critique
3.4. Sémaphore
3.5. Bibliothèque UART avec appels système

SEQUENCEMENT

Travail préparatoire : Installation des outils de développement


Séance 1 : MODE COOPERATIF

Travail préparatoire : MODE COOPERATIF


Séance 2 : MODE COOPERATIF / MODE PREEMPTIF ET GESTION MEMOIRE

Travail préparatoire : MODE PREEMPTIF ET GESTION MEMOIRE


Séance 3 : MODE PREEMPTIF ET GESTION MEMOIRE / QUEUE DE MESSAGE ET SEMAPHORE

Travail préparatoire : QUEUE DE MESSAGE ET SEMAPHORE


Séance 4 : MODE PREEMPTIF ET GESTION MEMOIRE / QUEUE DE MESSAGE ET SEMAPHORE

Travail préparatoire : Architecture système du projet RACE


Séance 5 : QUEUE DE MESSAGE ET SEMAPHORE

1
SYSTEMES TEMPS REEL
Travaux Pratiques

PREAMBULE

Cet enseignement est avant tout dédié à la compréhension des services logiciels proposés par
un système d'exploitation, notamment ceux dits temps réel (déterministes). Il devra amener aux portes
de la définition d'une architecture système logicielle répondant à un cahier des charges donné. Les
concepts seront illustrés sur la technologie FreeRTOS, solution open source du marché et leader à
notre époque sur le marché des RTOS (Real Time Operating System) dans l'embarqué .

PIC32MZ EF Starter Kit MCU PIC32MZ Architecture

La trame de TP sur Système Embarqué sera illustrée sur processeur MCU (Micro Controller
Unit) haut de gamme PIC32MZ (PIC32MZ2048EFH144) proposé par Microchip et porté sur le starter
kit, ou maquette de démarrage PIC32MZ EF Starter Kit.

Vue de dessus Vue de dessous


1:PIC32MZ2048EFH144 1: sonde de programmation JTAG
4: Connecteur mini USB (sonde JTAG de programmation)
9: Module USB/UART MCP2221 (connecté à l'UART2 du PIC32MZ)
9: Connecteur mini USB (communication série par UART virtualisée)

2
SYSTEMES TEMPS REEL
Travaux Pratiques

OUTILS DE DEVELOPPEMENT LOGICIEL

Installer les outils de développement et venir en séance avec vos machines personnelles (IDE
MPLABX, Framework de développement Harmony et toolchain C XC32). Les TP peuvent être réalisés
indifféremment sur plateforme Windows ou GNU\Linux.

 IDE MPLAB X (Environnement de Développement Intégré– dernière version):


http://www.microchip.com/mplabx

 Harmon Framework de développement (dernière version) :


https://www.microchip.com/mplab/mplab-harmony

 Toolchain C XC32 (Free mode – dernière version) :


http://www.microchip.com/xc32

La trame de TP n'utilisant comme interface que le module UART 2 du PIC32MZ en mode


transmission, il est alors possible de valider la compilation et le fonctionnement de vos programmes
depuis chez vous en mode Simulateur. Voici la procédure :

 Ouvrir les propriétés du projet : fenêtre Project > clic droit sur le nom du projet > Properties

 Sélectionner le simulateur : Hardware Tool > Simulator

 Configurer le simulateur : Simulator > Options Categories [UART2 IO Options] > Enable
UART2 IO

 Sauvegarder les propriétés du projet : Apply > OK

 Ouvrir une console de debug : Window > Debugging > Debug Console

 Compiler et exécuter le projet en mode Debug : Debug > Debug Main Project

 Visualiser la console de sortie : sélectionner la nouvelle fenêtre de sortie "UART 2 Output" et


Hop c'est magique (normalement) !

3
SYSTEMES TEMPS REEL
Travaux Pratiques

1. MODE COOPERATIF

Travail préparatoire

• 1. (0,5pt) Quels états peu prendre une tâche sous FreeRTOS ?

• 2. (1,5pts) Ces états sont-ils les mêmes quelque soit l'OS ou le RTOS utilisé ? Donner un
exemple pour un autre RTOS.

• 3. (0,5pt) Que se passe-t-il sous FreeRTOS lorsqu'aucune tâche, précédemment créée via
xTaskCreate(), ne s'exécute (état running) ?

• 4. (0,5pt) Qu'est-ce qu'un TCB ?

• 5. (1pt) Que trouve-t-on en général dans un TCB (prendre l'exemple de FreeRTOS) ?

4
SYSTEMES TEMPS REEL
Travaux Pratiques

1. MODE COOPERATIF

Travail en séance
1.1. Interface de communication

Avant de commencer à découvrir FreeRTOS, un micro-noyau temps réel libre et open source,
nous allons devoir mettre en place une interface de communication avec l'ordinateur de
développement (ordinateur host). Même à notre époque, l'une des premières interfaces mise en
œuvre lors de développement sur MCU est une communication série asynchrone via UART. La rapidité
de mise en œuvre, la simplicité du protocole et sa robustesse en font sa force. Cet exercice ayant déjà
été réalisé en première année à l'école sur architecture PIC18 de Microchip, le programme de
configuration et de gestion de l'UART sur architecture PIC32MZ de Microchip vous est donné. Il vous
est seulement demandé d'en comprendre le fonctionnement tout en réalisant quelques tests après
compilation.

• Créer un projet uart dans le répertoire disco/bsp/uart/pjct/.Ce projet doit inclure les fichiers
sources uart.c et main.c présents dans disco/bsp/uart/src/ et faisant référence au fichier d'en-
tête uart.h présent dans disco/bsp/uart/include/. Sinon, inclure la bibliothèque statique bsplib.a
à la place du fichier uart.c. Il vous sera nécessaire de spécifier à la chaîne de compilation les
chemins suivants afin de permettre au pré-processeur de trouver le headers nécessaires :
▪ C:\<your_path>\disco\bsp
▪ C:\Program Files (x86)\Microchip\harmony\v<your_version>\framework
▪ ou C:\microchip\harmony\v<your_version>\framework

• Ouvrir un terminal asynchrone de communication côté ordinateur (TeraTerm, PuTTY, minicom,


kermit …) et s'assurer de sa bonne configuration afin d'analyser les données envoyées depuis la
maquette.

• Compiler et interpréter le fonctionnement du programme. Ne pas hésiter à modifier le fichier


main.c afin de bien comprendre le fonctionnement de l'API de gestion de l'UART.

• Pour information, lors de développements sur processeurs pour l'embarqué, nous utilisons des
convertisseurs USB-serial afin de communiquer avec l'ordinateur de développement. Ces
interfaces nécessitent l'installation de drivers sous Windows et sont nativement reconnus sous
Linux en apparaissant à la racine dans le répertoire /dev sous le nom ttyXXXX (teletypewriter
terminal) :

5
SYSTEMES TEMPS REEL
Travaux Pratiques

1.2. mode coopératif

Nous allons maintenant découvrir pas à pas les principaux services proposés par FreeRTOS.
Dans un premier temps, intéressons-nous au mode coopératif, très peu utilisé pour des problèmes de
robustesse et d'égalité de partage de temps CPU, problèmes que nous illustrerons dès le premier
exercice. En mode coopératif, chaque tâche doit explicitement permettre à une autre tâche de
s'exécuter en effectuant un appel système, sans quoi, aucune autre tâche ne pourra prendre la main.
Nous allons dans un premier temps créer 3 tâches de même priorité. Chaque tâche enverra une chaîne
de caractères à l'ordinateur par liaison série.

• Créer un projet MPLABX coop dans le répertoire disco/apps/coop/pjct. Ce projet doit inclure les
fichiers sources applicatifs disco/bsp/uart/src/uart.c (ou bilbiothèque bsplib.a), ainsi que utask.c
(user tasks), ktrap.c (kernel trap) et main.c présents dans le répertoire disco/apps/coop/src .
Bien observer le contenu des répertoires du projet coop avant de créer le projet.

• Inclure à votre projet les sources de FreeRTOS sans oublier de configurer vos chaînes de
compilation C et ASM avec les chemins vers les différents répertoires contenant des fichiers
d'en-tête. S 'aider de l'annexe 4.

• La documentation du noyau est accessible en ligne sur le site officiel de la société proposant
FreeRTOS :

http://www.freertos.org/

6
SYSTEMES TEMPS REEL
Travaux Pratiques

• Créer 3 tâches de priorité 1. Les fonctions implémentant les tâches se nommeront task1(),
task2() et task3(). Chaque tâche ne fera qu'envoyer une chaîne de caractères à l'ordinateur puis
attendre un délai de quelques centaines de millisecondes via une temporisation logicielle avant
de répéter l'opération. Prenons l'exemple de la tâche 1 :

uart_puts("\r\n1111111");

// wait few 100ms


for (int i=0; i<4000000; i++);

• Compléter, compiler et interpréter le fonctionnement du programme en visualisant les données


reçues côté ordinateur.

Pour ce premier exercice, l'analyse du fonctionnement du programme est donnée. Ce travail


sera bien entendu à votre charge pour la totalité des exercices qui suivent :

Les traits pleins apparaissant sur les chronogrammes représentent le code en cours d'exécution
par le CPU. Nous constatons que nous sommes bloqués dans la tâche 3. Du coup, trois questions se
soulèvent :

• Pourquoi sommes-nous bloqués ? tout simplement car en mode coopératif, la tâche doit
appeler elle même l'ordonnanceur ou une fonction système réalisant un appel du
scheduler.

• Pourquoi dans la tâche 3 ? Avec FreeRTOS, au démarrage si plusieurs tâches de même


priorité sont susceptibles de prendre la main, c'est la dernière créée qui sera la
première à démarrer. Cette politique est différente en fonction de l'OS rencontré.

• Dans quel état se trouvent les tâches 1 et 2 ? elles se trouvent à l'état prêt (ready). Elles
sont toutes les deux prêtes à prendre la main si la tâche 3 le leur donne.

7
SYSTEMES TEMPS REEL
Travaux Pratiques

• Nous allons maintenant forcer des commutations de contexte en appelant l'ordonnanceur.


Dans chaque tâche, à la suite de la temporisation logicielle, appelez la fonction suivante :

taskYIELD();

Vous constaterez que le noyau donne la main à chaque tâche à tour de rôle. Beaucoup d'OS
temps réel légers travaillent ainsi, il s'agit de la technique dîtes du round-robin qui suit le principe de
fonctionnement d'un tourniquet. Chaque tâches de même priorité prêtes ou en court d'exécution
prendra la main à tour de rôle. Le principal avantage de cette technique est de ne nécessiter qu'une
intelligence très réduite au niveau du code du kernel.

Nous venons d'illustrer le principe de la coopération entre tâches ainsi que le principal
problème amené si une boucle infinie (bug) intervient dans le code d'une tâche. Les tâches bloquées
ou prêtes ne peuvent plus prendre la main et votre application tombe !

• Pour information, durant la totalité de la trame de TP, nous étudierons le fonctionnement de


nos programmes à l'aide de chronogrammes comme ci-dessus. Sachez néanmoins qu'il existe
des outils dédiés, souvent propriétaires et donc payant permettant ce type d'analyses. Prenez
quelques minutes pour visualiser la vidéo présentant les outils de trace proposés par FreeRTOS
(outils payant en fonctions des services demandés) :

http://www.youtube.com/watch?feature=player_embedded&v=WTNc1PwoMG4

8
SYSTEMES TEMPS REEL
Travaux Pratiques

1.3. État bloqué

Intéressons-nous à la fonction bloquante vTaskDelay(). Afin de pouvoir utiliser cette fonction,


ne pas oublier de mettre à 1 la macro INCLUDE_vTaskDelay présente dans le fichier d'en-tête
FreeRTOSConfig.h.

• Compléter le programme précédent en remplaçant dans la tâche 1 la temporisation logicielle


et taskYIELD() par :

uart_puts("\r\n1111111");
vTaskDelay(3000);

• Compléter le chronogramme ci-dessous en étant prudent aux quelques pièges pouvant


apparaître. Pour information, 3000 signifie 3000 ticks donc 3 secondes dans notre cas (1 tick =
1ms cf. FreeRTOSConfig.h). Préciser à chaque fois les appels des fonctions taskYIELD() et
vTaskDelay(3000) ainsi que l'état pris par chaque tâche (R = ready et B = blocked) :

• Remplacer maintenant les temporisations logicielles et taskYIELD() par vTaskDelay(3000) dans


chaque tâche. Compléter le chronogramme suivant et préciser l'état pris par chaque tâche (R =
ready et B = blocked) :

• Quel code s'exécute lorsque nous nous trouvons dans aucune des 3 tâches ?

9
SYSTEMES TEMPS REEL
Travaux Pratiques

1.4. Tâche idle

Intéressons-nous à la tâche Idle et découvrons comment la détourner afin d'y insérer du code
utilisateur. Par défaut en mode coopératif la tâche Idle ne fait que forcer des commutations de
contexte en appelant la fonction taskYIELD().

• Mettre à 1 la macro configUSE_IDLE_HOOK présente dans le fichier d'en-tête


FreeRTOSConfig.h.

• Créer alors une fonction (et non une tâche !) nommée vApplicationIdleHook(). Attention ce
nom est imposé par le système. Cette fonction ne fera qu'envoyer une chaîne de caractères :

/**
* @fn void vApplicationIdleHook( void )
* @brief function called by idle task
*/
void vApplicationIdleHook( void ){
uart_putc('i');
}

• Chercher le code de la tâche Idle dans les sources de FreeRTOS

• Tester votre code et compléter le chronogramme ci-dessous :

• Proposer des cas d'applications et exemples d'utilisation de la tâche Idle. Ne pas hésiter à
s'aider du web et d'exemples de détournement de la tâche Idle sur d'autres noyaux temps réel.

10
SYSTEMES TEMPS REEL
Travaux Pratiques

2. MODE PREEMPTIF ET GESTION MEMOIRE

Travail préparatoire

• 1. (0,5pt) Quels sont les inconvénients et avantages d'un OS coopératif (aidez-vous du Web) ?

• 2. (0,5pt) Quels sont les inconvénients et avantages d'un OS préemptif (aidez-vous du Web) ?

• 3. (1pt) Que trouve-t-on sur le Tas ?

• 4. (1,5pt) Les stratégies de gestion du tas par FreeRTOS sont implémentées dans les fichiers
heap_1.c, heap_2.c, heap_3.c, heap_4.c et heap_5.c présents dans le répertoire
/disco/rtos/FreeRTOS/Source/portable/MemMang de notre arborescence de TP.

• Quelles sont les différences entre les stratégies utilisant heap_1.c ou heap_2.c ?
• Quelles sont les différences entre les stratégies utilisant heap_2.c ou heap_3.c ?

• 5. (1pt) Qu'est-ce qu'une pile ou stack et que trouve-t-on sur la pile ?

• 6. (1,5pt) Qu'elle est la taille par défaut de la pile de la tâche Idle dans le cadre de notre trame
de TP ? Expliquez votre démarche pour répondre à cette question.

11
SYSTEMES TEMPS REEL
Travaux Pratiques

2. MODE PREEMPTIF ET GESTION MEMOIRE

Travail en séance
2.1. mode préemptif

A partir de maintenant et jusqu'à la fin de la trame de TP nous travaillerons exclusivement en


mode préemptif (macro configUSE_PREMPTION à 1 dans le fichier FreeRTOSConfig.h). En mode
préemptif, le scheduler prend périodiquement la main, interrompant ainsi une tâche en cours
d'exécution, puis force un ré-ordonnancement. Cette périodicité se nomme tick (timer clock) et est
configurable sous FreeRTOS à travers la macro configTICK_RATE_HZ présente dans FreeRTOSConfig.h.

• Réaliser une copie physique des fichiers du projet coop vers le projet preempt. Créer un projet
MPLABX preempt dans le répertoire disco/apps/preempt/pjct. Ce projet doit inclure les fichiers
sources applicatifs disco/bsp/uart/src/uart.c (ou bibliothèque bsplib.a), ainsi que utask.c (user
tasks), ktrap.c (kernel trap) et main.c présents (après copie) dans le répertoire
disco/apps/preempt/src .

• Inclure à votre projet les sources de FreeRTOS sans oublier de configurer vos chaînes de
compilation C et ASM avec les chemins vers les différents répertoires contenant des fichiers
d'en-tête. S'aider de l'annexe 4.

• Créer 3 tâches, une de priorité 2 et deux de priorité 1. Les fonctions implémentant les tâches
se nommeront respectivement task1(), task2() et task3(). Chaque tâche appellera une fonction
bloquante puis ne fera qu'envoyer une chaîne de caractères à l'ordinateur.

• Prenons l'exemple des tâches 2 et 3 de priorité 1 (exemple de la tâche 2) :

uart_puts("\r\n2222222");
vTaskDelay(1000);

• La tâche 1 (priorité 2) sera bloquée quant-à elle durant 3000 ticks :

uart_puts("\r\n1111111");
vTaskDelay(3000);

12
SYSTEMES TEMPS REEL
Travaux Pratiques

• Compléter, compiler et interpréter le fonctionnement du programme en visualisant les données


reçues côté ordinateur. Le comportement du programme peut sembler étrange dans un
premier temps, mais s'explique bien entendu très bien (entrelacement de caractères).
Expliquer le fonctionnement du programme.

• Compléter le chronogramme suivant et préciser l'état pris par chaque tâche (R = ready et B =
blocked). Attention aux pièges :

• Dans notre cas, la tâche 1 est-elle périodique ?

• De quoi dépend la périodicité d'une tâche appelant la fonction vTaskDelay()?

• FreeRTOS propose la fonction xTaskGetTickCount() permettant de récupérer la valeur courante


du tick. Récupérer à chaque réveil de la tâche 1 cette valeur, puis l'envoyer à l'ordinateur
(s'aider de la fonction standard sprintf) :

uart_puts("\r\n1111111 – tick : ");


// read and send by UART current tick value

• Utiliser maintenant la fonction vTaskDelayUntil() puis interpréter le résultat obtenu.

13
SYSTEMES TEMPS REEL
Travaux Pratiques

2.2. Gestion mémoire

La partie qui suit est extrêmement importante et sujette à énormément de bugs et mauvais
développement en milieu industriel, notamment lorsque nous travaillons sur de petits exécutifs temps
réel comme FreeRTOS. Problématique différente sur OS évolué (GNU\Linux, Android …) et processeur
avec MMU (Memory Managment Unit). Sur RTOS, le développeur doit avoir une très bonne gestion et
maîtrise des ressources mémoire utilisées par la chaîne de compilation et le système. Prenons un petit
programme d'exemple et observons le mapping mémoire de données du processeur.

int gbl;

/**
* @fn int main(void)
*/
void main(void){
int lclMain;
xTaskCreate(task, "task", 100, NULL, 1, NULL);
vTaskStartScheduler();
}

/**
* @fn void task(void *pvParameters)
*/
void task(void *pvParameters){
int lclTask;
}

• Représenter ci-contre un découpage du mapping mémoire en


faisant apparaître : tas de FreeRTOS, pile fonction main, pile de
la tâche, TCB de la tâche

• Que trouve-t-on dans le reste de la mémoire (hors zones


spécifiées dans la question précédente) ?

• Où si situe physiquement la chaîne de caractères ''task'' ?


Mettre à jour le schéma ci-contre.

• Où si situe physiquement la variable globale gbl ? Mettre à jour


le schéma ci-contre.

• Où si situe physiquement la variable locale lclMain ? Mettre à


jour le schéma ci-contre.

• Où si situe physiquement la variable locale lclTask ? Mettre à


jour le schéma ci-contre.

14
SYSTEMES TEMPS REEL
Travaux Pratiques

FreeRTOS propose une API de programmation permettant de détecter certaines exceptions du


noyau et d'appeler des fonctions de callback en cas d’occurrence. Le kernel permet notamment de
détecter certains stack overflow (débordement de pile) et heap overflow. Pour information, les stack
overflow font partis des bugs les plus répandus durant des développement sur STR et peuvent être
délicats à mettre au jour dans certains cas. Il vous est très très très fortement conseillé d'implémenter
ce type de détection durant vos phases de développement. Malheureusement, lorsque vous vous
trouvez dans l'une de ces fonctions de callback, il est déjà trop tard !

• Nous allons maintenant mettre en place les mécanismes de détection de débordement de pile.
Forcer à 1, 2 ou 3 la macro configCHECK_FOR_STACK_OVERFLOW présente dans le fichier
FreeRTOSConfig.h. En fonction de la valeur choisie différentes stratégies de détection de stack
overflow seront appliquées par le kernel :

http://www.freertos.org/Stacks-and-stack-overflow-checking.html

• Observer la fonction vApplicationStackOverflowHook présente dans le fichier ktrap.c (kernel


trap).

void vApplicationStackOverflowHook( xTaskHandle xTask, signed char *pcTaskName ){


uart_puts("\r\n*** Application error - ");
uart_puts(pcTaskName);
uart_puts(" stackoverflow ***");

/* it's a trap. Comment while(1) to force application software reset */


while(1);
PLIB_DEVCON_SystemUnlock(DEVCON_ID_0);
PLIB_RESET_SoftwareResetEnable(RESET_ID_0);
}

• Éditer la fonction suivante et l'appeler dans la tâche 1. Interpréter et illustrer le comportement


du programme sur le schéma ci-contre :

void growth( void ) {


vTaskDelay(1);
growth();
}

• Effectuer le même exercice avec la fonction suivante. Interpréter et


illustrer le comportement du programme sur le schéma ci-contre :

void growth( void ) {


return growth();
}

15
SYSTEMES TEMPS REEL
Travaux Pratiques

• Nous allons maintenant mettre en place les mécanismes de détection de débordement de tas.
Forcer à 1 la macro configUSE_MALLOC_FAILED_HOOK présente dans le fichier
FreeRTOSConfig.h. Observer la fonction vApplicationMallocFailedHook dans le fichier ktrap.c
(kernel trap).

void vApplicationMallocFailedHook( void )


{
uart_puts("\r\n*** Application error - heap overflow ***");

/* it's a trap. Comment while(1) to force application software reset */


while(1);
PLIB_DEVCON_SystemUnlock(DEVCON_ID_0);
PLIB_RESET_SoftwareResetEnable(RESET_ID_0);
}

• Éditer le code suivant dans la tâche 1. Nous allons créer une tâche depuis
la tâche 1 dont la pile dépasse la taille du tas. Interpréter et illustrer le
comportement du programme sur le schéma ci-contre :

// force heap allocation failed


xTaskCreate(task1, "mfailed",
configTOTAL_HEAP_SIZE,
NULL,
tskIDLE_PRIORITY + 2,
NULL);

16
SYSTEMES TEMPS REEL
Travaux Pratiques

3. QUEUE DE MESSAGE ET SEMAPHORE

Travail préparatoire

• 1. (2pts) Exemple de communication entre 3 tâches via queue de messages :

void prodTask1( void *pvParam ){ void prodTask2( void *pvParam ){ void consTask( void *pvParam ){
int x=1; int x=2; int y;
char sBuffer[20];
while(1){ while(1){
if (xQueueSend(xQueue, \ if (xQueueSend(xQueue, \ while(1){
&x, 0) == pdTRUE ){ &x, 0) == pdTRUE ){ xQueueReceive(xQueue, &y, \
x += 2; x += 2; portMAX_DELAY );
vTaskDelay( 1 ); vTaskDelay( 1 ); sprintf(sBuffer, ''%d'', y);
} } uart_puts(sBuffer) ;
} } }
} } }

L'application présentée ci-dessus met en œuvre 2 tâches producteur de priorité 1


(prodTask1() et prodTask2()) et une tâche consommateur de priorité 2 (consTask()) sachant que
le kernel fonctionne en mode préemptif. Représenter la séquence d'exécution des tâches à
partir du démarrage de l'application. Notez bien sur le chronogramme les instants clés ainsi que
les appels de fonction associés (R = ready et B = blocked) :

• 2. (0,5pt) Qu'observerait-on au niveau d'un terminal asynchrone côté ordinateur ?

17
SYSTEMES TEMPS REEL
Travaux Pratiques

• 3. (1,5pt) En utilisant 3 sémaphores binaires, proposez une solution permettant de chaîner


l'exécution de 3 tâches en respectant l'ordre suivant : task3, task1, task2, task3, task1, task2,
task3 ...

void task1( void *pvParam ){ void task2( void *pvParam ){ void task3( void *pvParam ){

while(1){ while(1){ while(1){

} } }
} } }

• 4. (0,5pt) Comment faire pour chaîner 6 tâches ?

• 5. (1,5pt) Compléter le chronogramme ci-dessous de la totalité des prises et ventes de


sémaphores sachant qu'avant le démarrage de l'ordonnanceur le sémaphore 3 (S3) a été vendu
une fois (Give) et les deux autres ont déjà été pris (Take). Ne pas oublier de représenter les
états de chaque tâche (R = ready et B = blocked) :

18
SYSTEMES TEMPS REEL
Travaux Pratiques

3. QUEUE DE MESSAGE ET SEMAPHORE

Travail en séance
3.1. Queue de message

Intéressons-nous maintenant aux outils de communication, synchronisation et protection


proposés par FreeRTOS. Dans cette partie nous allons nous attarder sur les files d'attente (ou queue de
message ou boîte aux lettres ou mail boxes selon le système utilisé) ainsi que sur les sémaphores, qui
ne sont qu'un dérivé sans message des files d'attente. D'ailleurs, sous FreeRTOS il n'existe aucun fichier
source propre aux sémaphores. Les sémaphores sont implémentés et utilisent les mêmes sources que
les files d'attente, seul un fichier d'en-tête existe permettant de wrapper l'API de gestion des
sémaphores vers celle pour le queue (simple skin).

• Réaliser une copie physique des fichiers du projet preempt vers le projet tools. Créer un projet
MPLABX tools dans le répertoire disco/apps/tools/pjct. Ce projet doit inclure les fichiers sources
applicatifs disco/bsp/uart/src/uart.c (ou bibliothèque bsplib.a), ainsi que utask.c (user tasks),
ktrap.c (kernel trap) et main.c présents (après copie) dans le répertoire disco/apps/tools/src .

• Inclure à votre projet les sources de FreeRTOS sans oublier de configurer vos chaînes de
compilation C et ASM avec les chemins vers les différents répertoires contenant des fichiers
d'en-tête. Ne pas hésiter à s'aider des documents d'annexe.

• Créer 3 tâches, la première, périodique de priorité 2 et les deux autres de priorité 1. Les
fonctions implémentant les tâches se nommeront respectivement task1(), task2() et task3().
Chaque tâche ne fera qu'envoyer une chaîne de caractères à l'ordinateur puis se bloquer.

• La tâche 3 (priorité 1)ne fera qu'envoyer un chaîne de caractères côté ordinateur puis se
bloquera durant 1s :

uart_puts("\r\n3333333");
vTaskDelay(1000);

• La tâche 1 (priorité 2) devra être périodique avec un périodicité de 5s. Elle devra
récupérer la valeur courante du tick (sans l'envoyer à l'ordinateur) puis la postera dans
une queue de message pour la tâche 2 (priorité 1). La fonction d'écriture dans la file
d'attente ne devra pas être bloquante.

uart_puts("\r\n1111111 – post tick");


/* post current tick value by message queue without timeout*/
vTaskDelayUntil( … );

19
SYSTEMES TEMPS REEL
Travaux Pratiques

• La tâche 2 devra être synchronisée avec la tâche 1 et récupérera la valeur courante du


tick avant de la renvoyer à l'ordinateur via liaison série. Le reste du temps, cette fonction
devra rester bloqué.

/* read message queue */


uart_puts(''\r\n2222222 – tick : '');
// print current tick value ...

• Ne pas oublier de créer un queue de message. A vous de fixer la taille de la file d'attente
ainsi que la taille de chaque élément.

• Compléter, compiler et interpréter le fonctionnement du programme en visualisant les données


reçues côté ordinateur.

• Compléter le chronogramme ci-dessous (R = ready et B = blocked). Attention aux pièges :

• Quel est la période d'exécution de la tâche 2 ?

Nous venons ici d'illustrer deux concepts importants dans le domaine des systèmes
d'exploitation, la synchronisation et la communication. La synchronisation permet notamment de
synchroniser l'exécution d'une tâche (par exemple implémentant un traitement long) suite à
l'exécution d'une autre tâche ou d'une ISR (traitement toujours court). La communication inter-tâche
consiste quant-à-elle en la capacité d’effectuer des échanges sécurisés d'informations entre
différentes tâches de l'application. Notion de partage d'information dans des cas d'usage avec
plusieurs écrivains et/ou plusieurs lecteurs. Un troisième concept important, est l'exclusion mutuelle.

20
SYSTEMES TEMPS REEL
Travaux Pratiques

3.2. Timeout

Certaines fonctions pour la gestion de queue de messages ou de sémaphores utilisent un


Timeout. La notion de timeout ne s'applique qu'à des appels système bloquant. Lorsqu'une tâche est
bloquée, celle-ci se réveillera (passage à l'état prêt) automatiquement après un laps de temps nommé
Timeout, même si l'événement attendu n'est pas arrivé (libération de sémaphore, écriture dans une
file d'attente ...).

• Forcer le réveil de la tâche 2 toutes les secondes en utilisant le Timeout associé à la fonction
xQueueReceive(). Après avoir testé la nature du réveil de la tâche, envoyer l'une des deux
chaînes de caractères suivantes :

• Réveil par lecture du message présent dans la queue :

uart_puts(''\r\n2222222 – tick : '');


// print current tick value ...

• Réveil par timeout :

uart_puts(''\r\n2222222 – timeout : '');

• Que se passe-t-il si nous forçons à 0 le Timeout d'une fonction bloquante ?

• Que se passe-t-il si nous forçons à portMAX_DELAY le Timeout d'une fonction bloquante (ainsi
que la macro INCLUDE_vTaskSuspend à 1) ? À quelle valeur théorique de timeout correspond
cet argument ?

• Peut-on trouver un timeout sur une fonction système non bloquante ?

21
SYSTEMES TEMPS REEL
Travaux Pratiques

3.3. Section Critique

Une section critique est une région de code pour laquelle nous devons garantir sa bonne
exécution et l'intégrité des données à sa sortie. Il s'agira le plus souvent de protéger une ressource
partagée (variable globale, accès à un périphérique …). Une section critique peut-être protégée par
différents outils système :

• Sémaphores
• Mutex (mutual exclusion)
• fonctions dédiées le plus souvent par masquage d'interruption

Vous avez normalement dû constater que les tâches 2 et 3 étant de même priorité, elles se
partagent à tour de rôle l'accès à l'UART. Nous allons donc protéger l'accès à ce périphérique. Cela
signifie qu'une tâche ayant pris cette ressource matérielle la gardera jusqu'à-ce qu'elle ait fini le
traitement en cours.

• Pour les tâches 2 et 3, placer la fonction d'envoi de données à l'ordinateur dans une section
critique. Utiliser pour cela les macros taskENTER_CRITICAL() et taskEXIT_CRITICAL().

• Compléter le chronogramme suivant et préciser l'état pris par chaque tâche (R = ready et B =
blocked). Attention aux pièges :

• Cette implémentation des sections critiques par FreeRTOS est assez dangereuse, notamment
dans l'exemple actuellement présenté, vu que les Ticks (générés par timer matériel) ne sont
plus vus de l'ordonnanceur pendant la durée d'exécution de la section (section longue en
temps d'exécution). Quel problème cela peut-il poser ?

Nous découvrirons par la suite une solution à base de sémaphores plus douce et surtout
interruptible par le kernel. Attention, une section critique doit-être la plus courte possible par principe.
Ne pas oublier qu'elle peut potentiellement être partagée avec d'autres tâches voir ISR's. Les solutions
utilisant le principe de masquage d'interruption sont donc à utiliser pour des sections de code très
courtes en temps d'exécution.

22
SYSTEMES TEMPS REEL
Travaux Pratiques

3.4. Sémaphore

Durant l'exercice précédent vous avez été amené à manipuler des sections critiques en utilisant
les fonctions taskENTER_CRITICAL() et taskEXIT_CRITICAL(). Cependant ces deux fonctions sont à
manier avec précaution car une région critique ainsi créée ne peut plus être préemptée par le noyau,
ni par les interruptions matérielles en dessous d'un certain niveau de priorité système (section non
interruptible). Ceci peut donc devenir très dangereux en cas de mauvaise programmation et en
fonction de la criticité de l'application. A l'aide de sémaphores, nous allons créer des sections critiques
pouvant être préempté par le système et également interrompues par les périphériques matériels.

Dans l'exercice qui suit, nous allons réécrire en partie la bibliothèque C de gestion de l'UART et
donc modifier le fichier source uart.c ainsi que le fichier d'en-tête uart.h.

• retirer les sections critiques précédemment insérées

• Retirer le fichier uart.c du projet MPLABX et le remplacer par le fichier


disco/bsp/kuart/src/kuart.c étant lui même dépendant du fichier
disco/bsp/kuart/include/kuart.h (kernel uart). Modifier dans un premier temps les sources voire
la configuration de la chaîne de compilation afin d'assurer une bonne compilation du projet
modifié.

• Créer un sémaphore binaire en choisissant un nom adapté au travail en cours puis modifier le
code source de la fonction uart_puts afin de s'assurer qu'une seule tâche à la fois puisse
prendre en main l'UART en transmission.

• Interpréter le fonctionnement du programme puis compléter le chronogramme suivant en


précisant l'état pris par chaque tâche (R = ready et B = blocked) :

• Sous FreeRTOS, qu'elle différence existe-t-il entre un sémaphore binaire et un mutex ?

• Dans notre cas, est-il plus intéressant d'utiliser un sémaphore binaire ou un mutex ? Justifier
votre réponse puis modifier si nécessaire le programme.

23
SYSTEMES TEMPS REEL
Travaux Pratiques

3.5. Bibliothèque UART avec appels système

Nous allons, dans cet ultime exercice, modifier plus profondément les sources de la librairie de
gestion du module UART. Le but étant d'obtenir une bibliothèque optimisée pour travailler avec
FreeRTOS (pas en vitesse d'exécution mais en robustesse et efficacité).

A titre indicatif, beaucoup d'applications font cohabiter bibliothèque réseau (ou stack réseau) et
système d'exploitation. Microchip propose par exemple une librairie réseau libre et open source
indépendante de tout OS, qui n'est donc pas optimisée pour travailler avec notre kernel. FreeRTOS
propose en revanche une librairie réseau implémentant des appels système qui est donc optimisée
pour cohabiter avec le noyau, néanmoins cette stack est un outil propriétaire. Observons rapidement
le coût de certains de ces services en 2014 (gratuit en 2018 depuis le rachat par AMAZON) :

• Supprimer dans les fichiers disco/bsp/kuart/src/kuart.c et disco/bsp/kuart/include/kuart.h toute


référence à l'utilisation du buffer circulaire permettant l'échange d'information entre l'ISR de
réception de l'UART et la fonction uart_getc.

• Modifier l'ISR ainsi que la fonction uart_getc en synchronisant par queue de message les réveils
de la fonction d'interruption avec l'appel de la fonction uart_getc. Chaque caractère reçu sera
posté dans la file d'attente et la fonction de réception de caractères implémentera donc un
appel système bloquant en vidant cette queue de message.

• Une fois ce travail réalisé, modifier le code de la tâche 3 de façon à réceptionner puis renvoyer
des chaînes de caractères envoyées depuis l'ordinateur. S'assurer du bon fonctionnement du
programme. Ultime test, envoyer un fichier texte depuis l'ordinateur et s'assurer de sa bonne
réception et renvoi par l'application embarquée. Le fichier est présent dans le répertoire
disco/bsp/kuart/test/rx-test-file1-2-3.txt. Sous TeraTerm, aller dans Fichier → Envoyer un
fichier...

uart_puts("\r\ndisco# ");
while (1) {
if (uart_gets(str_tmp, APP_BUFFER_SIZE, UART_ECHO)) {
uart_puts(str_tmp);
uart_puts("\r\ndisco# ");
}
}

24
SYSTEMES TEMPS REEL
Travaux Pratiques

25
SYSTEMES TEMPS REEL
Travaux Pratiques

26
SYSTEMES TEMPS REEL
Travaux Pratiques

27
SYSTEMES TEMPS REEL
Travaux Pratiques

28
SYSTEMES TEMPS REEL
Travaux Pratiques

29
SYSTEMES TEMPS REEL
Travaux Pratiques

30
SYSTEMES TEMPS REEL
Travaux Pratiques

31

Vous aimerez peut-être aussi