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 | |
| 512 | Vous 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 | |
| 514 | Ré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 |
| 516 | cd ~/k06 |
| 517 | tar xvzf tp3.tgz |
| 518 | cd tp3/1_gameover |
| 519 | }}} |
| 520 | |
| 521 | Le code de l'application est le suivant (dans uapp/main.c) |
| 522 | |
| 523 | {{{#!c |
| 524 | #include <libc.h> |
| 525 | int 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 | |
| 554 | 1. 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. |
| 556 | 1. 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 | }}} |
| 598 | 1. 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 | }}} |
| 604 | 1. 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 |
| 606 | void 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 | } |
| 614 | static 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 | } |
| 620 | static 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 |
| 630 | void 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 | } |
| 637 | static 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 | } |
| 642 | static 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 | |
| 659 | Dans 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: |
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 | |
| 666 | Tapez votre nom : Moi |
| 667 | Donnez un nombre entre 1 et 99: 45 |
| 668 | 45 est trop grand: 20 |
| 669 | 20 est trop grand: |
| 670 | 0 est trop petit: |
| 671 | Game Over |
| 672 | [105002991] EXIT status = 1 |
| 673 | }}} |
| 674 | l'application pourrait afficher: |
| 675 | {{{ |
| 676 | _ ___ __ |
| 677 | | |__ /'v'\ / / |
| 678 | | / /( )/ _ \ |
| 679 | |_\_\ x___x \___/ |
| 680 | |
| 681 | Tapez votre nom : Moi |
| 682 | Donnez un nombre entre 1 et 99: 45 |
| 683 | 45 est trop grand: 20 |
| 684 | 20 est trop grand: |
| 685 | ..3 : 12 |
| 686 | 12 est trop petit: 15 |
| 687 | 15 est trop petit: |
| 688 | ..2 : |
| 689 | ..1 : |
| 690 | Game Over |
| 691 | [115002778] EXIT status = 1 |
| 692 | }}} |
| 693 | {{{#!protected ------------------------------------------------------------------------------------ |
| 694 | '' |
| 695 | `kernel/harch.c` |
| 696 | {{{#!c |
| 697 | extern void arch_init (int tick, unsigned quantum); |
| 698 | }}} |
| 699 | `kernel/harch.c` |
| 700 | {{{#!c |
| 701 | static unsigned timer_quantum; |
| 702 | static 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 | } |
| 709 | static 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); |