Changes between Initial Version and Version 1 of Archi-1-TD9


Ignore:
Timestamp:
Dec 4, 2021, 8:18:26 AM (4 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Archi-1-TD9

    v1 v1  
     1**
     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]]**
     15Codes (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]__ ]
     20**
     21{{{#!html
     22<h1> <font size="+2"> Boot et premier programme en mode kernel
     23</font></h1>
     24}}}
     25
     26Cette 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.
     27La 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
     48kO6/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
     65
     66
     67Les trois figures ci-dessous donnent des informations sur l'architecture du prototype **almo1** sur lequel vous allez travailler.
     68* À gauche, vous avez un schéma simplifié.
     69* Au centre, vous avez la représentation des 4 registres internes du contrôleur de terminal `TTY` nécessaires pour commander un couple écran-clavier.
     70* À droite, vous avez la représentation de l'espace d'adressage du prototype. 
     71
     72[[Image(htdocs:img/almo1.png,nolink,height=300)]]
     73[[Image(htdocs:img/TTY.png,nolink,height=200,top)]]
     74[[Image(htdocs:img/espace_adresse.png,nolink,height=300)]]
     75
     76
     77**Questions**
     78
     791. Il y a deux mémoires dans **almo1** : RAM et ROM. Qu'est-ce qui les distinguent et que contiennent-elles ?
     80{{{#!protected ------------------------------------------------------------------------------------
     81''
     82Cours 9 / slides 6 et 9
     83* La ROM est une mémoire morte, c'est-à-dire en lecture seule. Elle contient le code de démarrage du prototype.
     84* La RAM est une mémoire vive, c'est-à-dire pouvant être lue et écrite. Elle contient le code et les données.
     85''
     86}}}
     871. Qu'est-ce l'espace d'adressage du MIPS ? Quelle taille fait-il ?\\Quelles sont les instructions du MIPS permettant d'utiliser ces adresses ? Est-ce synonyme de mémoire ?
     88{{{#!protected ------------------------------------------------------------------------------------
     89''
     90Cours 9 / slide 7
     91* L'espace d'adressage du MIPS est l'ensemble des adresses que peut former le MIPS.
     92* Les adresses sont sur 32 bits et désignent chacune un octet, il y a donc 2^32^ octets.
     93* On accède à l'espace d'adressage avec les instructions load/store (`lw`, `lh`, `lb`, `lhu`, `lbu`, `sw`, `sh`, `sb`).
     94* Non, les mémoires sont des composants contenant des cases de mémoire adressable. Les mémoires sont placées (on dit aussi « ''mappées'' » dans l'espace d'adressage).
     95''
     96}}}
     971. Dans quel composant matériel se trouve le code de démarrage et à quelle adresse est-il placé dans l'espace d'adressage et pourquoi à cette adresse ?
     98{{{#!protected ------------------------------------------------------------------------------------
     99''
     100Cours 9 / slide 6 et 7
     101* Le code de boot est dans la mémoire ROM.
     102* Il commence à l'adresse `0xBFC00000` parce que c'est l'adresse qu'envoie le MIPS au démarrage.
     103''
     104}}}
     1051. Quel composant permet de faire des entrées-sorties dans almo1 ?\\Citez d'autres composants qui pourraient être présents dans un autre SoC ?
     106{{{#!protected ------------------------------------------------------------------------------------
     107''
     108Cours 9 / slide 6 + connaissances personnelles
     109* Ici, c'est le composant `TTY` qui permet de sortir des caractères sur un écran et de lire des caractères depuis un clavier.
     110* Dans un autre SoC, on pourrait avoir un contrôleur de disque, un contrôleur vidéo, un port réseau Ethernet, un port USB, des entrées analogiques (pour mesurer des tensions), etc.
     111''
     112}}}
     1131. Il y a 4 registres dans le contrôleur de `TTY`, à quelles adresses sont-ils placés dans l'espace d'adressage ?\\Comme ce sont des registres, est-ce que le MIPS peut les utiliser comme opérandes pour ses instructions (comme add, or, etc.) ?\\Dans quel registre faut-il écrire pour envoyer un caractère sur l'écran du terminal (implicitement à la position du curseur) ?\\Que contiennent les registres `TTY_STATUS` et `TTY_READ` ?\\Quelle est l'adresse de `TTY_WRITE` dans l'espace d'adressage ?
     114{{{#!protected ------------------------------------------------------------------------------------
     115''
     116Cours 9 / slide 10
     117* Le composant `TTY` est placé à partir de l'adresse `0xD0200000`.
     118* Non, ce sont des registres de périphériques placés dans l'espace d'adressage et donc accessibles par des instructions load/store uniquement.
     119* Pour écrire un caractère sur l'écran, il faut écrire le code ASCII du caractère dans le registre `TTY_WRITE`
     120* `TTY_STATUS` contient 1 s'il y a au moins un caractère en attente d'être lu, `TTY_READ` contient le code ASCII du caractère tapé au clavier si `TTY_STATUS==1`
     121''
     122}}}
     1231. Le contrôleur de `TTY` peut contrôler de 1 à 4 terminaux. Chaque terminal dispose d'un ensemble de 4 registres (on appelle ça une carte de registres, ou en anglais une ''register map''). Ces ensembles de 4 registres sont placés à des adresses contiguës. S'il y a 2 terminaux (`TTY0` et `TTY1`), à quelle adresse est le registre `TTY_READ` de `TTY1` ?
     124{{{#!protected ------------------------------------------------------------------------------------
     125''
     126Cours 9 / slide 10
     127* Si les adresses utilisées par `TTY0` commencent à `0xd0200000` alors celles de `TTY1` commencent à l'adresse `0xd0200010` et donc `TTY_READ` est à l'adresse `0xd0200018`.
     128''
     129}}}
     1301. Que représentent les flèches bleues sur le schéma ? Pourquoi ne vont-elles que dans une seule direction ?
     131{{{#!protected ------------------------------------------------------------------------------------
     132''
     133Cours 9 / slide 11
     134* Ces flèches représentent les requêtes d'accès à la mémoire, c'est-à-dire les ''loads'' et les ''stores'' qui sont émis par le MIPS lors de l'exécution des instructions `lw`, `sw`, etc. Les requêtes sont émises par le MIPS et reçues par les composants mémoires ou périphériques.
     135* On ne représente pas les données qui circulent, mais juste les requêtes, pour ne pas alourdir inutilement le schéma. Implicitement, si le MIPS envoie une requête de lecture alors il y aura une donnée qui va revenir, c'est obligatoire, alors on ne la dessine pas, car ce n'est pas intéressant. En revanche, le fait que le MIPS soit le seul composant à émettre des requêtes est une information intéressante.
     136''
     137}}}
     138
     139
     140== A2. Programmation assembleur
     141 
     142
     143L'usage du code assembleur est réduit au minimum. Il est utilisé uniquement où c'est indispensable. C'est le cas du code de démarrage. Ce code ne peut pas être écrit en C pour au moins une  raison importante. Le compilateur C suppose la présence d'une pile et d'un registre du processeur contenant le pointeur de pile, or au démarrage les registres sont vides (leur contenu n'est pas significatif). Dans cette partie, nous allons nous intéresser à quelques éléments de l'assembleur qui vous permettront de comprendre le code en TP.
     144
     145**Questions**
     146
     1471. Nous savons que l'adresse du premier registre du `TTY` est `0xd0200000` est qu'à cette adresse se trouve le registre `TTY_WRITE` du `TTY0`.\\Écrivez le code permettant d'écrire le code ASCII `'x'` sur le terminal 0. Vous avez droit à tous les registres du MIPS puisqu'à ce stade il n'y pas de conventions sur leur utilisation.
     148{{{#!protected ------------------------------------------------------------------------------------
     149''
     150Cours 9 / slide 10\\\\
     151Ce qu'il faut bien comprendre, c'est que l'adresse du registre TTY_WRITE est l'adresse d'une __sortie du SoC__, ce n'est pas une mémoire à proprement parler. Il est d'ailleurs interdit de lire à cette adresse. Pour écrire un message à l'écran, il faut écrire tous les caractères du message à cette adresse (0xD0200000). En principe, entre chaque écriture, il faut attendre un peu que le caractère précédent soit parti, parce que le débit du port de sortie matériel (USB par exemple) est beaucoup plus lent que ce que peut faire le processeur. Dans notre cas, c'est un simulateur de SoC et les caractères sont envoyés vers un terminal sans délai. Dans ce cas, il n'est pas nécessaire d'attendre.
     152{{{#!asm
     153lui   $4, 0xD020
     154ori   $4, $4, 0x0000   // cette instruction ne sert a rien puisqu on ajoute 0, mais je la mets pour le cas general
     155ori   $5, $0, 'x'
     156sb    $5, 0($4)        // Notez que l immediat 0 devant ($4) n est pas obligatoire mais on s obligera als le mettre
     157}}}
     158''
     159}}}
     1601. Un problème avec le code précédent est que l'adresse du `TTY` est un choix de l'architecte du prototype et s'il décide de placer le `TTY` ailleurs dans l'espace d'adressage, il faudra réécrire le code. Il est préférable d'utiliser une étiquette pour désigner cette adresse : on suppose désormais que l'adresse du premier registre du `TTY`  se nomme `__tty_regs_map`. Le code assembleur ne connait pas l'adresse, mais il ne connaît que le symbole. Ainsi, pour écrire `'x'` sur le terminal 0, nous devons utiliser la macro instruction `la $r, label`. Cette macro-instruction est remplacée lors de l'assemblage du code par une suite composée de deux instructions `lui` et `ori`. Il existe aussi la macro instruction `li` qui demande de charger une valeur sur 32 bits dans un registre. Pour être plus précis, les macro-instructions
     161{{{#!asm
     162la $r, label
     163li $r, 0x87654321
     164}}}
     165 sont remplacées par
     166{{{#!asm
     167lui $r, label>>16
     168ori $r, $r, label & 0xFFFF
     169lui $r, 0x8765
     170ori $r, $r, 0x4321
     171}}}
     172 Réécrivez le code de la question précédente en utilisant `la` et `li`
     173{{{#!protected ------------------------------------------------------------------------------------
     174''
     175Cours 9 / slide 19\\\\
     176Il suffit de remplacer les instructions `lui` et `ori` par `la` et `li`.
     177{{{#!asm
     178la    $4, __tty_regs_map
     179li    $5, 'x'
     180sb    $5, 0($4)
     181}}}
     182''
     183}}}
     1841. En assembleur pour sauter à une adresse de manière inconditionnelle, on utilise les instructions `j label` et `jr $r`. Ces instructions permettent-elles d'effectuer un saut à n'importe quelle adresse ?
     185{{{#!protected ------------------------------------------------------------------------------------
     186''
     187La réponse n'est pas dans le cours, mais dans la connaissance du codage des instructions de saut (`j`ump et `b`ranch). Il faut avoir compris que l'instruction `j` et toutes les branchements (`bne`, `beq`, etc.) sont relatives au PC. Il n'est pas possible d'aller n'importe où dans l'espace d'adressage.
     188* `j label` malgré sa forme assembleur effectue un saut relativement au `PC` puisque le `label` n'est pas entièrement encodé dans l'instruction binaire (cf. cours sur les sauts). Cette instruction réalise :\\`PC ← (PC & 0xF0000000) | (ZeroExtended(label, 32) << 2)`\\Les 4 bits de poids forts du `PC` sont conservés, le saut est bien relatif au PC\\(`ZeroExtended` désigne ici le fait d'étendre le label sur 32 bits en ajoutant des zéros en tête).\\ Autrement dit, si `j label` est à l'adresse `PC`, l'adresse `label` doit avoir le même chiffre de poids fort (en hexa décimal), c'est-à-dire les mêmes 4 bits de poids fort en binaire). Sinon l'assembleur provoque une erreur lors du codage.
     189* A l'inverse, `jr $r` effectue un saut absolu puisque cette instruction réalise `PC ← $r`
     190
     191Autrement dit, si l’on veut aller exécuter du code n'importe où en mémoire, il faut utiliser `jr`.
     192''
     193}}}
     1941. Vous avez utilisé les directives `.text` et `.data` pour définir les sections où placer les instructions et les variables globales, mais il existe la possibilité de demander la création d'une nouvelle section dans le code objet produit par le compilateur avec la directive `.section name,"flags"`
     195   - `name` est le nom de la nouvelle section.  On met souvent un `.name` (avec un `.`au début) pour montrer que c'est une section et
     196   - `"flags"` informe sur le contenu : `"ax"` pour des instructions, `"ad"` pour des données (ceux que ça intéresse pourront regarder le manuel de l'assembleur [https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as_7.html#SEC119 Assembleur/Directives/.section])
     197 Écrivez le code assembleur créant la section `".mytext"` et suivi de l'addition des registres `$5` et `$6` dans `$4`
     198{{{#!protected ------------------------------------------------------------------------------------
     199''
     200Cours 9 / slide 19\\\\
     201Pour répondre, il faut avoir compris l'explication donnée dans la question. L'intérêt de cette question est de revenir sur la notion de section. Une section est un segment d'adresses ayant un but spécifique. On définit des segments d'adresses pour le code, pour les données (il y a d'ailleurs plusieurs types de sections en fonction du type de données). Ces segments sont placés dans l'espace d'adressage par l'éditeur de liens (`ld`) et la manière dont ils sont placés est définie dans un fichier donné en paramètre de l'éditeur de lien, ce fichier de description de placement est le `ldscript`).
     202{{{#!as
     203 .section .mytext,"ax"
     204 addu $4,$5,$6
     205}}}
     206''
     207}}}
     2081. À quoi sert la directive `.globl label` ?
     209{{{#!protected ------------------------------------------------------------------------------------
     210''
     211Cours 9 / slide 19\\\\
     212Ce qu'il faut comprendre, c'est que les comportements du `C` et de l'assembleur sont inversés vis-à-vis des labels. Dans un fichier `.c`, quand on définit un label (une fonction ou variable), ce label est par défaut `extern`, c'est-à-dire qu'il est utilisable dans un autre fichier `.c`. Si on veut que le label ne soit utilisable que dans le fichier dans lequel il est défini, il faut utiliser le mot clé `static` lors de sa déclaration. En assembleur, c'est l'inverse, les labels sont par défaut `static`, c'est-à-dire utilisable uniquement dans le fichier où ils sont définis. Si on veut qu'ils soient utilisables dans les autres fichiers, il faut le dire avec la directive `.globl`.
     213* `globl` signifie `glob`al `l`abel. Cette directive permet de dire que le `label` est visible en dehors de son fichier de définition. Ainsi il est utilisable dans les autres fichiers assembleur ou les autres fichier C du programme.
     214''
     215}}}
     2161. Écrivez une séquence de code qui affiche la chaîne de caractère `"Hello"` sur `TTY0`. Ce n'est pas une fonction et vous pouvez utiliser tous les registres que vous voulez. Vous supposez que `__tty_regs_maps` est déjà défini.
     217{{{#!protected ------------------------------------------------------------------------------------
     218''
     219Il faut laisser le temps d'écrire ce programme. Si la notion de section est comprise, si les macros `li` et `la` sont comprises, si l'écriture d'une boucle en assembleur est comprise, si l'usage du registre `write` du TTY est compris, alors c'est très facile, et si on donne la correction trop vite alors même ceux qui n'ont pas tout compris vont trouver ça évident.\\
     220Si les étudiants ne démarrent pas alors on peut donner la section `.data` et l'initialisation des registres `$4` et `$5`.
     221{{{#!asm
     222.data
     223hello:  .asciiz "Hello"
     224.text
     225    la      $4, hello                   // $4 <- address of string
     226    la      $5, __tty_regs_map          // $5 <- address of tty's registers map
     227
     228print:
     229    lb      $8, 0($4)                   // get current char
     230    sb      $8, 0($5)                   // send the current char to the tty
     231    addiu   $4, $4, 1                   // point to the next char
     232    bne     $8, $0, print               // check that it is not null, if ok it must be printed
     233}}}
     234''
     235}}}
     2361. En regardant le dessin de l'espace d'adressage du prototype **almo1** (plus haut et sur le slide 7 du cours 9), dites à quelle adresse devra être initialisé le pointeur de pile **pour le kernel**. Rappelez pourquoi c'est indispensable de le définir avant d'appeler une fonction C et écrivez le code qui fait l'initialisation, en supposant que l'adresse du pointeur de pile a pour nom `__kdata_end`.
     237{{{#!protected ------------------------------------------------------------------------------------
     238''
     239On parle ici du pointeur de pile pour le `kernel`, on doit mettre le pointeur de pile dans la section des données du kernel. Ici, il n'y en a qu'une c'est `.kdata`. La pile est mise en haut de cette de cette section aux adresses les plus grandes. Les adresses du bas de la section sont occupées par les données globales du kernel.
     240* La pile va être initialisée juste à la première adresse au-delà de la zone kdata donc\\`__kdata_end = 0x80020000` + `0x003E0000` = `0x80400000`\\Oui, on initialise le pointeur de pile a une adresse qui est en dehors du segment! Ce n'est pas un problème parce que le compilateur C n'écrira jamais à cette adresse, car elle n'est pas dans le contexte de la fonction C. La première chose qu'il fera c'est décrémenter le pointeur de pile pour pour allouer le contexte de la fonction.
     241* En effet, la première chose que fait une fonction, c'est décrémenter le pointeur de pile pour écrire `$31`, etc. Il faut donc que le pointeur ait été défini avant d'entrer dans la fonction.
     242{{{#!asm
     243 la  $29, __kdata_end
     244}}}   
     245''
     246}}}
     247
     248== A3. Programmation en C
     249
     250
     251Vous savez déjà programmer en C, mais vous allez voir des syntaxes ou des cas d'usage que vous ne connaissez peut-être pas encore.
     252Les questions qui sont posées ici n'ont pas toutes été vues en cours, mais vous connaissez peut-être les réponses, sinon ce sera l'occasion d'apprendre.
     253
     254**Questions
     255
     2561. Quels sont les usages du mot clé `static` en C ?  (c'est une directive que l'on donne au compilateur C)
     257{{{#!protected ------------------------------------------------------------------------------------
     258''
     259Le cours 9 n'en parle pas, mais dans le code vous trouverez cette directive un peu partout. Il y a deux usages, on a déjà parlé du premier, mais pas encore du second.
     2601. Déclarer `static` une variable globale ou une fonction en faisant précéder leur définition du mot clé `static` permet de limiter la visibilité de cette variable ou de cette fonction au seul fichier de déclaration. Notez que par défaut les variables et les fonctions du C ne sont pas `static`, il faut le demander explicitement. C'est exactement l'inverse en assembleur où tout label est implicitement  `static` ; il faut demander avec la directive `.globl` de le rendre visible.
     2611. Déclarer `static` une variable locale permet de la rendre persistante, c'est-à-dire qu'elle conserve sa valeur entre deux appels. Cette variable locale n'est pas dans le contexte de la fonction (c'est-à-dire qu'elle n'est pas dans la pile parce que le contexte est libéré en sortie de fonction). Une variable locale `static` est en fait allouée comme une variable globale mais son usage est limité à la seule fonction où elle est définie.
     262''
     263}}}
     2641. Pourquoi déclarer des fonctions ou des variables `extern` ?
     265{{{#!protected ------------------------------------------------------------------------------------
     266''
     267Ça n'ont plus ce n'est pas dit dans le cours mais c'est sensé être connu, sinon c'est qu'il y a des choses à apprendre. Notez que la directive externe est implicite en C et qu'on peut donc ne pas l'écrire. On la met pour la lisibilité du code.
     268* Les déclarations `extern` permettent d'informer que le compilateur qu'une variable ou qu'une fonction existe et est définie ailleurs. Le compilateur connaît ainsi le type de la variable ou du prototype des fonctions, il sait donc comment les utiliser. En C, par défaut, les variables et les fonctions doivent être déclarées / leur existence et type doit être connus avant leur utilisation.
     269* Il n'y a pas de déclaration `extern` en assembleur parce que ce n'est pas un langage typé. Pour l'assembleur, un label c'est juste une adresse donc un nombre.
     270''
     271}}}
     2721. Comment déclarer un tableau de structures en variable globale ? La structure est nommée `test_s`, elle a deux champs `int` nommés `a` et `b`. Le tableau est nommé `tab` et a 2 cases.
     273{{{#!protected ------------------------------------------------------------------------------------
     274''
     275Là encore, ce sont des connaissances censées être connues, mais c'est important parce qu'on a besoin de le comprendre pour la déclaration des registres du TTY.
     276{{{#!c
     277struct test_s {
     278  int a;
     279  int b;
     280};
     281struct test_s tab[2];
     282}}}
     283''
     284}}}
     2851. Quelle est la différence entre `#include "file.h"` et `#include <file.h>` ? Quelle option du compilateur C permet de spécifier les répertoires lesquels se trouvent les fichiers include ?
     286{{{#!protected ------------------------------------------------------------------------------------
     287''
     288Cours 9 / slides 18 et 58\\\\
     289Ce sont toujours des connaissances connues en principe, mais comme c'est utilisé dans le code, ce n'est pas inutile d'en parler rapidement.
     290* Avec `#include "file.h"`, le préprocesseur recherche le fichier dans le répertoire local.
     291* Avec `#include <file.h>`, le préprocesseur recherche le fichier dans les répertoires standards tel que `/usr/include` et dans les répertoires spécifiés par l'option `-I` du préprocesseur. Il peut y avoir plusieurs fois `-I` dans la commande, par exemple `-Idir1 -Idir2 -Idir3`.
     292* C'est donc l'option `-I` qui permet de définir les répertoires de recherche.
     293''
     294}}}
     2951. Comment définir une macro-instruction C uniquement si elle n'est pas déjà définie ? Écrivez un exemple.
     296{{{#!protected ------------------------------------------------------------------------------------
     297''
     298Cours 9 / slides 9 et 58\\\\
     299Cette déclaration est présente à plusieurs endroits dans le code. Elle permet de définir des valeurs de paramètres par défaut, s'ils ne sont pas déjà définis ailleurs, soit plus haut dans le code, ou dans un fichier inclu, ou encore passé en paramètre du compilateur par l'option `-D`.
     300* En utilisant, une directive `#ifndef` :
     301{{{#!c
     302#ifndef MACRO
     303#define MACRO
     304#endif
     305}}}
     306''
     307}}}
     3081. Comment être certain de ne pas inclure plusieurs fois le même fichier `.h` ?
     309{{{#!protected ------------------------------------------------------------------------------------
     310''
     311Cours 9 / slides 9 et 58\\\\
     312C'est un usage de ce qui a été vu dans la question précédente. C'est utilisé dans tous les fichiers .h (sauf oubli).
     313* En utilisant ce que nous venons de voir dans la question précédente : on peut définir une macro instruction différente au début de chaque fichier `.h` (en utilisant le nom du fichier comme nom de macro pour éviter les collisions de nom). On peut alors tester l'existence de cette macro comme condition d'inclusion du fichier.
     314{{{#!c
     315————————————————————— debut du fichier filename.h
     316#ifndef _FILENAME_H_
     317#define _FILENAME_H_
     318
     319[... contenu du fichier ...]
     320
     321#endif
     322————————————————————— fichier de fichier filename.h
     323}}}
     324''
     325}}}
     3261. Supposons que la structure `tty_s` et le tableau de registres de `TTY` soient définis comme suit. Écrivez une fonction C `int getchar(void)` bloquante qui attend un caractère tapé au clavier sur le `TTY0`. Nous vous rappelons qu'il faut attendre que le registre `TTY_STATUS` soit différent de 0 avant de lire `TTY_READ`. `NTTYS` est un `#define` définit dans le Makefile de compilation avec le nombre de terminaux du SoC (en utilisant l'option `-D` de gcc).
     327{{{#!c
     328struct tty_s {
     329    int write;          // tty's output
     330    int status;         // tty's status something to read if not null)
     331    int read;           // tty's input
     332    int unused;         // unused
     333};
     334extern volatile struct tty_s __tty_regs_map[NTTYS];
     335}}}
     336{{{#!protected ------------------------------------------------------------------------------------
     337''
     338Cours 9 / slide 10\\\\
     339En principe, cela ne devrait pas poser de difficulté, mais cela nécessite d'avoir compris comment fonctionne le TTY et de savoir écrire une fonction en C. Pour aider, au cas où, on peut donner la description de ce qui est attendu:\\Tant que le registre `status` est à 0 alors attendre, puis lire le registre `read`.\\Notez que cela nécessite de savoir accéder aux champs d'une structure.
     340{{{#!c
     341int getchar(void)
     342{
     343   while (__tty_regs_map[0].status == 0);
     344   return __tty_regs_map[0].read;
     345}
     346}}}
     347''
     348}}}
     3491. Savez-vous à quoi sert le mot clé `volatile` ? Nous n'en avons pas parlé en cours, mais c'est nécessaire pour les adresses des registres de périphérique, une idée ... ?
     350{{{#!protected ------------------------------------------------------------------------------------
     351''
     352Ce n'est pas dit dans le cours, mais c'est un concept important. Quand le programme doit aller chercher une donnée dans la mémoire puis faire plusieurs calculs dessus, le compilateur optimise en réservant un registre du processeur pour cette variable afin de ne pas être obligé d'aller lire la mémoire à chaque fois. Mais, il y a des cas où ce comportement n'est pas souhaitable (il est même interdit). C'est le cas pour les données qui se trouvent dans les registres de périphériques. Ces données peuvent être changées par le périphérique sans que le processeur le sache, de sorte qu'une valeur lue par le processeur à l'instant `t` n'est plus la même (dans le registre du périphérique) à l'instant `t+1`. Le compilateur ne doit pas optimiser, il doit aller chercher la donnée en mémoire à chaque fois que le programme le demande.
     353* `volatile` permet de dire à `gcc` que la variable en mémoire peut changer à tout moment, elle est volatile. Ainsi quand le programme demande de lire une variable `volatile` le compilateur doit toujours aller la lire en mémoire. Il ne doit jamais chercher à optimiser en utilisant un registre afin de réduire le nombre de lecture mémoire (load). De même, quand le programme écrit dans une variable `volatile`, cela doit toujours provoquer une écriture dans la mémoire (store).
     354* Ainsi, les registres de périphériques doivent toujours être impérativement lus ou écrits à chaque fois que le programme le demande, parce que c'est justement ces lectures et ces écritures qui commandent le périphérique.
     355''
     356}}}
     357
     358
     359
     360== A4. Compilation
     361
     362
     363
     364Pour obtenir le programme exécutable, nous allons utiliser :
     365* `gcc -o file.o -c file.c`
     366  - Appel du compilateur avec l'option `-c` qui demande à `gcc` de faire le préprocessing puis la compilation c pour produire le fichier objet `file.o`
     367* `ld -o bin.x -Tkernel.ld files.o ...`
     368  - Appel de l'éditeur de liens pour produire l'exécutable `bin.x` en assemblant tous les fichiers objets `.o`, en les plaçant dans l'espace d'adressage et résolvant les liens entre eux. \\Autrement dit, quand un fichier `h.o` utilise une fonction `fg()` ou une variable `vg` définie dans un autre fichier `g.o` (`h` et `g` sont là pour illustrer), alors l'éditeur de liens place dans l'espace d'adressage les sections `.text` et `.data` des fichiers `h.o` et `g.o`, puis il détermine alors quelles sont les adresses de `fg()` et `vg` en mémoire et il complètent les instructions de `h` qui utilisent ces adresses.
     369* `objdump -D file.o > file.o.s` ou `objdump -D bin.x > bin.x.s`
     370  - Appel du désassembleur qui prend les fichiers binaires (`.o` ou `.x`) pour retrouver le code produit par le compilateur à des fins de debug ou de curiosité.
     371
     372**Questions**
     373
     374Le fichier `kernel.ld` décrit l'espace d'adressage et la manière de remplir les sections dans le programme exécutable. Ce fichier est utilisé par l'éditeur de lien. C'est un `ldscript`, c'est-à-dire un [https://www.wikiwand.com/fr/Langage_de_script `script`] pour `ld`.
     375{{{#!c
     376__tty_regs_map   = 0xd0200000 ;
     377__boot_origin    = 0xbfc00000 ;
     378__boot_length    = 0x00001000 ;
     379__ktext_origin   = 0x80000000 ;
     380__ktext_length   = 0x00020000 ;
     381[... question 1 ...]
     382__kdata_end      = __kdata_origin + __kdata_length ;
     383
     384MEMORY {
     385    boot_region  : ORIGIN = __boot_origin,  LENGTH = __boot_length
     386    ktext_region : ORIGIN = __ktext_origin, LENGTH = __ktext_length
     387[... question 2 ...]
     388}
     389
     390SECTIONS {
     391    .boot : {
     392        *(.boot)   
     393    } > boot_region
     394[... question 3 ...]
     395    .kdata : {
     396        *(.*data*)     
     397    } > kdata_region
     398}
     399}}}
     400
     4011. Le fichier `kernel.ld` commence par la déclaration des variables donnant des informations sur les adresses et les tailles des régions de mémoire. Ces symboles n'ont pas de type et ils sont visibles de tous les programmes C, il faut juste leur donner un type pour que le compilateur puisse les exploiter, c'est ce que nous avons fait pour `extern volatile struct tty_s __tty_regs_map[NTTYS]`. En regardant dans le dessin de la représentation de l'espace d'adressage, complétez les lignes de déclaration des variables pour la région `kdata_region`
     402{{{#!protected ------------------------------------------------------------------------------------
     403''
     404Cours 9 / slides 23 et 24\\\\
     405Pour répondre, il faut savoir interpréter le dessin représentant l'espace d'adressage. Si ça semble difficile, il faut revoir ce qu'est l'espace d'adressage.
     406{{{#!c
     407__kdata_origin   = 0x80020000 ;
     408__kdata_length   = 0x003E0000 ;
     409}}}
     410''
     411}}}
     4121. Le fichier contient ensuite la déclaration des régions (dans `MEMORY{...}`) qui seront remplies par l'éditeur de lien avec les sections trouvées dans les fichiers objets selon un ordre décrit dans la partie `SECTIONS{}` du `ldscript`.  Complétez cette partie (la zone `[... question 2 ...]`) pour ajouter les lignes correspondant à la déclaration de la région `kdata_region` ?
     413{{{#!protected ------------------------------------------------------------------------------------
     414''
     415Cours 9 / slides 23 et 24\\\\
     416La syntaxe est assez explicite, cela ne devrait pas poser de problème.
     417{{{#!c
     418kdata_region : ORIGIN = __kdata_origin, LENGTH = __kdata_length
     419}}}
     420''
     421}}}
     4221.  Enfin le fichier contient comment sont remplies les régions avec les sections. Complétez les lignes correspondant à la description du remplissage de la région `ktext_region`. Vous devez la remplir avec les sections `.text` issus de tous les fichiers.
     423{{{#!protected ------------------------------------------------------------------------------------
     424''
     425Cours 9 / slides 23 et 24\\\\
     426Il faut bien comprendre que `.ktext` est une section produite par l'éditeur de liens. C'est ce que l'on appelle une section de sortie. `.text` est une section que l'éditeur de liens trouve dans un fichier objet `.o`, c'est ce que l'on appelle une section d'entrée. Comme il y a plusieurs fichiers objet, on doit dire à l'éditeur de lien de prendre toutes les sections `.text` de tous les fichiers qu'on lui donne. Le `*` devant `(.text)` est une expression régulière permettant de dire à l'éditeur de liens quels fichiers sont concernés, ici avec `*` c'est tous les fichiers. Les expressions régulières sont celles qu'on utilise avec le `shell`.
     427{{{#!c
     428.ktext : {
     429    *(.text)
     430} > ktext_region
     431}}}
     432''
     433}}}
     434 
     435Nous allons systématiquement utiliser des Makefiles pour la compilation du code, mais aussi pour lancer le simulateur du prototype **almo1**. Pour cette première séance, les Makefiles ne permettent pas de faire des recompilations partielles de fichiers. Les Makefiles sont utilisés pour agréger toutes les actions que nous voulons faire sur les fichiers, c'est-à-dire : compiler, exécuter avec ou sans trace, nettoyer le répertoire. Nous avons recopié partiellement le premier Makefile pour montrer sa forme et poser quelques questions, auxquels vous savez certainement répondre.
     436{{{#!make
     437# Tools and parameters definitions
     438# ------------------------------------------------------------------------------
     439NTTY   ?= 2 #                          default number of ttys
     440
     441CC      = mipsel-unknown-elf-gcc #     compiler
     442LD      = mipsel-unknown-elf-ld #      linker
     443OD      = mipsel-unknown-elf-objdump # desassembler
     444SX      = almo1.x #                    prototype simulator
     445
     446CFLAGS  = -c #                         stop after compilation, then produce .o
     447CFLAGS += -Wall -Werror #              gives almost all C warnings and considers them to be errors
     448CFLAGS += -mips32r2 #                  define of MIPS version
     449CFLAGS += -std=c99 #                   define of syntax version of C
     450CFLAGS += -fno-common #                do not use common sections for non-static vars (only bss)
     451CFLAGS += -fno-builtin #               do not use builtin functions of gcc (such as strlen)
     452CFLAGS += -fomit-frame-pointer #       only use of stack pointer ($29)
     453CFLAGS += -G0 #                        do not use global data pointer ($28)
     454CFLAGS += -O3 #                        full optimisation mode of compiler
     455CFLAGS += -I. #                        directories where include files like <file.h> are located
     456CFLAGS += -DNTTYS=$(NTTY) #            #define NTTYS with the number of ttys in the prototype   
     457
     458# Rules (here they are used such as simple shell scripts)
     459# ------------------------------------------------------------------------------
     460help:
     461    @echo "\nUsage : make <compil|exec|clean> [NTTY=num]\n"
     462    @echo "        compil  : compiles all sources"
     463    @echo "        exec    : executes the prototype"
     464    @echo "        clean   : clean all compiled files\n"
     465
     466compil:
     467    $(CC) -o hcpua.o $(CFLAGS) hcpua.S
     468    @$(OD) -D hcpua.o > hcpua.o.s
     469    $(LD) -o kernel.x -T kernel.ld hcpua.o
     470    @$(OD) -D kernel.x > kernel.x.s
     471
     472exec: compil
     473    $(SX) -KERNEL kernel.x -NTTYS $(NTTY)
     474
     475clean:
     476    -rm *.o* *.x* *~ *.log.* proc?_term? 2> /dev/null || true
     477}}}
     4784. Au début du fichier se trouve la déclaration des variables du Makefile, quelle est la différence entre `=`, `?=` et `+=` ?
     479{{{#!protected ------------------------------------------------------------------------------------
     480''
     481Ce n'est pas expliqué dans le cours, mais c'est utilisé dans les `Makefile`s. La syntaxe des `Makefile`s peut-être très complexe (c'est un vieux langage), ici nous ne verrons qu'une partie. La question sur la déclaration des variables optionnelles est intéressante si on fait le parallèle avec ce qu'il faut faire pour avoir le même comportement avec le préprocesseur et ses `#ifndef` (on a une autre méthode encore pour le `shell`.\\A notez que le Makefile `voit` les variables du `shell` comme s'il les avait définies lui même.
     482* `=`  fait une affectation simple
     483* `?=` fait une affection de la variable si elle n'est pas déjà définie comme variable d'environnement du shell ou dans la ligne de commande de make, par exemple avec `FROM`
     484* `+=` concatène la valeur courante à la valeur actuelle, c'est une concaténation de chaîne de caractères.
     485''
     486}}}
     4871. Où est utilisé `CFLAGS` ? Que fait `-DNTTYS=$(NTTY)` et pourquoi est-ce utile ici ?
     488{{{#!protected ------------------------------------------------------------------------------------
     489''
     490Cours 9 / Slide 55\\\\
     491Le compilateur C peut avoir beaucoup de paramètres. Définir une variable `CFLAGS` permet de les déclarer une fois au début et d'utiliser cette variable plusieurs fois dans le `Makefile`. Si on veut changer un argument, il suffit de le faire une seule fois. Ce genre de choses est nécessaire si on veut faire des `Makefile`s facile à lire et à faire évoluer.
     492* La variable `CFLAGS` est utilisée par `gcc`, il y a ici toutes les options indispensables pour compiler mais il en existe beaucoup, ce qui fait des tonnes de combinaison d'options !
     493* `-DNTTYS=$(NTTY)` permet de définir et donner une valeur à une macro (ici définition `NTTYS` avec la valeur `$(NNTY)` comme le fait un `#define` dans un fichier C. Cette commande éviter donc d'ouvrir les codes pour les changer.
     494''
     495}}}
     4961. Si on exécute `make` sans cible, que se passe-t-il ?
     497{{{#!protected ------------------------------------------------------------------------------------
     498''
     499Cours 9 / slides 28 et 29\\\\
     500Mettre une règle `help` comme règle par défaut permet de documenter l'usage du `Makefile`, ce qui est plutôt une bonne pratique quand il y a beaucoup de cible et de paramètres. C'est d'autant plus vrai qu'on utilise les Makefiles comme des ensembles de `shell script`.
     501* C'est la première cible qui est choisie, donc ici c'est équivalent à `make help`. Cela affiche l'usage pour connaître les cibles disponibles.
     502''
     503}}}
     5041. à quoi servent  `@` et `-` au début de certaines commandes ?
     505{{{#!protected ------------------------------------------------------------------------------------
     506''
     507Ce n'est pas dit en cours, mais comme c'est utilisé, il n'est pas inutile de le savoir.
     508* `@` permet de ne pas afficher la commande avant son exécution. On peut rendre ce comportement systématique en ajoutant la règle `.SILENT:` n'importe où dans le fichier.
     509* `-`  permet de ne pas stopper l'exécution des commandes même si elles rendent une erreur, c'est-à-dire une valeur de sortie différente de 0.
     510''
     511}}}
     512
     513{{{#!comment
     514
     515==
     516= B. Travaux pratiques
     517
     518
     519
     520Pour 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.
     521
     522Vous 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].
     523
     524Si 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é.
     525
     526
     527
     528== B1. Premier programme en assembleur dans la seule section de boot
     529
     530
     531
     532Nous commençons par un petit programme de quelques lignes en assembleur, placé entièrement dans la région mémoire
     533du boot, qui réalise l'affichage du message "Hello World". C'est un tout tout petit programme, mais pour obtenir
     534l'exécutable, vous devrez utiliser tous les outils de la chaîne de cross-compilation MIPS et
     535pour l'exécuter vous devrez exécuter le simulateur du prototype. C'est simple, mais c'est nouveau pour
     536beaucoup d'entre vous
     537
     538**Objectifs**
     539
     540- produire un exécutable à partir d'un code en assembleur.
     541- savoir comment afficher un caractère sur un terminal.
     542- analyse d'une trace d'exécution
     543
     544**Fichiers**
     545
     546{{{
     5471_hello_boot
     548├── hcpua.S      : code dépendant du cpu matériel en assembleur
     549├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     550└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     551}}}
     552
     553
     554
     555**Questions**
     556
     557
     5581. Dans quel fichier se trouve la description de l'espace d'adressage du MIPS ? Que trouve-t-on dans ce fichier ?
     559{{{#!protected ------------------------------------------------------------------------------------
     560''
     561- C'est dans le fichier kernel.ld. \\On y trouve:
     562  - la définition de variables du ldscript. Ce sont essentiellement des adresses dans l'espace d'adressage,
     563    mais pas seulement, il y a aussi la taille des régions.
     564  - On trouve ensuite la déclaration des régions mémoires.
     565  - 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.
     566''
     567}}}
     5681. Dans quel fichier se trouve le code de boot et pourquoi, selon vous, avoir nommé ce fichier ainsi ?
     569{{{#!protected ------------------------------------------------------------------------------------
     570''
     571- 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
     572  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`
     573''
     574}}}
     5751. À quelle adresse démarre le MIPS ? Où peut-on le vérifier ?
     576{{{#!protected ------------------------------------------------------------------------------------
     577''
     578- L'adresse de démarrage est `0xBFC00000`.
     579- On peut le vérifier dans le fichier `kernel.ld`.
     580  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
     581  cette région que l'on met le code de boot.
     582''
     583}}}
     5841. Que produit `gcc` quand on utilise l'option `-c` ?
     585{{{#!protected ------------------------------------------------------------------------------------
     586''
     587- L'option `-c` demande à `gcc` de s'arrêter après avoir produit le fichier objet.
     588- Il produit donc un fichier au format `.o`
     589''
     590}}}
     5911. Que fait l'éditeur de liens ? Comment est-il invoqué ?
     592{{{#!protected ------------------------------------------------------------------------------------
     593''
     594- 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`).
     595- L'éditeur de liens est appelé par `gcc` si on n'a pas l'option `-c`ou directement par `ld` (ici `mipsel_unknown_ld`)
     596''
     597}}}
     5981. De quels fichiers a besoin l'éditeur de liens pour fonctionner ?
     599{{{#!protected ------------------------------------------------------------------------------------
     600''
     601- L'éditeur de liens a besoin des fichiers objets `.o` et du fichier `ldscript` (ici, `kernel.ld`)
     602''
     603}}}
     6041. Dans quelle section se trouve le code de boot pour le compilateur ? ''(la réponse est dans le code assembleur)''
     605{{{#!protected ------------------------------------------------------------------------------------
     606''
     607- Le code de boot a été mis dans une section `.text`.
     608''
     609}}}
     6101. Dans quelle section se trouve le message "hello" pour le compilateur ? Ce choix est particulier, mais ce message est en lecture seule.
     611{{{#!protected ------------------------------------------------------------------------------------
     612''
     613- Le message est aussi la section `.text`.
     614''
     615}}}
     6161. Dans quelle section se trouve le code de boot dans le code exécutable ? (la réponse est dans `hcpua.S`)
     617{{{#!protected ------------------------------------------------------------------------------------
     618''
     619- Dans le programme exécutable, le code de boot est mis dans la section `.boot`.
     620''
     621}}}
     6221. Dans quelle région de la mémoire le code de boot est-il placé ? (la réponse est dans `kernel.ld`)
     623{{{#!protected ------------------------------------------------------------------------------------
     624''
     625- Le code de boot est placé dans la région `boot_region`
     626''
     627}}}
     6281. 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)
     629{{{#!protected ------------------------------------------------------------------------------------
     630''
     631- Le fichier `kernel.ld` déclare une variable `__tty_regs_map` initialisée avec l'adresse de
     632  où sont placés les registres de contrôles du `TTY`. Le premier registre à l'adresse `__tty_regs_map`
     633  est l'adresse du registre de sortie `TTY_WRITE`.
     634''
     635}}}
     6361. Le code de boot se contente d'afficher un message, comment sait-on que le message est fini
     637   et que le programme doit s'arrêter ? (ou quel est le caractère de fin de chaîne ?)
     638{{{#!protected ------------------------------------------------------------------------------------
     639''
     640- C'est quand la boucle d'affichage détecte le `0` terminateur de la chaîne de caractères.
     641''
     642}}}
     6431. 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.
     644{{{#!protected ------------------------------------------------------------------------------------
     645''
     646- If faut arrêter le programme, car il n'y a plus de code, mais on ne sait pas arrêter le processeur,
     647  alors on le faire tourner en rond.
     648''
     649}}}
     650
     651**Exercices**
     652 
     653- Exécutez le programme en lançant le simulateur avec `make exec`, qu'observez-vous ?
     654{{{#!protected ------------------------------------------------------------------------------------
     655''
     656- 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.
     657''
     658}}}
     659- 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 
     660 - On voit la séquence des instructions exécutées
     661 - La première colonne nous informe que les adresses lues sont dans l'espace Kernel
     662 - La seconde colonne sont les numéros de cycles
     663 - La troisième sont les adresses
     664 - La quatrième le code binaire des instructions
     665 - Le reste de la ligne contient l'instruction désassemblée
     666 - Lorsque les adresses ont un nom, c'est à dire qu'une étiquette leur a été attribuée, celle-ci est indiquée.
     667
     668 `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.
     669{{{#!protected ------------------------------------------------------------------------------------
     670''
     671{{{#!asm
     672K    12:     <boot>:--------------------------------------------------------------------------------
     673K    12:     0xbfc00000 0x3c04bfc0  lui a0,0xbfc0
     674K    13:     0xbfc00004 0x24840028  addiu   a0,a0,40
     675K    14:     0xbfc00008 0x3c05d020  lui a1,0xd020
     676K    15:     0xbfc0000c 0x24a50000  addiu   a1,a1,0
     677K    26:     <print>:-------------------------------------------------------------------------------
     678K    26:     0xbfc00010 0x80880000  lb  t0,0(a0)
     679K    27:     0xbfc00014 0xa0a80000  sb  t0,0(a1)
     680K    37:     --> READ  MEMORY @ 0xbfc00028 BE=---1 --> 0x6c6c6548
     681K    39:     <-- WRITE MEMORY @ 0xd0200000 BE=---1 <-- 0x48
     682}}}
     683''
     684}}}
     685- Modifiez le code de `hcpua.S` afin d'afficher le message "Au revoir\n" après le message "Hello".\\
     686  Vous devez avoir deux messages, et pas seulement étendre le premier.
     687{{{#!protected ------------------------------------------------------------------------------------
     688''
     689* 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.
     690''
     691}}}
     692
     693
     694
     695== B2. Saut dans le code du noyau en assembleur
     696
     697
     698
     699Dans le deuxième programme, nous restons en assembleur, mais nous avons deux fichiers source : (1) le fichier contenant
     700le 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.
     701
     702**Objectifs**
     703
     704- Savoir comment le programme de boot fait pour sauter à l'adresse de la routine kinit.
     705- Avoir un fichier kernel.ld un peu plus complet.
     706 
     707**Fichiers**
     708
     709{{{
     7102_init_asm/
     711├── hcpua.S      : code dépendant du cpu matériel en assembleur
     712├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     713├── kinit.S      : fichier contenant le code de démarrage du noyau, ici c'est une routine kinit.
     714└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     715}}}
     716
     717**Questions**
     718
     7191. Regarder dans le fichier `hcpua.S`, dans quelle section est désormais le code de boot ?
     720{{{#!protected ------------------------------------------------------------------------------------
     721''
     722- Il a été placé dans la section de `.boot`
     723''
     724}}}
     7252. Le code de boot ne fait que sauter à l'adresse `kinit` avec l'instruction `jr`,
     726   il n'y a pas de retour, ce n'est donc pas un `jal`. Où est défini `kinit` ?
     727   Comment le code de boot connait-il cette adresse ?
     728   Pourquoi ne pas avoir utilisé `j init` et donc pourquoi passer par un registre ?
     729{{{#!protected ------------------------------------------------------------------------------------
     730''
     731- `kinit` est défini dans la `kinit.S`.
     732- `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.
     733- 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`.
     734''
     735}}}
     7361. Dans `kernel.ld`, que signifie `*(.*data*)` ?
     737{{{#!protected ------------------------------------------------------------------------------------
     738''
     739- C'est une manière de désigner toutes les sections nommées `.*data*` (avec `*` = n'importe quoi)
     740  présentes dans n'importe quel fichier objets reçu par l'éditeur de liens.
     741''
     742}}}
     7431. 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])
     744{{{#!protected ------------------------------------------------------------------------------------
     745''
     746- `__kdata_end` est l'adresse du premier octet placé juste après la région data.
     747- les 2 «`_`» permettent d'éviter les conflits avec les noms des symboles (fonction, variable, type, etc.)
     748  présents dans le programme.
     749''
     750}}}
     751
     752**Exercices**
     753
     754- Exécutez le programme sur le simulateur. Est-ce différent de l'étape 1 ?
     755{{{#!protected ------------------------------------------------------------------------------------
     756''
     757* Non, c'est le même comportement.
     758''
     759}}}
     760- Modifiez le code, comme pour l'étape 1, afin d'afficher un second message ?
     761{{{#!protected ------------------------------------------------------------------------------------
     762''
     763* C'est très semblable, c'est même identique à l'étape 1, l'idée est d'ouvrir le code...
     764''
     765}}}
     766
     767
     768
     769== B3. Saut dans la fonction kinit() du noyau en langage C
     770
     771
     772
     773Dans ce troisième programme, nous faisons la même chose que pour le deuxième mais `kinit()` est désormais écrit en
     774langage C. Cela change peu de choses, sauf une chose importante `kinit()` est une fonction et donc il faut absolument
     775une pile d'exécution.
     776
     777**Objectifs**
     778
     779- Savoir comment et où déclarer la pile d'exécution du noyau.
     780- Savoir comment afficher un caractère sur un terminal depuis un programme C.
     781
     782**Fichiers**
     783
     784{{{
     7853_init_c/
     786├── hcpua.S      : code dépendant du cpu matériel en assembleur
     787├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     788├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
     789└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     790}}}
     791
     792**Questions**
     793
     7941. Quand faut-il initialiser la pile ? Dans quel fichier est-ce ? Quelle est la valeur du pointeur initial ?
     795{{{#!protected ------------------------------------------------------------------------------------
     796''
     797- Il faut initialiser le pointeur avant d'appeler `kinit()`
     798- C'est dans le fichier `hcpua.S`
     799- `$29` ← `__kdata_end`, c'est-à-dire `0x80400000`
     800''
     801}}}
     8021. Dans quel fichier le mot clé `volatile` est-il utilisé ? Rappeler son rôle.
     803{{{#!protected ------------------------------------------------------------------------------------
     804''
     805* 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.
     806''
     807}}}
     808
     809**Exercices**
     810
     811- Exécutez le programme sur le simulateur. Est-ce différent de l'étape 1 ?
     812{{{#!protected ------------------------------------------------------------------------------------
     813''
     814* Non, c'est le même comportement :-)
     815''
     816}}}
     817- 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.
     818{{{#!protected ------------------------------------------------------------------------------------
     819''
     820* Dans kinit.o.s, l'adresse de `kinit` est `0` alors que `kernel.x.s` l'adresse est `0x80000000`.
     821* Dans kinit.o.s, `kinit` est dans la section `.text` alors que dans `kernel.x.s` `kinit` est dans la section `.ktext`.
     822* La raison est que
     823  * 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.
     824  * dans `kernel.x.s` `kinit` est placé et mis dans la section `.ktext` comme le fichier `kernel.ld` le demande.
     825''
     826}}}
     827- Modifiez le code de `kinit.c`, et comme pour l'étape 1, afficher un second message ?
     828{{{#!protected ------------------------------------------------------------------------------------
     829''
     830* 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.
     831''
     832}}}
     833
     834
     835
     836== B4.  Accès aux registres de contrôle des terminaux `TTY`
     837
     838
     839
     840Le 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.
     841
     842**Objectifs**
     843
     844- Savoir comment compiler un programme C avec du code conditionnel.
     845- Savoir comment décrire en C l'ensemble des registres d'un contrôleur de périphérique et y accéder.
     846 
     847**Fichiers**
     848
     849{{{
     8504_nttys/
     851├── hcpua.S      : code dépendant du cpu matériel en assembleur
     852├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     853├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
     854└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     855}}}
     856
     857**Questions**
     858
     8591. Dans le fichier `kinit.c`, il est question d'un loopback, à quoi cela sert-il ?
     860{{{#!protected ------------------------------------------------------------------------------------
     861''
     862- 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.
     863- 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`.
     864''
     865}}}
     8661. Dans le fichier `kinit.c`, on trouve `__tty_regs_map[ tty%NTTYS ].write = *s`, expliquez le modulo.
     867{{{#!protected ------------------------------------------------------------------------------------
     868''
     869* 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
     870''
     871}}}
     8721. 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 ?
     873{{{#!protected ------------------------------------------------------------------------------------
     874''
     875- Deux fenêtres sont apparues avec un message uniquement dans la fenêtre `proc0_term0`.
     876- 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`.
     877''
     878}}}
     879
     880**Exercices**
     881
     882- Modifiez le code pour afficher un message sur le second terminal, il y a toujours une attente sur le premier terminal.
     883{{{#!protected ------------------------------------------------------------------------------------
     884''
     885- C'est juste deux copier-coller (message et boucle d'affichage).
     886''
     887}}}
     888- 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.
     889{{{#!protected ------------------------------------------------------------------------------------
     890''
     891{{{#!c
     892void kinit (void)
     893{
     894    char *s;
     895    char c;
     896
     897    for (s = hello_0; *s; s++) {                // for all char in hello string
     898        __tty_regs_map[ 0%NTTYS ].write = *s;   // write it to the tty output register
     899    }
     900
     901    for (s = hello_1; *s; s++) {                // for all char in hello string
     902        __tty_regs_map[ 1%NTTYS ].write = *s;   // write it to the tty output register
     903    }
     904
     905    while (1) {
     906
     907        if (__tty_regs_map[0].status != 0) {    // wait for a char typed on keyboard 0
     908            c = __tty_regs_map[0].read;         // read the char
     909            __tty_regs_map[0].write = c;        // and print it (that is a software loopback)
     910        }
     911        if (__tty_regs_map[1].status != 0) {    // wait for a char typed on keyboard 1
     912            c = __tty_regs_map[1].read;         // read the char
     913            if ((c >= 'a') && (c <= 'z'))
     914                c += 'A' - 'a';
     915            else if ((c >= 'A') && (c <= 'Z'))
     916                c += 'a' - 'A';
     917            __tty_regs_map[1].write = c;        // and print it (that is a software loopback)
     918        }
     919
     920    }
     921
     922    while (1);
     923}}}
     924''
     925}}}
     926
     927
     928
     929== B5. Premier petit pilote pour le terminal
     930
     931
     932
     933Dans 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.
     934
     935**Objectifs**
     936
     937- Savoir comment créer un début de pilote pour le terminal `TTY`.
     938- Savoir comment décrire une API en C
     939- Savoir appeler une fonction en assembleur depuis le C
     940 
     941**Fichiers**
     942
     943{{{
     9445_driver/
     945├── harch.c      : code dépendant de l'architecture du SoC, pour le moment c'est juste le pilote du TTY
     946├── harch.h      : API du code dépendant de l'architecture
     947├── hcpu.h       : prototype de la fonction clock()
     948├── hcpua.S      : code dépendant du cpu matériel en assembleur
     949├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     950├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
     951└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     952}}}
     953   
     954**Questions**
     955
     9561. 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 ?
     957{{{#!protected ------------------------------------------------------------------------------------
     958''
     959- 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.
     960''
     961}}}
     9621. 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`
     963{{{#!protected ------------------------------------------------------------------------------------
     964''
     965* 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`.
     966* `.globl clock` permet de faire en sorte que la fonction soit visible par les autres fichiers C.
     967''
     968}}}
     9691. 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()`
     970{{{#!protected ------------------------------------------------------------------------------------
     971''
     972* 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.
     973''
     974}}}
     975
     976**Exercices**
     977
     978- Afin de vous ''détendre un peu'', vous allez créer un petit jeu `guess`
     979  - `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.
     980 __Étapes__
     981  - Vous créez deux fichiers `guess.c` et `guess.h`.
     982    - `guess.c` contient le jeu il y au moins une fonction `guess()`
     983    - `guess.h` contient les déclarations externes de `guess.c`
     984  - `kinit()` doit lancer `guess()`
     985  - `guess()`
     986     - vous demande de taper une touche pour démarrer le jeu.
     987     - 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)
     988     - exécute en boucle jusqu'à réussite
     989       - demande d'un chiffre
     990       - comparaison avec le tirage et affichage des messages `"trop grand"`, `"trop petit"` ou `"gagné"`
     991  - Vous devrez modifier le Makefile puisque vous avez un fichier à compiler en plus.
     992  - Si c'est trop facile, vous pouvez complexifier en utilisant des nombres à 2 chiffres ou plus.
     993{{{#!protected
     994[htdocs:files/6_guess.tgz 6_guess.tgz à décompresser dans tp1]\\\\
     995**kinit.h**
     996{{{#!c
     997#include <harch.h>
     998#include <guess.h>
     999
     1000char hello[] = "Hello World!\n\n";
     1001char end[] = "\nend!\n";
     1002
     1003void kinit (void)
     1004{
     1005    tty_puts (0, hello);         // print hello string
     1006    guess();
     1007    tty_puts (0, end);           // print end string on terminal 0
     1008    while (1);                   // infinite loop
     1009}
     1010}}}
     1011**guess.h**
     1012{{{#!c
     1013#ifndef _GUESS_H_
     1014#define _GUESS_H_
     1015
     1016extern void guess (void);
     1017
     1018#endif
     1019}}}
     1020**guess.c**
     1021{{{#!c
     1022#include <guess.h>
     1023#include <harch.h>
     1024#include <hcpu.h>
     1025
     1026void guess (void)
     1027{
     1028    char c;
     1029    int num;
     1030    int random;
     1031
     1032    do {
     1033        random = clock() % 10;  // only one digit
     1034        do {
     1035            do {
     1036                tty_puts(0,"Donnez un nombre : ");
     1037                c = tty_getc(0);
     1038            } while ( (c < '0') && (c > '9') );
     1039            num = c - '0';
     1040
     1041            if (num < random)
     1042                tty_puts(0," -> trop petit\n");
     1043            else if (num > random)
     1044                tty_puts(0," -> trop grand\n");
     1045
     1046        } while (random != num);
     1047
     1048        tty_puts(0,"\nGagné\n");
     1049        tty_puts(0,"\nOn recommence [Y]/N ? ");
     1050        c = tty_getc(0);
     1051        tty_puts(0,"\n\n");
     1052
     1053    } while (c != 'N');
     1054}
     1055}}}
     1056}}}