Changes between Version 28 and Version 29 of AS6-TME-B2


Ignore:
Timestamp:
Feb 16, 2022, 1:19:37 PM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AS6-TME-B2

    v28 v29  
    11{{{#!protected
    2 
    3 = Gestion des interruptions
    4 
    52[[PageOutline]]
    63**
     
    5148
    5249
    53 = Questions
     50= A. Questions
    5451
    5552
     
    486483
    487484
    488 == Travaux pratiques
     485== B. Travaux pratiques
    489486
    490487
     
    509506**Question** : faire un dessin représentant la plateforme avec les signaux IRQ.
    510507
    511 
    512 === Analyse de code
    513 
    514 
    515 L'archive du [htdocs:files/tp2.tgz code du tp2 (tp2.tgz)] doit être placés à coté du code de tp1 et dézippé.
    516 
    517 Pour ce TP, il n'y aura qu'une seule étape parce que j'ai voulu ajouter un minimum de code.
    518 En effet, la gestion des IRQ est liée à la gestion des périphériques puisque c'est eux c'est essentiellement eux qui les produise. Or, la gestion des périphériques de faire par le mécanisme des pilotes de périphériques. J'en présenterai une version simplifiée lorsque nous verrons les périphériques initiateurs. Pour l'heure, il s'agit de comprendre juste comment sont traités les IRQ et de jouer un peu avec.
    519 
    520 Par rapport à l'étape 6 sur tp1, voici les changements
     508= 1. Game over simple
     509
     510 
     511
     512Vous allez réaliser un petit jeu dans lequel vous deviez deviner un nombre tiré au hasard. Ce jeu avait été mis dans `kinit` parce qu'à ce moment, il n'y avait pas encore d'application utilisateur. Nous vous proposons de mettre le jeu dans l'application user et de limiter le temps pendant lequel vous pouvez jouer. Nous allons vous guider pas-à-pas.
     513
     514Récuperez l'[htdocs:files/tp3.tgz archive du code du tp3], placez-là dans le répertoire `kO6` et décompressez-là. Les commandes ci-dessous supposent que vous avez mis l'archive dans le répertoire `k06`
     515{{{#!bash
     516cd ~/k06
     517tar xvzf tp3.tgz
     518cd tp3/1_gameover
     519}}}
     520
     521Le code de l'application est le suivant (dans uapp/main.c)
     522
     523{{{#!c
     524#include <libc.h>
     525int main (void)
     526{
     527    int guess;
     528    int random;
     529    char buf[8];
     530    char name[16];
     531
     532    fprintf(0,"Tapez votre nom : ");
     533    fgets(name, sizeof(name), 0);
     534    if (name[strlen(name)] == '\n')
     535        name[strlen(name)] = 0;
     536    srand(clock()); // start the random generator with a "random" seed.
     537
     538    random = 1 + rand() % 99;
     539    fprintf(0,"Donnez un nombre entre 1 et 99: ");
     540    do {
     541        fgets(buf, sizeof(buf), 0);
     542        guess = atoi (buf);
     543        if (guess < random)
     544            fprintf(0,"%d est trop petit: ", guess);
     545        else if (guess > random)
     546            fprintf(0,"%d est trop grand: ", guess);
     547    } while (random != guess);
     548
     549    fprintf(0,"\nGagné %s\n", name);
     550    return 0;
     551}
     552}}}
     553
     5541. Essayez le jeu (dans le répertoire `tp3/1_gameover`) : tapez `make exec`\\
     555   comme vous pouvez le constater, vous avez le temps de jouer.
     5561. Dans la version précédente du gestionnaire de syscall, nous avions masqué les IRQ en écrivant `0` dans le registre `c0_status`(registre $12 du coprocesseur 0). Cela avait pour conséquence de mettre tout à 0, entre autre le bit `IE`. Il faut modifier ça, parce que sinon, lorsque l'utilisateur demandera à lire le clavier avec l'appel système `fgets()`, l'IRQ venant du timer ne sera jamais prise en compte (`TODO1`), ensuite au retour de la fonction qui réalise l'appel système, il faut masquer les IRQ pour ne pas avoir d'interruption pendant la restauration des registres jusqu'au `eret` qui fait sortir du kernel.
     557{{{#!c
     558    addiu   $29,    $29,    -8*4        // context for $31 + EPC + SR + syscall_code + 4 args
     559    mfc0    $27,    $14                 // $27 <- EPC (addr of syscall instruction)
     560    mfc0    $26,    $12                 // $26 <- SR (status register)
     561    addiu   $27,    $27,    4           // $27 <- EPC+4 (return address)
     562    sw      $31,    7*4($29)            // save $31 because it will be erased
     563    sw      $27,    6*4($29)            // save EPC+4 (return address of syscall)
     564    sw      $26,    5*4($29)            // save SR (status register)
     565    sw      $2,     4*4($29)            // save syscall code (useful for debug message)
     566// TODO1: remplacez "mtc0 $0, $12" par 2 autres pour mettre 1 dans les bits c0_sr.HWI0 et c0_sr.IE
     567// vous pouvez utiliser $26
     568    mtc0    $0,     $12                 // SR <- kernel-mode without INT (UM=0 ERL=0 EXL=0 IE=0)
     569
     570    la      $26,    syscall_vector      // $26 <- table of syscall functions
     571    andi    $2,     $2,     SYSCALL_NR-1// apply syscall mask
     572    sll     $2,     $2,     2           // compute syscall index (mutiply by 4)
     573    addu    $2,     $26,    $2          // $2 <- & syscall_vector[$2]
     574    lw      $2,     ($2)                // at the end: $2 <- syscall_vector[$2]
     575    jalr    $2                          // call syscall function
     576
     577// TODO2: Il faut mettre 0 dans SR pour masquer les interruptions
     578    lw      $26,    5*4($29)            // get old SR
     579    lw      $27,    6*4($29)            // get return address of syscall
     580    lw      $31,    7*4($29)            // restore $31 (return address of syscall function)
     581    mtc0    $26,    $12                 // restore SR
     582    mtc0    $27,    $14                 // restore EPC
     583    addiu   $29,    $29,    8*4         // restore stack pointer
     584    eret                                // return : jr EPC with EXL <- 0
     585}}}
     586{{{#!protected ------------------------------------------------------------------------------------
     587''
     588{{{#!c
     589// TODO1: remplacez "mtc0 $0, $12" par 2 autres pour mettre 1 dans les bits c0_sr.HWI0 et c0_sr.IE
     590    li      $26,    0x401               // next value of SR
     591    mtc0    $26,    $12                 // SR <- kernel-mode with INT (HWI0=1 UM=0 ERL=0 EXL=0 IE=1)
     592
     593// TODO2: Il faut mettre 0 dans SR pour masquer les interruptions
     594    mtc0    $0,     $12                 // SR <- kernel-mode without INT (UM=0 ERL=0 EXL=0 IE=0)
     595}}}
     596''
     597}}}
     5981. Ouvrez le fichier `kernel/kinit.c`. Dans cette fonction, on appelle `archi_init()` avec en paramètre un nombre qui va servir de période d'horloge. Le simulateur de la plateforme sur les machines de la PPTI va environ à 3.5MHz. Combien de secondes demande-t-on dans ce code ?
     599{{{#!protected ------------------------------------------------------------------------------------
     600''
     601  arch_init (30*3500000);     // about 30 secondes with this simulator (3.5MHz)
     602''
     603}}}
     6041. Ouvrez le fichier `kernel/harch.c` et vous allez devoir remplir 3 fonctions pour configurer le timer: `arch_init()`, `timer_init()` et `timer_isr()` (pour trouver ces fonctions cherchez le mot `TODO`)
     605{{{#!c
     606void arch_init (int tick)
     607{
     608// TODO A remplir avec 4 lignes :
     609// 1) appel de la fonction timer_init pour le timer 0 avec tick comme période
     610// 2) mise à 1 du bit 0 du registre ICU_MASK en utilisant la fonction icu_set_mask()
     611// 3) initialisation de la table irq_vector_isr[] vecteur d'interruption avec timer_isr()
     612// 4) initialisation de la table irq_vector_dev[] vecteur d'interruption avec 0
     613}
     614static void timer_init (int timer, int tick)
     615{
     616// TODO A remplir avec 2 lignes :
     617// 1) initialiser le registre period du timer n°timer avec la période tick (reçus en argument)
     618// 2) initialiser le registre mode   du timer n°timer avec 3 (démarre le timer avec IRQ demandée) si la période est non nulle
     619}
     620static void timer_isr (int timer)
     621{
     622// TODO A remplir avec 3 lignes :
     623// 1) Acquiter l'interruption du timer en écrivant n'importe quoi dans le registre resetirq
     624// 2) afficher un message "Game Over" avec kprintf()
     625// 3) appeler la fonction kernel exit() (c'est une sortie définitive ici)
     626}
     627}}}
     628{{{#!protected
     629{{{#!c
     630void arch_init (int tick)
     631{
     632    timer_init (0, tick);           // sets period of timer n'0 (thus for CPU n'0) and starts it
     633    icu_set_mask (0, 0);            // [CPU n'0].IRQ <-- ICU.PIN[0] <- Interrupt signal timer n'0
     634    irq_vector_isr [0] = timer_isr; // tell the kernel which isr to exec for ICU.PIN n'0
     635    irq_vector_dev [0] = 0;         // device instance attached to ICU.PIN n'0
     636}
     637static void timer_init (int timer, int tick)
     638{
     639    __timer_regs_map[timer].period =  tick;     // next period
     640    __timer_regs_map[timer].mode = (tick)?3:0;  // timer ON with IRQ only if (tick != 0)
     641}
     642static void timer_isr (int timer)
     643{
     644    __timer_regs_map[timer].resetirq = 1;       // IRQ acknoledgement to lower the interrupt signal
     645    kprintf ("\nGame Over\n");
     646    exit(1);
     647}
     648}}}
     649}}}
     650
     651
     652
     653
     654= B. Game over avec décompteur
     655
     656
     657
     658
     659Dans ce qui précède, l'exécution de l'ISR du Timer est fatale puisqu'elle stoppe l'application après l'affichage de "Game Over!". Nous vous proposons de modifier l'ISR afin d'avoir un comportement un peu plus réaliste. Dans cette nouvelle version, l'ISR du timer décrémente un compteur alloué dans une variable globale du noyau puis elle revient dans l'application tant que ce compteur est différent de 0. Donc, dans l'ISR du timer si le compteur est différent de 0, elle affiche un message avec la valeur du compteur, sinon elle affiche "game over!" et stoppe l'application, comme dans l'exercice précédent.\\\\Par exemple, au lieu d'afficher:
    521660{{{
    522 7_isr/
    523 ├── common
    524 │   └── syscalls.h  : pas de changement parce qu'il y a pas d'ajout d'appel système
    525 ├── kernel
    526 │   ├── harch.c     : ajout des fonctions d'accès aux nouveaux périphériques, des isr et de la fonction arch_init()
    527 │   ├── harch.h     : ajout de la déclaration de la fonction arch_init, les autres fonctions de harch.c ne sont pas exportés.
    528 │   ├── hcpu.h      : pas de changement
    529 │   ├── hcpu.S      : ajout du gestionnaire d'interruption dans kentry
    530 │   ├── kernel.ld   : ajout des informations sur les segments des nouveaux périphériques
    531 │   ├── kinit.c     : ajout de l'appel à arch_init() et acceptation des interruptions
    532 │   ├── klibc.c     : pas de changement
    533 │   ├── klibc.h     : pas de changement
    534 │   ├── kpanic.c    : pas de changement
    535 │   ├── kpanic.h    : pas de changement
    536 │   ├── ksyscalls.c : pas de changement
    537 │   └── Makefile    : ajout des #defines pour indiquer le nombres de périphériques ajoutés
    538 ├── Makefile        : changement mais rien d'important
    539 ├── uapp           
    540 │   ├── main.c      : pas d'appel à exit() parce qu'actuellement le noyau n'est pas interruptible
    541 │   └── Makefile    : pas de changement
    542 └── ulib
    543     ├── crt0.c      : pas de changement
    544     ├── libc.c      : pas de changement
    545     ├── libc.h      : pas de changement
    546     ├── Makefile    : pas de changement
    547     └── user.ld     : pas de changement
    548 }}}
    549 
    550 
    551 === Exercice
    552 
    553 
    554 Je vous propose de modifier un peu le code de harch.c pour gérer l'interruption de sur le TTY1 et faire un petit programme de dessin...
    555  
    556 1. Vous devez modifier la fonction `arch_init()` pour demander démasquer l'interruption du TTY1 dans l'ICU et programmer le vecteur d'interruption.
    557 2. Vous allez modifier la fonction `isr_tty()` pour y créer un petit jeu de dessin.
    558    - L'idée est de gérer les touches de ''flèche'' du clavier et de déplacer le curseur sur la fenêtre.
    559    - Ensuite vous pouvez utiliser les touches U (pour UP) et D pour down pour lever ou baisser un crayon.
    560    - Si vous vous déplacez avec le crayon baissé ça écrit des '*'.
    561    - Vous pouvez ajouter d'autres fonctionnalités si ça vous amuse :-)
    562 
    563 Pour déplacer le curseur, nous allons utiliser les [http://braun-home.net/michael/info/misc/VT100_commands.htm commandes d'échappement `VT100`] comprises par les fenêtres `xterm`.
    564 
    565 Vous allez aussi devoir créer un automate d'état fini dans la fonction `tty_isr`, c'est assez simple ici.
    566 Je vous donne un début de code que vous pouvez analyser et modifier.
    567 Normalement, on ne fait pas un ''jeu'' dans une isr, mais un automate oui, c'est possible.
    568 
    569 Quand vous tapez sur une flèche ⟶ le clavier envoie 3 code ascii : le code ESC (27), '[', ’C'
    570 L'automate a trois états :
    571 * 0 = on attend n'importe quel caractère
    572 * 1 = on a reçu ESC et on attend '['
    573 * 2 = on attend 'A' ou 'B' ou 'C' ou 'D'
    574 
    575 {{{#!c
    576 static void tty_isr (int irq)
    577 {
    578     static int state[NTTYS];
    579 
    580     char c = __tty_regs_map[ (irq - 3) % NTTYS].read;
    581     switch (irq) {
    582     case 3:
    583         kprintf (0, "code ascii %d", c);
    584         if ((c >= 32) && (c <= 126))
    585             kprintf (0, " = %c", c);
    586         kprintf (0, "\n");
    587         break;
    588     case 4:
    589         switch (c) {
    590         case 27  :
    591             state[1] = 1;
    592             break;
    593         case '[' :
    594             if (state[1] == 1)
    595                 state[1] = 2;
    596             else {
    597                 state[1] = 0;
    598             }
    599             break;
    600         case 'B' :
    601             if (state[1] == 2) {
    602                 kprintf (1, "%c[B", 27);
    603             }
    604             state[1] = 0;
    605             break;
    606         case 'C' :
    607             if (state[1] == 2) {
    608                 kprintf (1, "%c[C", 27);
    609             }
    610             state[1] = 0;
    611             break;
    612         }
     661   _     ___    __
     662  | |__ /'v'\  / /
     663  | / /(     )/ _ \
     664  |_\_\ x___x \___/
     665
     666Tapez votre nom : Moi
     667Donnez un nombre entre 1 et 99: 45
     66845 est trop grand: 20
     66920 est trop grand:
     6700 est trop petit:
     671Game Over
     672[105002991] EXIT status = 1
     673}}}
     674 l'application pourrait afficher:
     675{{{
     676   _     ___    __
     677  | |__ /'v'\  / /
     678  | / /(     )/ _ \
     679  |_\_\ x___x \___/
     680
     681Tapez votre nom : Moi
     682Donnez un nombre entre 1 et 99: 45
     68345 est trop grand: 20
     68420 est trop grand:
     685..3 : 12
     68612 est trop petit: 15
     68715 est trop petit:
     688..2 :
     689..1 :
     690Game Over
     691[115002778] EXIT status = 1
     692}}}
     693{{{#!protected ------------------------------------------------------------------------------------
     694''
     695`kernel/harch.c`
     696{{{#!c
     697extern void arch_init (int tick, unsigned quantum);
     698}}}
     699`kernel/harch.c`
     700{{{#!c
     701static unsigned timer_quantum;
     702static void timer_init (int timer, int tick, unsigned quantum)
     703
     704    __timer_regs_map[timer].resetirq = 0;       // to delete previous untraited IRQ
     705    __timer_regs_map[timer].period =  tick;     // next period
     706    __timer_regs_map[timer].mode = (tick)?3:0;  // timer ON with IRQ only if (tick != 0)
     707    timer_quantum = quantum  % 100;             // %100 to avoid aberrant value
     708}
     709static void timer_isr (int timer)
     710{
     711    __timer_regs_map[timer].resetirq = 1;
     712    if (timer_quantum) {
     713        kprintf ("\n...%d : ", timer_quantum--);
     714    } else {   
     715        kprintf ("\nGame Over\n");
     716        exit(1);
    613717    }
    614718}
    615719}}}
     720`kernel/kinit.c`
     721{{{#!c
     722void kinit (void)
     723{
     724    [...]
     725    arch_init (3*3500000, 10); // tick is about 3 seconde, quantum is then about 30 secondes
     726    [...]
     727}
     728}}}
     729''
     730}}}
     731
     732
     733
     734= 3. Évaluation de la durée d'une ISR
     735
     736
     737
     738Dans cet usage du TIMER, les ISR ne sont pas fatales, sauf la dernière. En utilisant le mode debug (make debug) et le fichier `trace0.S`, déterminez la durée en cycles du traitement par le noyau d'une IRQ du timer. Ce n'est pas exactement la même durée pour toutes les IRQ.
     739
     740Pour cette question, il faut commenter les affichages dans l'ISR, changer la valeur du tick pour voir plus d'IRQ (par exemple 1000) et exécuter en mode débug puis regarder la trace dans `trace0.S`, il faut chercher un `kentry` correspondant à une IRQ et chercher l'instruction `eret` qui marque la fin du traitement.
     741{{{#!protected ------------------------------------------------------------------------------------
     742''
     743* La durée mesurée est de l'ordre de 430 cycles pour la première (vers le cycle 3600).
     744''
     745}}}