Changes between Version 2 and Version 3 of Archi-1-TD9


Ignore:
Timestamp:
Dec 18, 2021, 4:25:39 PM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Archi-1-TD9

    v2 v3  
    11**
    2 [ __[wiki:WikiStart Start]__ ]
    3 [ __[wiki:Howto-TP Config]__ ]
    4 [ __[htdocs:cours/doc_MIPS32.pdf MIPS User]__ ]
    5 [ __[wiki:Doc-MIPS-Archi-Asm-kernel MIPS Kernel]__ ]
    6 
    7 [ __[htdocs:cours/AS5-9-4p.pdf Cours 9]__ ]
    8 [ __[htdocs:cours/AS5-10-4p.pdf Cours 10]__ ]
    9 [ __[htdocs:cours/AS5-11-4p.pdf Cours 11]__ ]
    10 
    11 [ ''TME 9'' ]
    12 [ __[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME10 TME 10]__ ]
    13 [ __[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME11 TME 11]__ ]
    14 **[[PageOutline]]**
    15 Codes (tgz) →
    16 [ __[htdocs:files/kO6bin.tgz gcc & simulateur]__ ]
    17 [ __[htdocs:files/tp1.tgz TME 9]__ ]
    18 [ __[htdocs:files/tp2.tgz TME 10]__ ]
    19 [ __[htdocs:files/tp3.tgz TME 11]__ ]
     2DOCS
     3[__[wiki: Start]__][__[wiki:Howto-TP Config]__][__[htdocs:cours/doc_MIPS32.pdf User]__][__[wiki:Doc-MIPS-Archi-Asm-kernel Kernel]__]
     4— COURS
     5[__[htdocs:cours/AS5-9-4p.pdf 9]__]
     6[__[htdocs:cours/AS5-10-4p.pdf 10]__]
     7[__[htdocs:cours/AS5-11-4p.pdf 11]__]
     8— TD
     9[''9''][__[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME10 10]__][__[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME11 11]__]
     10— TP
     11[__[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME9 9]__][__[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME10 10]__][__[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME11 11]__]
     12— ZIP
     13[__[htdocs:files/kO6bin.tgz gcc...]__][__[htdocs:files/tp1.tgz 9]__][__[htdocs:files/tp2.tgz 10]__][__[htdocs:files/tp3.tgz 11]__]
     14
     15[[PageOutline]]
    2016**
    2117{{{#!html
     
    2420}}}
    2521
    26 Cette page décrit la séance complète : TD et TP. Elle commence par des exercices à faire sur papier et puis elle continue et se termine par des questions sur le code et quelques exercices de codage simples à écrire et à tester sur le prototype.
    27 La partie pratique  est découpée en 5 étapes. Pour chaque étape, nous donnons (1) une brève description, (2) une liste des objectifs principaux de l'étape, (3) une liste des fichiers avec un bref commentaire sur chaque fichier, (4) une liste de questions simples dont les réponses sont dans le code, le cours ou le TD et enfin (5) un exercice de codage.
    28 
    29 **IMPORTANT\\Avant de faire cette séance, vous devez avoir lu les documents suivants** :
    30 * [wiki: Description des objectifs de cette séance et des suivantes] : ''obligatoire''
    31 * [htdocs:cours/AS5-9-4p.pdf Cours de démarrage présentant l'architecture matérielle et logicielle que vous allez manipuler] ''obligatoire''
    32 * [wiki:Howto-TP Configuration de l'environnement des TP] : ''obligatoire''
    33 * [htdocs:cours/doc_MIPS32.pdf Document sur l'assembleur du MIPS et la convention d'appel des fonctions] : ''recommandé, normalement déjà lu''
    34 * [wiki:Doc-MIPS-Archi-Asm-kernel Documentation sur le mode kernel du MIPS32] : ''optionnel pour cette séance''
    35 
    36 
    37 **Récupération du code du TP**
    38 
    39 * **Vous devez avoir installé le simulateur du prototype almo1 et la chaine de cross-compilation MIPS ([wiki:Howto-TP Config sections 2.2 et 3.2])**
    40 * Téléchargez **[htdocs:files/tp1.tgz l'archive code du tp1]** et placez là dans le répertoire **`~/kO6`** (ou dans le répertoire que vous avez choisi, relisez la page sur la configuration si ce n'est pas clair). 
    41 * Ouvrez un `terminal`
    42 * Allez dans le répertoire `kO6` : **`cd ~/kO6`**
    43 * Décompressez l'archive du tp1 (dans le répertoire **`kO6`**) : **`tar xvzf tp1.tgz`**
    44 * Exécutez la commande **`cd ; tree -L 1 kO6/tp1/`**.\\
    45   ''(si vous n'avez pas `tree` sur votre Linux, vous pouvez l'installer, c'est un outil utile, mais pas indispensable pour ces TP)''\\
    46   Vous devrez obtenir ceci:
    47 {{{#!bash
    48 kO6/tp1
    49 ├── 1_hello_boot
    50 ├── 2_init_asm
    51 ├── 3_init_c
    52 ├── 4_nttys
    53 ├── 5_driver
    54 └── Makefile
    55 }}}
    56 
    57 
    58 
    59 ==
    60 = A. Travaux dirigés
    61 
    62 
    63 
    64 == A1. Analyse de l'architecture
     22= A1. Analyse de l'architecture
    6523
    6624
     
    13896
    13997
    140 == A2. Programmation assembleur
     98= A2. Programmation assembleur
    14199 
    142100
     
    246204}}}
    247205
    248 == A3. Programmation en C
     206= A3. Programmation en C
    249207
    250208
     
    358316
    359317
    360 == A4. Compilation
     318= A4. Compilation
    361319
    362320
     
    510468''
    511469}}}
    512 
    513 
    514 ==
    515 = B. Travaux pratiques
    516 
    517 
    518 
    519 Pour les travaux pratiques, vous devez d'abord répondre aux questions, elles ont pour but de vous faire lire le code et revoir les points du cours. Les réponses sont dans le cours ou dans les fichiers sources. Certaines ont déjà été traitées en TD, c'est normal. Ensuite, vous passez aux exercices pratiques.
    520 
    521 Vous devez avoir récupérer l'archive tp1.tgz pour pouvoir faire cette partie, si ce n'est pas la cas, retournez lire la section `Récupération du code du TP` en haut de cette page. La variable `shell` `$kO6` doit être définie dans votre environnement si vous avez suivi les consignes de la page [wiki:Howto-TP Config sections 2.2].
    522 
    523 Si vous avez bien suivi les étapes de configuration de l'environnement et de récupération du code alors le code se trouve dans `~/kO6/tp1/`, et ouvrez un terminal et allez-y. Dans le répertoire `~/kO6/tp1/` vous avez 5 sous-répertoires et un Makefile. Le fichier `~/kO6/tp1/Makefile` permet de faire le ménage en appelant les Makefiles des sous-répertoires avec la cible `clean`, il est simple, mais c'est un Makefile hiérarchique. Ouvrez-le par curiosité.
    524 
    525 
    526 
    527 == B1. Premier programme en assembleur dans la seule section de boot
    528 
    529 
    530 
    531 Nous commençons par un petit programme de quelques lignes en assembleur, placé entièrement dans la région mémoire
    532 du boot, qui réalise l'affichage du message "Hello World". C'est un tout tout petit programme, mais pour obtenir
    533 l'exécutable, vous devrez utiliser tous les outils de la chaîne de cross-compilation MIPS et
    534 pour l'exécuter vous devrez exécuter le simulateur du prototype. C'est simple, mais c'est nouveau pour
    535 beaucoup d'entre vous
    536 
    537 **Objectifs**
    538 
    539 - produire un exécutable à partir d'un code en assembleur.
    540 - savoir comment afficher un caractère sur un terminal.
    541 - analyse d'une trace d'exécution
    542 
    543 **Fichiers**
    544 
    545 {{{
    546 1_hello_boot
    547 ├── hcpua.S      : code dépendant du cpu matériel en assembleur
    548 ├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
    549 └── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
    550 }}}
    551 
    552 
    553 
    554 **Questions**
    555 
    556 
    557 1. Dans quel fichier se trouve la description de l'espace d'adressage du MIPS ? Que trouve-t-on dans ce fichier ?
    558 {{{#!protected ------------------------------------------------------------------------------------
    559 ''
    560 - C'est dans le fichier kernel.ld. \\On y trouve:
    561   - la définition de variables du ldscript. Ce sont essentiellement des adresses dans l'espace d'adressage,
    562     mais pas seulement, il y a aussi la taille des régions.
    563   - On trouve ensuite la déclaration des régions mémoires.
    564   - et enfin la définition des sections de sortie qui seront mises dans le fichier binaire produit et dans quelle région elles sont placées.
    565 ''
    566 }}}
    567 1. Dans quel fichier se trouve le code de boot et pourquoi, selon vous, avoir nommé ce fichier ainsi ?
    568 {{{#!protected ------------------------------------------------------------------------------------
    569 ''
    570 - Le code de boot est dans le fichier `hcpua.S`. Il a a été nommé ainsi parce que c'est du code qui dépend du
    571   hardware, qu'il concerne le cpu et que c'est du code en assembleur. Nous verrons plus tard qu'il y aura un fichier `hcpuc.c`
    572 ''
    573 }}}
    574 1. À quelle adresse démarre le MIPS ? Où peut-on le vérifier ?
    575 {{{#!protected ------------------------------------------------------------------------------------
    576 ''
    577 - L'adresse de démarrage est `0xBFC00000`.
    578 - On peut le vérifier dans le fichier `kernel.ld`.
    579   Il y a une définition des régions de la mémoire, dont une région commençant à cette adresse-là, et c'est dans
    580   cette région que l'on met le code de boot.
    581 ''
    582 }}}
    583 1. Que produit `gcc` quand on utilise l'option `-c` ?
    584 {{{#!protected ------------------------------------------------------------------------------------
    585 ''
    586 - L'option `-c` demande à `gcc` de s'arrêter après avoir produit le fichier objet.
    587 - Il produit donc un fichier au format `.o`
    588 ''
    589 }}}
    590 1. Que fait l'éditeur de liens ? Comment est-il invoqué ?
    591 {{{#!protected ------------------------------------------------------------------------------------
    592 ''
    593 - L'éditeur de liens rassemble toutes les sections produites par le compilateur, et donc présentes dans les fichiers objet `.o`, et il les place dans de nouvelles sections, elles-mêmes placées dans les régions de la mémoire, conformément au fichier ldscript (ici `kernel.ld`).
    594 - L'éditeur de liens est appelé par `gcc` si on n'a pas l'option `-c`ou directement par `ld` (ici `mipsel_unknown_ld`)
    595 ''
    596 }}}
    597 1. De quels fichiers a besoin l'éditeur de liens pour fonctionner ?
    598 {{{#!protected ------------------------------------------------------------------------------------
    599 ''
    600 - L'éditeur de liens a besoin des fichiers objets `.o` et du fichier `ldscript` (ici, `kernel.ld`)
    601 ''
    602 }}}
    603 1. Dans quelle section se trouve le code de boot pour le compilateur ? ''(la réponse est dans le code assembleur)''
    604 {{{#!protected ------------------------------------------------------------------------------------
    605 ''
    606 - Le code de boot a été mis dans une section `.text`.
    607 ''
    608 }}}
    609 1. Dans quelle section se trouve le message "hello" pour le compilateur ? Ce choix est particulier, mais ce message est en lecture seule.
    610 {{{#!protected ------------------------------------------------------------------------------------
    611 ''
    612 - Le message est aussi la section `.text`.
    613 ''
    614 }}}
    615 1. Dans quelle section se trouve le code de boot dans le code exécutable ? (la réponse est dans `hcpua.S`)
    616 {{{#!protected ------------------------------------------------------------------------------------
    617 ''
    618 - Dans le programme exécutable, le code de boot est mis dans la section `.boot`.
    619 ''
    620 }}}
    621 1. Dans quelle région de la mémoire le code de boot est-il placé ? (la réponse est dans `kernel.ld`)
    622 {{{#!protected ------------------------------------------------------------------------------------
    623 ''
    624 - Le code de boot est placé dans la région `boot_region`
    625 ''
    626 }}}
    627 1. Comment connaît-on l'adresse du registre de sortie du contrôleur de terminal `TTY` ? (la réponse est dans `kernel.ld' et sur cette page)
    628 {{{#!protected ------------------------------------------------------------------------------------
    629 ''
    630 - Le fichier `kernel.ld` déclare une variable `__tty_regs_map` initialisée avec l'adresse de
    631   où sont placés les registres de contrôles du `TTY`. Le premier registre à l'adresse `__tty_regs_map`
    632   est l'adresse du registre de sortie `TTY_WRITE`.
    633 ''
    634 }}}
    635 1. Le code de boot se contente d'afficher un message, comment sait-on que le message est fini
    636    et que le programme doit s'arrêter ? (ou quel est le caractère de fin de chaîne ?)
    637 {{{#!protected ------------------------------------------------------------------------------------
    638 ''
    639 - C'est quand la boucle d'affichage détecte le `0` terminateur de la chaîne de caractères.
    640 ''
    641 }}}
    642 1. Pourquoi terminer le programme par un `dead: j dead` ? Notez qu'on ne peut pas encore faire un ''`syscall exit`'' parce qu'il n'y a pas de gestionnaire de syscall et surtout parce `syscall` est une instruction appeler par une application utilisateur, et qu'il n'y en a pas encore.
    643 {{{#!protected ------------------------------------------------------------------------------------
    644 ''
    645 - If faut arrêter le programme, car il n'y a plus de code, mais on ne sait pas arrêter le processeur,
    646   alors on le faire tourner en rond.
    647 ''
    648 }}}
    649 
    650 **Exercices**
    651  
    652 - Exécutez le programme en lançant le simulateur avec `make exec`, qu'observez-vous ?
    653 {{{#!protected ------------------------------------------------------------------------------------
    654 ''
    655 - On voit une fenêtre `xterm` qui affiche un message et c'est tout. Dans le terminal de lancement de `make exec`, on voit le compteur de cycles avancer.
    656 ''
    657 }}}
    658 - Exécutez le programme en lançant le simulateur avec `make debug`.\\Cela exécute le programme pour une courte durée et cela produit deux fichiers `trace0.s` et `label0.s`.\\`trace0.s` contient la trace des instructions assembleur exécutées par le processeur. \\Ouvrez `trace.0.s` et repérez ce qui est cité ici 
    659  - On voit la séquence des instructions exécutées
    660  - La première colonne nous informe que les adresses lues sont dans l'espace Kernel
    661  - La seconde colonne sont les numéros de cycles
    662  - La troisième sont les adresses
    663  - La quatrième le code binaire des instructions
    664  - Le reste de la ligne contient l'instruction désassemblée
    665  - Lorsque les adresses ont un nom, c'est à dire qu'une étiquette leur a été attribuée, celle-ci est indiquée.
    666 
    667  `label0.s` contient la séquence des appels de fonctions de l'exécutions. C'est en fait un extrait de la trace.\\Ouvrez le fichier `label0.s` et interprétez ce que vous voyez.
    668 {{{#!protected ------------------------------------------------------------------------------------
    669 ''
    670 {{{#!asm
    671 K    12:     <boot>:--------------------------------------------------------------------------------
    672 K    12:     0xbfc00000 0x3c04bfc0  lui a0,0xbfc0
    673 K    13:     0xbfc00004 0x24840028  addiu   a0,a0,40
    674 K    14:     0xbfc00008 0x3c05d020  lui a1,0xd020
    675 K    15:     0xbfc0000c 0x24a50000  addiu   a1,a1,0
    676 K    26:     <print>:-------------------------------------------------------------------------------
    677 K    26:     0xbfc00010 0x80880000  lb  t0,0(a0)
    678 K    27:     0xbfc00014 0xa0a80000  sb  t0,0(a1)
    679 K    37:     --> READ  MEMORY @ 0xbfc00028 BE=---1 --> 0x6c6c6548
    680 K    39:     <-- WRITE MEMORY @ 0xd0200000 BE=---1 <-- 0x48
    681 }}}
    682 ''
    683 }}}
    684 - Modifiez le code de `hcpua.S` afin d'afficher le message "Au revoir\n" après le message "Hello".\\
    685   Vous devez avoir deux messages, et pas seulement étendre le premier.
    686 {{{#!protected ------------------------------------------------------------------------------------
    687 ''
    688 * Il faut dupliquer la boucle d'affichage et le message. Il faut juste faire attention aux labels en en créant des nouveaux. On ne peut pas utiliser des fonctions parce qu'il n'y a pas encore de pile d'exécution.
    689 ''
    690 }}}
    691 
    692 
    693 
    694 == B2. Saut dans le code du noyau en assembleur
    695 
    696 
    697 
    698 Dans le deuxième programme, nous restons en assembleur, mais nous avons deux fichiers source : (1) le fichier contenant
    699 le code de boot et (2) le fichier contenant le code du noyau. Ici, le code du noyau c'est juste une ''fonction'' `kinit()`. Ce n'est pas vraiment une fonction car on n'utilise pas la pile.
    700 
    701 **Objectifs**
    702 
    703 - Savoir comment le programme de boot fait pour sauter à l'adresse de la routine kinit.
    704 - Avoir un fichier kernel.ld un peu plus complet.
    705  
    706 **Fichiers**
    707 
    708 {{{
    709 2_init_asm/
    710 ├── hcpua.S      : code dépendant du cpu matériel en assembleur
    711 ├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
    712 ├── kinit.S      : fichier contenant le code de démarrage du noyau, ici c'est une routine kinit.
    713 └── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
    714 }}}
    715 
    716 **Questions**
    717 
    718 1. Regarder dans le fichier `hcpua.S`, dans quelle section est désormais le code de boot ?
    719 {{{#!protected ------------------------------------------------------------------------------------
    720 ''
    721 - Il a été placé dans la section de `.boot`
    722 ''
    723 }}}
    724 2. Le code de boot ne fait que sauter à l'adresse `kinit` avec l'instruction `jr`,
    725    il n'y a pas de retour, ce n'est donc pas un `jal`. Où est défini `kinit` ?
    726    Comment le code de boot connait-il cette adresse ?
    727    Pourquoi ne pas avoir utilisé `j init` et donc pourquoi passer par un registre ?
    728 {{{#!protected ------------------------------------------------------------------------------------
    729 ''
    730 - `kinit` est défini dans la `kinit.S`.
    731 - `hcpua.S` ne connait pas cette adresse, mais grâce au `.globl kinit`, l'éditeur de lien saura compléter `hcpua.o`, dans l'exécutable.
    732 - Le code de boot est en `0xBFC00000`, `kinit` est en `0x80000000`, ces deux adresses ne partagent pas les 4 bits de poids fort, c'est trop loin pour un simple `j`.
    733 ''
    734 }}}
    735 1. Dans `kernel.ld`, que signifie `*(.*data*)` ?
    736 {{{#!protected ------------------------------------------------------------------------------------
    737 ''
    738 - C'est une manière de désigner toutes les sections nommées `.*data*` (avec `*` = n'importe quoi)
    739   présentes dans n'importe quel fichier objets reçu par l'éditeur de liens.
    740 ''
    741 }}}
    742 1. Quelle est la valeur de `__kdata_end` ? Pourquoi mettre 2 «`_`» au début des variables du `ldscript` ? ([https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html réponse])
    743 {{{#!protected ------------------------------------------------------------------------------------
    744 ''
    745 - `__kdata_end` est l'adresse du premier octet placé juste après la région data.
    746 - les 2 «`_`» permettent d'éviter les conflits avec les noms des symboles (fonction, variable, type, etc.)
    747   présents dans le programme.
    748 ''
    749 }}}
    750 
    751 **Exercices**
    752 
    753 - Exécutez le programme sur le simulateur. Est-ce différent de l'étape 1 ?
    754 {{{#!protected ------------------------------------------------------------------------------------
    755 ''
    756 * Non, c'est le même comportement.
    757 ''
    758 }}}
    759 - Modifiez le code, comme pour l'étape 1, afin d'afficher un second message ?
    760 {{{#!protected ------------------------------------------------------------------------------------
    761 ''
    762 * C'est très semblable, c'est même identique à l'étape 1, l'idée est d'ouvrir le code...
    763 ''
    764 }}}
    765 
    766 
    767 
    768 == B3. Saut dans la fonction kinit() du noyau en langage C
    769 
    770 
    771 
    772 Dans ce troisième programme, nous faisons la même chose que pour le deuxième mais `kinit()` est désormais écrit en
    773 langage C. Cela change peu de choses, sauf une chose importante `kinit()` est une fonction et donc il faut absolument
    774 une pile d'exécution.
    775 
    776 **Objectifs**
    777 
    778 - Savoir comment et où déclarer la pile d'exécution du noyau.
    779 - Savoir comment afficher un caractère sur un terminal depuis un programme C.
    780 
    781 **Fichiers**
    782 
    783 {{{
    784 3_init_c/
    785 ├── hcpua.S      : code dépendant du cpu matériel en assembleur
    786 ├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
    787 ├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
    788 └── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
    789 }}}
    790 
    791 **Questions**
    792 
    793 1. Quand faut-il initialiser la pile ? Dans quel fichier est-ce ? Quelle est la valeur du pointeur initial ?
    794 {{{#!protected ------------------------------------------------------------------------------------
    795 ''
    796 - Il faut initialiser le pointeur avant d'appeler `kinit()`
    797 - C'est dans le fichier `hcpua.S`
    798 - `$29` ← `__kdata_end`, c'est-à-dire `0x80400000`
    799 ''
    800 }}}
    801 1. Dans quel fichier le mot clé `volatile` est-il utilisé ? Rappeler son rôle.
    802 {{{#!protected ------------------------------------------------------------------------------------
    803 ''
    804 * Il est utilisé dans `kinit.c` pour informer le compilateur que la variable `__tty_regs_map` doit toujours être lue en mémoire et ne peut jamais être "optimisée" dans un registre. Les écritures dans la doivent aussi toujours avoir lieu. Cette variable désigne les registres du contrôleur de terminal. Quand le programme accède en lecture ou écriture à cette variable, il veut accéder au terminal, il faut vraiment qu'il y ait des load/store dans le programme assembleur correspondant au programme source.
    805 ''
    806 }}}
    807 
    808 **Exercices**
    809 
    810 - Exécutez le programme sur le simulateur. Est-ce différent de l'étape 1 ?
    811 {{{#!protected ------------------------------------------------------------------------------------
    812 ''
    813 * Non, c'est le même comportement :-)
    814 ''
    815 }}}
    816 - Ouvrez les fichiers `kinit.o.s` et `kernel.x.s`, le premier fichier est le désassemblage de `kinit.o` et le second est le désassemblage de `kernel.x`. Dans ces fichiers, vous avez plusieurs sections. Les sections `.MIPS.abiflags`, `.reginfo` et `.pdr` ne nous sont pas utiles (elles servent au chargeur d'application, elles contiennent des informations sur le contenu du fichier et cela ne nous intéresse pas).\\Notez l'adresse de `kinit` dans les deux fichiers, sont-ce les mêmes ? Sont-elles dans les mêmes sections ? Expliquez pourquoi.
    817 {{{#!protected ------------------------------------------------------------------------------------
    818 ''
    819 * Dans kinit.o.s, l'adresse de `kinit` est `0` alors que `kernel.x.s` l'adresse est `0x80000000`.
    820 * Dans kinit.o.s, `kinit` est dans la section `.text` alors que dans `kernel.x.s` `kinit` est dans la section `.ktext`.
    821 * La raison est que
    822   * dans `kinit.o`, `kinit` n'a pas encore été placé, le compilateur commence toutes ses sections à 0, donc `kinit` est dans la section `.text` et elle commence à 0.
    823   * dans `kernel.x.s` `kinit` est placé et mis dans la section `.ktext` comme le fichier `kernel.ld` le demande.
    824 ''
    825 }}}
    826 - Modifiez le code de `kinit.c`, et comme pour l'étape 1, afficher un second message ?
    827 {{{#!protected ------------------------------------------------------------------------------------
    828 ''
    829 * Hormis, qu'il s'agit de code C, il n'y a pas de différence de principe, c'est toujours du copier-coller, l'important c'est d'ouvrir le code.
    830 ''
    831 }}}
    832 
    833 
    834 
    835 == B4.  Accès aux registres de contrôle des terminaux `TTY`
    836 
    837 
    838 
    839 Le prototype de SoC que nous utilisons pour les TP est configurable. Il est possible par exemple de choisir le nombre de terminaux texte (TTY). Par défaut, il y en a un mais, nous pouvons en avoir jusqu'à 4. Nous allons modifier le code du noyau pour s'adapter à cette variabilité. En outre, pour le moment, nous ne faisions qu'écrire sur le terminal, maintenant, nous allons aussi lire le clavier.
    840 
    841 **Objectifs**
    842 
    843 - Savoir comment compiler un programme C avec du code conditionnel.
    844 - Savoir comment décrire en C l'ensemble des registres d'un contrôleur de périphérique et y accéder.
    845  
    846 **Fichiers**
    847 
    848 {{{
    849 4_nttys/
    850 ├── hcpua.S      : code dépendant du cpu matériel en assembleur
    851 ├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
    852 ├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
    853 └── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
    854 }}}
    855 
    856 **Questions**
    857 
    858 1. Dans le fichier `kinit.c`, il est question d'un loopback, à quoi cela sert-il ?
    859 {{{#!protected ------------------------------------------------------------------------------------
    860 ''
    861 - Par défaut les caractères tapés au clavier ne sont pas affichés par le matériel. Or, l'utilisateur s'attend au fait que s'il tape sur une touche, celle-ci s'affiche. Comme ce n'est pas le matériel qui le fait c'est au logiciel de le faire.
    862 - Le fait que ce soit le logiciel qui fait cette opération de loopback permet de savoir que le programme tourne. Si un caractère s'affiche quand on tape au clavier, c'est qu'un morceau du programme a lu le registre `read` et réécrit cette valeur dans le registre `write`.
    863 ''
    864 }}}
    865 1. Dans le fichier `kinit.c`, on trouve `__tty_regs_map[ tty%NTTYS ].write = *s`, expliquez le modulo.
    866 {{{#!protected ------------------------------------------------------------------------------------
    867 ''
    868 * C'est une sécurité, un peu inutile ici, qui permet de ne pas écrire en dehors de la zone des registres du contrôleur de tty
    869 ''
    870 }}}
    871 1. Exécutez le programme sur le simulateur. Qu'observez-vous ? Est-ce que les deux fenêtres ont le même comportement vis-à-vis du clavier ?
    872 {{{#!protected ------------------------------------------------------------------------------------
    873 ''
    874 - Deux fenêtres sont apparues avec un message uniquement dans la fenêtre `proc0_term0`.
    875 - Non. Quand on tape dans la fenêtre `proc0_term0`, les touches tapées s'affichent alors que rien ne se passe dans la fenêtre `proc0_term1`.
    876 ''
    877 }}}
    878 
    879 **Exercices**
    880 
    881 - Modifiez le code pour afficher un message sur le second terminal, il y a toujours une attente sur le premier terminal.
    882 {{{#!protected ------------------------------------------------------------------------------------
    883 ''
    884 - C'est juste deux copier-coller (message et boucle d'affichage).
    885 ''
    886 }}}
    887 - Modifiez le code pour que le programme affiche les touches tapées au clavier sur les deux terminaux. C'est-à-dire, ce que vous tapez sur le terminal `proc0_term0` s'affiche sur ce même terminal, et pareil pour `proc0_term1`. L'idée est de ne plus faire d'attente bloquante sur le registre `TTY_STATUS` de chaque terminal. Pour que cela soit plus amusant, changez la casse (minuscule ←→ majuscule) sur le terminal `proc1_term1` (si vous tapez `bonjour 123`, il affiche `BONJOUR 123` et inversement.
    888 {{{#!protected ------------------------------------------------------------------------------------
    889 ''
    890 {{{#!c
    891 void kinit (void)
    892 {
    893     char *s;
    894     char c;
    895 
    896     for (s = hello_0; *s; s++) {                // for all char in hello string
    897         __tty_regs_map[ 0%NTTYS ].write = *s;   // write it to the tty output register
    898     }
    899 
    900     for (s = hello_1; *s; s++) {                // for all char in hello string
    901         __tty_regs_map[ 1%NTTYS ].write = *s;   // write it to the tty output register
    902     }
    903 
    904     while (1) {
    905 
    906         if (__tty_regs_map[0].status != 0) {    // wait for a char typed on keyboard 0
    907             c = __tty_regs_map[0].read;         // read the char
    908             __tty_regs_map[0].write = c;        // and print it (that is a software loopback)
    909         }
    910         if (__tty_regs_map[1].status != 0) {    // wait for a char typed on keyboard 1
    911             c = __tty_regs_map[1].read;         // read the char
    912             if ((c >= 'a') && (c <= 'z'))
    913                 c += 'A' - 'a';
    914             else if ((c >= 'A') && (c <= 'Z'))
    915                 c += 'a' - 'A';
    916             __tty_regs_map[1].write = c;        // and print it (that is a software loopback)
    917         }
    918 
    919     }
    920 
    921     while (1);
    922 }}}
    923 ''
    924 }}}
    925 
    926 
    927 
    928 == B5. Premier petit pilote pour le terminal
    929 
    930 
    931 
    932 Dans l'étape précédente, nous accédons aux registres de périphérique directement dans la fonction `kinit()`, ce n'est pas très simple. C'est pourquoi nous allons ajouter un niveau d'abstraction qui représente un début de pilote de périphérique (device driver). Ce pilote, même tout petit constitue une couche logicielle avec une API.
    933 
    934 **Objectifs**
    935 
    936 - Savoir comment créer un début de pilote pour le terminal `TTY`.
    937 - Savoir comment décrire une API en C
    938 - Savoir appeler une fonction en assembleur depuis le C
    939  
    940 **Fichiers**
    941 
    942 {{{
    943 5_driver/
    944 ├── harch.c      : code dépendant de l'architecture du SoC, pour le moment c'est juste le pilote du TTY
    945 ├── harch.h      : API du code dépendant de l'architecture
    946 ├── hcpu.h       : prototype de la fonction clock()
    947 ├── hcpua.S      : code dépendant du cpu matériel en assembleur
    948 ├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
    949 ├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
    950 └── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
    951 }}}
    952    
    953 **Questions**
    954 
    955 1. Le code du driver du TTY est dans le fichier `harch.c` et les prototypes sont dans `harch.h`. Si vous ouvrez `harch.h` vous allez voir que seuls les prototypes des fonctions `tty_gets()` et `tty_puts()` sont présents. La structure décrivant la carte des registres du `TTY` est déclarée dans le `.c`. Pourquoi avoir fait ainsi ?
    956 {{{#!protected ------------------------------------------------------------------------------------
    957 ''
    958 - Le noyau n'a pas besoin de savoir comment sont organisés les registres dans le TTY. Il a juste besoin de savoir comment écrire ou lire un message. Plus c'est cloisonné, moins il y a de risque de problèmes. En outre, cela simplifie un hypothétique portage sur une autre architecture.
    959 ''
    960 }}}
    961 1. Le MIPS dispose d'un compteur de cycles internes. Ce compteur est dans un banc de registres accessibles uniquement quand le processeur fonctionne en mode `kernel`. Nous verrons ça au prochain cours, mais en attendant nous allons quand même exploiter ce compteur. Pourquoi avoir mis la fonction dans `hcpua.S` ? Rappeler, pourquoi avoir mis `.globl clock`
    962 {{{#!protected ------------------------------------------------------------------------------------
    963 ''
    964 * La fonction qui lit ce registre (Attention ce `$9` ne désigne pas un registre GPR du processeur !) est nécessairement en assembleur car elle utilise des instructions particulières et dépend du matériel, elle est donc mise dans `hcpua.S`.
    965 * `.globl clock` permet de faire en sorte que la fonction soit visible par les autres fichiers C.
    966 ''
    967 }}}
    968 1. Compilez et exécutez le code avec `make exec`. Observez. Ensuite ouvrez le fichier `kernel.x.s` et regardez où a été placée la fonction `clock()`.\\Est-ce un problème si `kinit()` n'est plus au début du segment `ktext` ? Posez-vous la question de qui a besoin de connaître l'adresse de `kinit()`
    969 {{{#!protected ------------------------------------------------------------------------------------
    970 ''
    971 * Non, ce n'est pas un problème puisque ça fonctionne. Le code de boot a besoin de l'adresse de `kinit()` mais on l'obtient avec la macro `la` - c'est l'éditeur de lien qui fera en sorte que dans les codes binaire l'adresse de `kinit()` mise dans le registre `$26` soit la bonne.
    972 ''
    973 }}}
    974 
    975 **Exercices**
    976 
    977 - Afin de vous ''détendre un peu'', vous allez créer un petit jeu `guess`
    978   - `guess` tire un nombre entre '0' et '9' et vous devez le deviner en faisant des propositions.\\`guess` vous dit si c'est trop grand ou trop petit. Ce programme ne va révolutionner votre vie de programmeur(se), mais bon, c'est probablement le premier programme que vous allez écrire et faire tourner sur une machine sans système d'exploitation.
    979  __Étapes__
    980   - Vous créez deux fichiers `guess.c` et `guess.h`.
    981     - `guess.c` contient le jeu il y au moins une fonction `guess()`
    982     - `guess.h` contient les déclarations externes de `guess.c`
    983   - `kinit()` doit lancer `guess()`
    984   - `guess()`
    985      - vous demande de taper une touche pour démarrer le jeu.
    986      - effectue un tirage d'un nombre en utilisant la fonction `clock()` et ne gardant que le chiffre de poids faible (ce n'est pas aléatoire, mais c'est mieux que rien)
    987      - exécute en boucle jusqu'à réussite
    988        - demande d'un chiffre
    989        - comparaison avec le tirage et affichage des messages `"trop grand"`, `"trop petit"` ou `"gagné"`
    990   - Vous devrez modifier le Makefile puisque vous avez un fichier à compiler en plus.
    991   - Si c'est trop facile, vous pouvez complexifier en utilisant des nombres à 2 chiffres ou plus.
    992 {{{#!protected
    993 [htdocs:files/6_guess.tgz 6_guess.tgz à décompresser dans tp1]\\\\
    994 **kinit.h**
    995 {{{#!c
    996 #include <harch.h>
    997 #include <guess.h>
    998 
    999 char hello[] = "Hello World!\n\n";
    1000 char end[] = "\nend!\n";
    1001 
    1002 void kinit (void)
    1003 {
    1004     tty_puts (0, hello);         // print hello string
    1005     guess();
    1006     tty_puts (0, end);           // print end string on terminal 0
    1007     while (1);                   // infinite loop
    1008 }
    1009 }}}
    1010 **guess.h**
    1011 {{{#!c
    1012 #ifndef _GUESS_H_
    1013 #define _GUESS_H_
    1014 
    1015 extern void guess (void);
    1016 
    1017 #endif
    1018 }}}
    1019 **guess.c**
    1020 {{{#!c
    1021 #include <guess.h>
    1022 #include <harch.h>
    1023 #include <hcpu.h>
    1024 
    1025 void guess (void)
    1026 {
    1027     char c;
    1028     int num;
    1029     int random;
    1030 
    1031     do {
    1032         random = clock() % 10;  // only one digit
    1033         do {
    1034             do {
    1035                 tty_puts(0,"Donnez un nombre : ");
    1036                 c = tty_getc(0);
    1037             } while ( (c < '0') && (c > '9') );
    1038             num = c - '0';
    1039 
    1040             if (num < random)
    1041                 tty_puts(0," -> trop petit\n");
    1042             else if (num > random)
    1043                 tty_puts(0," -> trop grand\n");
    1044 
    1045         } while (random != num);
    1046 
    1047         tty_puts(0,"\nGagné\n");
    1048         tty_puts(0,"\nOn recommence [Y]/N ? ");
    1049         c = tty_getc(0);
    1050         tty_puts(0,"\n\n");
    1051 
    1052     } while (c != 'N');
    1053 }
    1054 }}}
    1055 }}}