Accueil
Démo
Exercices formatifs
Travaux pratiques
Simulation libre
Sessions sauvegardées

TP4: Gestion des interruptions et ordonnancement de processus

Ce travail pratique vaut 4% de la note totale du cours. Comme le stipule le plan de cours, le travail doit être fait individuellement.

Objectifs

Ce travail pratique vise les objectifs suivants:

  1. Se familiariser avec les interruptions
  2. Comprendre le fonctionnement d'une table des vecteurs d'interruption;
  3. Mettre en place une table des vecteurs d'interruption rudimentaire en assembleur ARM;
  4. Implanter une routine de traitement d'interruption en assembleur ARM;
  5. Comprendre la sauvegarde de contexte effectuée par un système d'exploitation;

Préparation

Pour ce TP, nous allons mettre en place une interruption périodique de type FIQ dans le simulateur. Cela permettra de simuler l'utilisation d'une horloge interne au processeur, capable d'interrompre régulièrement son travail. Pour ce faire, cliquez sur le bouton « Configurations » en bas à gauche. Entrez les informations suivantes:

  • Cochez la case « Activer »;
  • Sélectionnez le type « FIQ »;
  • Entrez « 90 » dans la case « cycles »;
  • Entrez « 250 » dans la case « cycles (premier) »;

Une fois ces étapes complétées, vous devriez obtenir la configuration suivante:

Cliquez sur le « x » en haut à droite du panneau de configuration pour retourner au simulateur.

Étapes

Ce TP s'effectue en deux étapes: écrire du code (pour 75% de la note du TP) et répondre à des questions (pour 25% de la note).

Étape 1: code (75%)

Premièrement, il vous faudra coder un système d'exploitation (très) rudimentaire, capable de gérer l'exécution alternative de deux processus distincts, sans que ces derniers ne soient affectés. Pour ce faire, le processeur génère périodiquement une interruption, indépendamment de ce que font les processus. Cette interruption est gérée par le système d'exploitation, qui peut alors décider d'arrêter temporairement un processus pour laisser la place à un autre. Ce système est qualifié de préemptif, puisqu'il peut interrompre un processus à n'importe quel moment, même si ce dernier est dans une boucle infinie. Nous verrons plus en détail les notions reliées à l'ordonnancement des processus dans la suite du cours, mais sachez pour l'instant que cette gestion des processus est tout à fait similaire à ce que fait votre ordinateur lorsque plusieurs programmes requièrent simultanément du temps de calcul.

Vous devez implémenter trois sections de code distinctes :

  1. La table des vecteurs d'interruption;
  2. Le code initialisant le système et les processus;
  3. La routine d'interruption permettant au système de changer le processus en cours d'exécution tout en sauvegardant son contexte.

Vous trouverez plus d'informations sur chacune de ces sections dans la section détails ci-bas.

Lorsque vous avez terminé, téléchargez tout d'abord votre fichier source.txt sur votre ordinateur grâce au bouton «télécharger» dans le simulateur, puis téléversez ce même fichier dans la boîte de dépôt qui est disponible sur le portail des cours.

Étape 2: questions (25%)

Deuxièmement, une fois votre code implanté et testé (nous verrons comment vous pouvez tester son bon fonctionnement), vous devez répondre à dix (10) questions sur le portail des cours. Notez que contrairement aux travaux pratiques précédents, ces questions ne portent pas sur des lignes en particulier, mais bien sur le fonctionnement global de votre programme et des interruptions en ARM.

Ces questions sont disponibles dans la section « Questionnaires » du portail des cours.

Pour toute question concernant la procédure de remise ou le travail lui-même, utilisez Piazza!

Détails sur le code

Important: n'écrivez votre code qu'aux endroits indiqués, sans toucher au reste. Toute autre modification au code pourrait causer des problèmes!

1. La table des vecteurs d'interruption

Comme beaucoup d'architectures, l'ARM utilise une table des vecteurs d'interruption pour permettre au programmeur de spécifier quoi faire pour chaque type d'interruption. Cette table est située au tout début de la mémoire, à partir de l'adresse 0x00. Le tableau suivant présente le détail de ces vecteurs d'interruption.

AdresseCorrespond à l'interruption...
0x00000000Reset
0x00000004Instruction invalide
0x00000008SVC (interruption logicielle)
0x0000000CPrefetch Abort
0x00000010Data Abort
0x00000014Inutilisé
0x00000018IRQ (Interruption Request)
0x0000001CFIQ (Fast Interrupt Request)

Comme vous pouvez le constater, vous utilisiez déjà une interruption sans le savoir, soit l'interruption Reset, en écrivant B main au tout début du programme. Cette instruction se retrouvait à l'adresse 0x00 et indiquait au processeur quoi faire lors d'un reset, à savoir aller à l'étiquette main.

Dans ce TP, nous avons implanté l'entrée de la table pour l'interruption Reset (voir B moninitialisation). Vous devrez écrire le code correspondant à l'interruption FIQ, qui devrait brancher vers votre routine de traitement de l'interruption, soit l'étiquette moninterruption.

2. Initialisation du système et des processus

Lorsqu'une interruption survient, le processus en cours d'exécution peut être n'importe où dans son code assembleur. Il nous faut par conséquent être capables d'enregistrer l'état du processus afin d'être en mesure de le restaurer plus tard, sans que ce dernier ne « remarque » qu'il a été interrompu. Plus précisément, dans ce TP, vous devrez sauvegarder :

  • Les registres R0 à R7 inclusivement (ne sauvegardez pas les registres R8 à R14);
  • Les drapeaux de l'ALU;
  • La valeur courante de PC.

Pour sauvegarder ces données, votre système dispose de deux piles différentes : Pile0, qui contient les informations nécessaires au processus 0, et Pile1 pour le processus 1. La figure suivante montre l'organisation mémoire déjà préparée pour vous dans le code fourni. Sur la pile de chaque processus se trouve l'adresse de retour, suivie de l'état des drapeaux, suivi des registres R7 à R0, dans cet ordre. R0 est donc le registre qui est le premier élément de la pile lorsque cette dernière est remplie. N'oubliez pas que la pile fonctionne de bas en haut!

Il vous faudra tout d'abord initialiser les piles Pile0 et Pile1. Pour ce faire, vous devrez accomplir les deux étapes suivantes:

  1. Initialiser la pile du processus 0;
  2. Initialiser la pile du processus 1;

Pour ce faire, utilisez la fonction initialiseProcessus, qui fait tout le travail pour vous. Vous n'avez qu'à l'appeler avec les bons paramètres (voyez sa documentation dans le code fourni pour plus de détails).

3. Routine de traitement de l'interruption

Votre routine de traitement de l'interruption correspond à l'étiquette moninterruption. Lors d'une interruption, votre routine doit, dans l'ordre :

  1. Déterminer le processus qui s'exécute présentement. Vous pouvez utiliser la variable ProcessusCourant pour mémoriser cette valeur;
  2. Sélectionner la pile du processus courant. Il vous faudra donc modifier le pointeur de pile SP selon l'identifiant de ce processus.
  3. Sauvegarder les registres R0 à R7, les drapeaux et PC (qui est automatiquement transféré dans LR par le processeur avant de commencer l'interruption) sur la pile du processus courant;
  4. Déterminer le prochain processus à exécuter. Dans le cadre de ce TP, il n'y a que deux processus, il suffit donc d'alterner entre les deux;
  5. Restaurer les registres R0 à R7 et les drapeaux du processus à reprendre à partir de sa pile;
  6. Sortir de l'interruption, en revenant dans le nouveau processus à exécuter. Pour revenir dans le bon processus, quel registre devrez-vous modifier?

Comme nous utilisons une interruption de type FIQ, votre routine de traitement de l'interruption peut utiliser les registres R8 à R15 sans interférer avec les processus. Pour vous simplifier la vie, servez-vous de ces registres comme registres de travail dans votre fonction moninterruption, ce qui vous évitera d'avoir à sauvegarder les registres que vous utiliserez dans moninterruption.

Tester et valider le bon fonctionnement du système

Afin de vous aider à déboguer votre code, il est recommandé d'utiliser les points d'arrêt (« breakpoints »), comme au TP3. Vous pouvez par exemple mettre un de ces points d'arrêt dans la routine d'interruption elle-même, afin de vérifier son bon fonctionnement. Une autre vérification simple est de vous assurer que les blocs correspondant aux processus 0 et 1 sont bel et bien exécutés en alternance à chaque interruption.

En ce qui concerne la validation de votre code, le code fourni prévoit un système capable de détecter certaines erreurs. Il fonctionne en écrivant un code correspondant à cette erreur dans le registre R7, nous vous demandons donc de vous abstenir d'utiliser ce registre. Au début du programme, la valeur de R7 est non-nulle, mais doit le devenir et le rester après la première exécution des deux processus. Si la valeur de R7 est toujours à 0 après plusieurs centaines de cycles, vous pouvez considérer votre système comme suffisamment stable. Dans le cas contraire, cela indique un problème dans votre procédure de sauvegarde/restauration de l'état des processus.

N'utilisez pas le registre R7 pour vos calculs car il vous servira à valider votre code. Cependant, vous devez le sauvegarder (et le restaurer) comme les autres (R0 à R7) sur la pile.

Ressources

Remerciements

Merci à Marc-André Gardner pour la création de ce TP!

Registre Généraux (User)

Nom Valeur
R0
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
R11
R12
R13 (sp)
R14 (lr)
R15 (pc)

Registre Généraux (FIQ)

Nom Valeur
R0
R1
R2
R3
R4
R5
R6
R7
R8 FIQ
R9 FIQ
R10 FIQ
R11 FIQ
R12 FIQ
R13 FIQ (sp)
R14 FIQ (lr)
R15 (pc)

Registre Généraux (IRQ)

Nom Valeur
R0
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
R11
R12
R13 IRQ (sp)
R14 IRQ (lr)
R15 (pc)

Registre Généraux (SVC)

Nom Valeur
R0
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
R11
R12
R13 SVC (sp)
R14 SVC (lr)
R15 (pc)

État courant

 CPSRSPSR
Negatif (N)
Zero (Z)
Retenue (C)
Dépassement (V)
Ignore IRQ
Ignore FIQ

Configurations

Interruptions

Activer
Type
 cycles
 cycles (premier)
Vitesse d'exécution :  ms

Français

SECTION INTVEC ; ----------------------------------------------------------------------- ; Partie 1. Table des vecteurs d'interruption ; ----------------------------------------------------------------------- B initialisation ; Reset ; ÉCRIVEZ ICI LE RESTE DE LA TABLE DES VECTEURS D'INTERRUPTION ; FIN DE LA TABLE DES VECTEURS D'INTERRUPTION ProcessusCourant ASSIGN32 0 ; Contient le numéro du processus en cours SECTION CODE ; ----------------------------------------------------------------------- ; Partie 3. Routine de traitement de l'interruption ; ----------------------------------------------------------------------- moninterruption ; Dans cette interruption, les registres R8 à R15 sont ; distincts de ceux utilisés par les processus (ils sont ; dans une banque de registre différente). Toutefois, les ; registres R0 à R7 sont partagés. ; Vous pouvez donc utiliser les registres R8 à R15 sans ; les sauvegarder préalablement, mais vous DEVEZ sauvegarder la ; valeur des registres R0 à R7 inclusivement, l'adresse de retour ; et les drapeaux de l'ALU. ; ; On ne peut pas directement sauvegarder les drapeaux, mais ; on peut les transférer dans un registre en utilisant l'instruction ; MRS Rx, SPSR (remplacez x par le numéro du registre voulu) ; Une fois en possession d'un registre contenant la valeur des drapeaux, ; on peut l'envoyer sur la pile pour sauvegarder ces derniers ; ; De même, on peut transférer un registre général dans le registre ; des drapeaux en utilisant l'instruction ; MSR SPSR, Ry (remplacez y par le numéro du registre voulu) ; ATTENTION : les deux instructions (pour écrire et pour lire les drapeaux) ; sont DIFFÉRENTES (dans un cas c'est MSR, dans l'autre MRS) ; ; Lorsqu'une interruption est déclenchée, le processeur termine l'exécution ; de l'instruction courante, puis transfère automatiquement le PC courant dans ; le registre LR (et non pas l'adresse de retour comme avec BL). ; C'est donc ce registre que vous devez utiliser lorsque vous voulez ; sauvegarder la position de l'instruction courante dans chaque processus. ; N'oubliez toutefois pas que la valeur de PC correspond à l'adresse de ; l'instruction courante PLUS 8! ; ; Étant donnée que les interruptions surviennent à tous les 90 cycles, ; votre interruption ne doit jamais demander plus de 80 cycles environ, ; sinon les processus n'auront même pas le temps de s'exécuter avant ; le déclenchement de la prochaine interruption! ; ÉCRIVEZ VOTRE CODE D'INTERRUPTION ICI ; FIN DE VOTRE CODE D'INTERRUPTION SUBS PC, LR, #4 ; On saute à PC-4 (ne pas oublier le +8 du prefetch!) ; pour restaurer l'exécution du processus ; Le S combiné au registre PC comme destination ; est requis pour indiquer au processeur ; la fin du traitement de l'interruption et ; réactiver les interruptions dans le CPSR main initialisation MOV R7, #16 ; Ce registre sera utilisé pour indiquer une erreur ; ne pas y toucher, sauf pour le sauvegarder! ; Ne pas effacer cette ligne: ${TEST_TVI} ; ----------------------------------------------------------------------- ; Partie 2. Initialisation du système et des processus ; ----------------------------------------------------------------------- ; ; Ici, vous avez 2 étapes à effectuer : ; 1. Initialiser la pile du processus 0 ; 2. Initialiser la pile du processus 1 ; Pour ce faire, utilisez la fonction initialiseProcessus, qui fait ; tout le travail pour vous. Vous n'avez qu'à l'appeler avec les ; bons paramètres. (voyez sa documentation pour plus de détails) ; ÉCRIVEZ VOTRE CODE D'INITIALISATION ICI ; FIN DE VOTRE CODE D'INITIALISATION ; Ne pas effacer cette ligne: ${TEST_INIT_P0} ; Ne pas effacer cette ligne: ${TEST_INIT_P1} ; Cette instruction devrait être à la fin du bloc d'initialisation B processus0 ; On débute l'exécution du premier processus initialiseProcessus ; SP : Adresse de la pile du processus ; R1 : État des drapeaux souhaité au début de l'exécution du processus ; R2 : Adresse de la première instruction du processus ; R3 : Nombre de registres généraux à stocker dans la pile ; ATTENTION : CETTE FONCTION MODIFIE LES VALEURS DES REGISTRES SP, R3, R2 ; et R11 SANS LES SAUVEGARDER! ADD SP, SP, #40 ; On va au sommet de la pile ADD R2, R2, #4 ; Pour correspondre à la valeur que le processeur écrit ; dans LR lorsqu'une interruption est déclenchée, on fait ; pointer LR sur la deuxième instruction du processus PUSH {R2} ; On stocke d'abord l'adresse de retour au dessus de la pile PUSH {R1} ; Puis on stocke les drapeaux de l'ALU MOV R11, #0 CMP R3, #0 ; On fait la boucle 'R3' fois boucleInit BXEQ LR PUSH {R11} ; On stocke 0 sur la pile à chaque itération SUBS R3, R3, #1 B boucleInit ; Code des processus à exécuter, chaque processus boucle à l'infini ; Vous n'avez pas à comprendre leur fonctionnement ; Toutefois, ils sont conçus pour mettre dans R7 une valeur ; différente de zéro (0) lorsqu'il y a une erreur dans votre ; code. Vous devriez être capable d'exécuter plusieurs centaines ; de cycles en obtenant une valeur finale de R7 de 0. ; Il est normal que cette valeur soit non nulle au départ, ; mais devrait devenir nulle (et le rester) après l'exécution de la ; première instruction de chaque processus. MOV R7, #64 processus0 ; Initialisation AND R7, R7, #0x40 MOV R0, #0 MOV R1, #0 MOV R2, #1 MOV R3, #64 MOV R4, #0 MOV R5, #0 MOV R6, #0 ; Boucle infinie boucleP0 ADD R0, R0, #1 ADD R1, R1, #2 ADD R2, R1, #10 SUBS R3, R3, #1 MOVEQ R0, #0 MOVEQ R1, #0 MOV R0, R0 MOV R1, R1 MOVEQ R3, #32 ; Vérification d'erreurs potentielles CMP R4, #0 ORRNE R7, R7, #1 CMP R5, #0 ORRNE R7, R7, #1 CMP R6, #0 ORRNE R7, R7, #1 CMP R3, #0 ORRLT R7, R7, #8 ; Ne pas enlever cette ligne: ${TEST_P0} B boucleP0 MOV R7, #128 processus1 ; Initialisation AND R7, R7, #0x80 MOV R4, #-1 MOV R5, #-2 MOV R6, #0 ; Boucle infinie boucleP1 SUB R4, R4, #1 SUB R5, R5, #2 ADD R6, R6, #1 CMP R6, #64 MOVEQ R4, #-1 MOVEQ R5, #-2 MOV R4, R4 MOV R5, R5 MOVEQ R6, #0 ; Vérification d'erreurs potentielles CMP R0, #0 ORRNE R7, R7, #2 CMP R1, #0 ORRNE R7, R7, #2 CMP R2, #0 ORRNE R7, R7, #2 CMP R6, #96 ORRGT R7, R7, #4 ; Ne pas enlever cette ligne: ${TEST_P1} B boucleP1 SECTION DATA ; Déclaration des piles ; Chaque pile doit pouvoir contenir 10 valeurs de 4 octets, soit : ; - 8 registres (de R0 à R7 inclusivement) ; - Les drapeaux (équivalent à un registre) ; - L'adresse de l'instruction courante (PC) PileP0 ALLOC32 10 ; Espace pour la pile du processus 0 PileP1 ALLOC32 10 ; Espace pour la pile du processus 1 ; Ne pas enlever cette ligne: ${TEST_DATA}

Instruction courante

Mémoire

Suivre PC
Cycle courant :