Changes between Initial Version and Version 1 of AS6-TME-B1


Ignore:
Timestamp:
Feb 7, 2022, 3:39:21 PM (2 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AS6-TME-B1

    v1 v1  
     1{{{#!protected
     2**[https://www-soc.lip6.fr/trac/archi-l3s6/wiki/AS6-TME1?action=edit EDIT]**
     3}}}
     4[[PageOutline]]
     5{{{#!html
     6<h1> <font size="+2"> Du boot au premier programme user
     7</font></h1>
     8}}}
     9
     10Cette page est composée de deux parties, une partie TD et une partie TP.
     11
     12Pour la partie TD, vous avez des questions dont les réponses sont pour la plupart dans le cours. Je vous demande d'y répondre et de formuler vos réponses dans un compte-rendu. Vous avez un corrigé qui peut vous aider, mais je vous demande de ne pas le lire avant d'avoir vous même répondu. Cette partie n'est pas noté mais les questions pourraient être posées à l'examen alors il est utile que vous arriviez à formuler par vous-même. Si vous êtes bloqué et que vous ne comprenez pas non plus la réponse, n'hésitez pas à le dire, c'est qu'elle n'est pas claire et je la modifierai.
     13
     14Pour le TP, chaque étape représente un nouveau concept et je donne (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.
     15
     16Les premières étapes du TP sont uniquement dans le noyau et le MIPS est alors en mode kernel puis, à la fin, les applications de l'utilisateur s'exécutent en mode user au dessus d'une petite libc.
     17
     18
     19**Récupération du code du TP**
     20
     21
     22* Vous devez être sur une **machine Linux** native ou virtualisée (sinon lisez [https://www-soc.lip6.fr/trac/archi-l3s6/wiki/Howto-TP Configuration de l'environnement des TP → Étape 1])
     23* Vous devez avoir le répertoire **`$HOME/2kO6`** contenant le prototype **almo1** et le **compilateur C pour le MIPS** (sinon lisez [https://www-soc.lip6.fr/trac/archi-l3s6/wiki/Howto-TP Configuration de l'environnement des TP → Étape 2])
     24* Téléchargez **[htdocs:files/tp1.tgz l'archive code du tp1]** et placez là dans le répertoire `$HOME/AS6` 
     25* Assurez-vous que vous avez déjà sourcé le fichier `Source-me.sh` (sinon lisez [https://www-soc.lip6.fr/trac/archi-l3s6/wiki/Howto-TP Configuration de l'environnement des TP → Étape 3])
     26* Ouvrez un `terminal`, allez dans le répertoire `AS6` (**`cd ~/AS6`**) et décompressez l'archive du tp1 avec **`tar xvzf tp1.tgz`**\\''Cette étape est peut-être inutile si vous avez déjà fait la décompression de l'archive au moment de son téléchargement.''
     27* Dans le `terminal`, exécutez la commande  **`cd ; tree -L 2 AS6`**. Vous devriez obtenir ceci:\\''(si vous n'avez pas `tree` sur votre Linux, vous pouvez l'installer, c'est un outil utile, mais pas indispensable pour ces TP)''
     28{{{#!bash
     29AS6
     30├── bin
     31│   ├── almo1.x
     32│   ├── gcc
     33│   ├── Source-me.sh
     34│   ├── test
     35│   └── tracelog
     36└── tp1
     37    ├── 1_hello_boot
     38    ├── 2_init_c
     39    ├── 3_driver
     40    ├── 4_klibc
     41    ├── 5_syscalls
     42    ├── 6_libc
     43    └── Makefile
     44}}}
     45
     46
     47
     48
     49==
     50= A. Travaux dirigés
     51
     52
     53
     54== A1. Analyse de l'architecture
     55
     56
     57Les trois figures ci-dessous donnent des informations sur l'architecture du prototype **almo1** sur lequel vous allez travailler.
     58* À gauche, vous avez un schéma de connexion simplifié.
     59* 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.
     60* À droite, vous avez la représentation de l'espace d'adressage implémenté pour le prototype. 
     61
     62[[Image(htdocs:img/almo1.png,nolink,height=300)]]
     63[[Image(htdocs:img/TTY.png,nolink,height=200,top)]]
     64[[Image(htdocs:img/espace_adresse.png,nolink,height=300)]]
     65
     66
     67**Questions**
     68
     691. Il y a deux mémoires dans **almo1** : RAM et ROM. Qu'est-ce qui les distinguent et que contiennent-elles ?
     70{{{#!protected ------------------------------------------------------------------------------------
     71'''''''''''''''
     72* La ROM est une mémoire morte, c'est-à-dire en lecture seule. Elle contient le code de démarrage du prototype.
     73* La RAM est une mémoire vive, c'est-à-dire pouvant être lue et écrite. Elle contient le code et les données.
     74'''''''''''''''
     75}}}
     761. 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 ?
     77{{{#!protected ------------------------------------------------------------------------------------
     78'''''''''''''''
     79* L'espace d'adressage du MIPS est l'ensemble des adresses que peut former le MIPS.
     80* Les adresses sont sur 32 bits et désignent chacune un octet, il y a donc 2^32^ octets.
     81* On accède à l'espace d'adressage avec les instructions load/store (`lw`, `lh`, `lb`, `lhu`, `lbu`, `sw`, `sh`, `sb`).
     82* 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).
     83'''''''''''''''
     84}}}
     851. 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 ?
     86{{{#!protected ------------------------------------------------------------------------------------
     87'''''''''''''''
     88* Le code de boot est dans la mémoire ROM.
     89* Il commence à l'adresse `0xBFC00000` parce que c'est l'adresse qu'envoie le MIPS au démarrage.
     90'''''''''''''''
     91}}}
     921. Quel composant permet de faire des entrées-sorties dans almo1 ?\\Citez d'autres composants qui pourraient être présents dans un autre SoC ?
     93{{{#!protected ------------------------------------------------------------------------------------
     94'''''''''''''''
     95* 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.
     96* 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.
     97'''''''''''''''
     98}}}
     991. 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 ?
     100{{{#!protected ------------------------------------------------------------------------------------
     101'''''''''''''''
     102* Le composant `TTY` est placé à partir de l'adresse `0xD0200000`.
     103* 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.
     104* Pour écrire un caractère sur l'écran, il faut écrire le code ASCII du caractère dans le registre `TTY_WRITE`
     105* `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`
     106'''''''''''''''
     107}}}
     1081. 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` ?
     109{{{#!protected ------------------------------------------------------------------------------------
     110'''''''''''''''
     111* 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`.
     112'''''''''''''''
     113}}}
     1141. Que représentent les flèches bleues sur le schéma ? Pourquoi ne vont-elles que dans une seule direction ?
     115{{{#!protected ------------------------------------------------------------------------------------
     116'''''''''''''''
     117* 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.
     118* 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.
     119'''''''''''''''
     120}}}
     121
     122
     123== A2. Programmation assembleur
     124 
     125
     126L'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.
     127
     128**Questions**
     129
     1301. 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.
     131{{{#!protected ------------------------------------------------------------------------------------
     132'''''''''''''''
     133{{{#!asm
     134lui   $4, 0xD020
     135ori   $4, $4, 0x0000   // cette instruction ne sert a rien puisqu on ajoute 0, mais je la mets pour le cas general
     136ori   $5, $0, 'x'
     137sb    $5, 0($4)        // Notez que l'immédiat 0 devant ($4) n est pas obligatoire mais on s'obligera à le mettre
     138}}}
     139'''''''''''''''
     140}}}
     1411. 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
     142{{{#!asm
     143la $r, label
     144li $r, 0x87654321
     145}}}
     146 sont remplacées par
     147{{{#!asm
     148lui $r, label>>16
     149ori $r, $r, label & 0xFFFF
     150lui $r, 0x8765
     151ori $r, $r, 0x4321
     152}}}
     153 Réécrivez le code de la question précédente en utilisant `la` et `li`
     154{{{#!protected ------------------------------------------------------------------------------------
     155'''''''''''''''
     156{{{#!asm
     157la    $4, __tty_regs_map
     158li    $5, 'x'
     159sb    $5, 0($4)
     160}}}
     161'''''''''''''''
     162}}}
     1631. 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 ?
     164{{{#!protected ------------------------------------------------------------------------------------
     165'''''''''''''''
     166*
     167  - `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) | (ZeroExtend(label, 32) << 2)`. Les 4 bits de poids forts du `PC` sont conservés, le saut est bien relatif au PC (ZeroExtend désigne ici le fait d'étendre le label sur 32 bits en ajoutant des zéros en tête, à changer s'il y a une meilleure syntaxe).
     168  - A l'inverse, `jr $r` effectue un saut absolu puisque cette instruction réalise `PC ← $r`
     169 Autrement dit, si l’on veut aller exécuter du code n'importe où en mémoire, il faut utiliser `jr`.
     170'''''''''''''''
     171}}}
     1721. 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"`
     173   - `name` est le nom de la nouvelle section.  On met souvent un `.name` pour montrer que c'est une section et
     174   - `"flags"` informe du contenu : `"ax"` pour des instructions, `"ad"` pour des données (ceux que ça intéresse pourront regarder là [https://frama.link/20UzK0FP])
     175 Écrivez le code assembleur créant la section `".mytext"` et suivi de l'addition des registres `$5` et `$6` dans `$4`
     176{{{#!protected ------------------------------------------------------------------------------------
     177'''''''''''''''
     178{{{#!asm
     179 .section .mytext,"ax"
     180 addu $4,$5,$6
     181}}}
     182'''''''''''''''
     183}}}
     1841. À quoi sert la directive `.globl label` ?
     185{{{#!protected ------------------------------------------------------------------------------------
     186'''''''''''''''
     187* `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 d'autres programmes assembleur ou d'autres programmes C.
     188'''''''''''''''
     189}}}
     1901. É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.
     191{{{#!protected ------------------------------------------------------------------------------------
     192'''''''''''''''
     193{{{#!asm
     194.data
     195hello:  .asciiz "Hello"
     196.text
     197    la      $4, hello                   // $4 <- address of string
     198    la      $5, __tty_regs_map          // $5 <- address of tty's registers map
     199
     200print:
     201    lb      $8, 0($4)                   // get current char
     202    sb      $8, 0($5)                   // send the current char to the tty
     203    addiu   $4, $4, 1                   // point to the next char
     204    bne     $8, $0, print               // check that it is not null, if ok it must be printed
     205}}}
     206'''''''''''''''
     207}}}
     2081. En regardant le dessin de l'espace d'adressage du prototype **almo1**, 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 vaut celle que représente le nom `__kdata_end`
     209{{{#!protected ------------------------------------------------------------------------------------
     210'''''''''''''''
     211* La pile va être initialisée juste à la première adresse au-delà de la zone kdata : `0x80020000` + `0x003E0000` = `0x80400000`
     212* 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.
     213{{{#!asm
     214 la  $29, __kdata_end
     215}}}   
     216'''''''''''''''
     217}}}
     218
     219== A3. Programmation en C
     220
     221
     222Vous 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.
     223Les 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.
     224
     225**Questions
     226
     2271. Quels sont les usages du mot clé `static` en C ?
     228{{{#!protected ------------------------------------------------------------------------------------
     229'''''''''''''''
     2301. 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.
     2311. 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 (celui-ci est dans la pile et il 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.
     232'''''''''''''''
     233}}}
     2341. Pourquoi déclarer des fonctions ou des variables `extern` ?
     235{{{#!protected ------------------------------------------------------------------------------------
     236'''''''''''''''
     237* 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.
     238* 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.
     239'''''''''''''''
     240}}}
     2411. 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.
     242{{{#!protected ------------------------------------------------------------------------------------
     243'''''''''''''''
     244{{{#!c
     245struct test_s {
     246  int a;
     247  int b;
     248};
     249struct test_s tab[2];
     250}}}
     251'''''''''''''''
     252}}}
     2531. Quelle est la différence entre `#include "file.h"` et `#include <file.h>` ?
     254{{{#!protected ------------------------------------------------------------------------------------
     255'''''''''''''''
     256* Avec `#include "file.h"`, le préprocesseur recherche le fichier dans le répertoire local.
     257* 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`.
     258'''''''''''''''
     259}}}
     2601. Comment définir une macro-instruction C uniquement si elle n'est pas déjà définie ? Écrivez un exemple.
     261{{{#!protected ------------------------------------------------------------------------------------
     262'''''''''''''''
     263* En utilisant, une directive `#ifndef`, par exemple :
     264{{{#!c
     265#ifndef MACRO
     266#define MACRO
     267#endif
     268}}}
     269'''''''''''''''
     270}}}
     2711. Comment être certain de ne pas inclure plusieurs fois le même fichier `.h` ?
     272{{{#!protected ------------------------------------------------------------------------------------
     273'''''''''''''''
     274* 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.
     275{{{#!c
     276————————————————————— début du fichier filename.h
     277#ifndef _FILENAME_H_
     278#define _FILENAME_H_
     279
     280[... contenu du fichier ...]
     281
     282#endif
     283————————————————————— fichier de fichier filename.h
     284}}}
     285'''''''''''''''
     286}}}
     2871. 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`.
     288{{{#!c
     289struct tty_s {
     290    int write;          // tty's output address
     291    int status;         // tty's status address something to read if not null)
     292    int read;           // tty's input address
     293    int unused;         // unused address
     294};
     295extern volatile struct tty_s __tty_regs_map[NTTYS];
     296}}}
     297{{{#!protected ------------------------------------------------------------------------------------
     298'''''''''''''''
     299{{{#!c
     300int getchar(void)
     301{
     302   while (__tty_regs_map[0].status == 0);
     303   return __tty_regs_map[0].read;
     304}
     305}}}
     306'''''''''''''''
     307}}}
     3081. 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 ... ?
     309{{{#!protected ------------------------------------------------------------------------------------
     310'''''''''''''''
     311* `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).
     312* 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.
     313'''''''''''''''
     314}}}
     315
     316
     317
     318== A4. Compilation
     319
     320
     321
     322Pour obtenir le programme exécutable, nous allons utiliser :
     323* `gcc -o file.o -c file.c`
     324  - 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`
     325* `ld -o bin.x -Tkernel.ld files.o ...`
     326  - Appel de l'éditeur de lien 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 (quand un `.o` utilise une fonction ou une variable définie dans un autre `.o`).
     327* `objdump -D file.o > file.o.s` ou `objdump -D bin.x > bin.x.s`
     328  - 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é.
     329
     330**Questions**
     331
     332Le fichier `kernel.ld` décrit l'espace d'adressage et la manière de remplir les sections dans le programme exécutable.
     333{{{#!c
     334__tty_regs_map   = 0xd0200000 ;
     335__boot_origin    = 0xbfc00000 ;
     336__boot_length    = 0x00001000 ;
     337__ktext_origin   = 0x80000000 ;
     338__ktext_length   = 0x00020000 ;
     339[... question 1 ...]
     340__kdata_end      = __kdata_origin + __kdata_length ;
     341
     342MEMORY {
     343    boot_region  : ORIGIN = __boot_origin,  LENGTH = __boot_length
     344    ktext_region : ORIGIN = __ktext_origin, LENGTH = __ktext_length
     345[... question 2 ...]
     346}
     347
     348SECTIONS {
     349    .boot : {
     350        *(.boot)   
     351    } > boot_region
     352[... question 3 ...]
     353    .kdata : {
     354        *(.*data*)     
     355    } > kdata_region
     356}
     357}}}
     358
     3591. Le fichier 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`
     360{{{#!protected ------------------------------------------------------------------------------------
     361'''''''''''''''
     362{{{#!c
     363__kdata_origin   = 0x80020000 ;
     364__kdata_length   = 0x003E0000 ;
     365}}}
     366'''''''''''''''
     367}}}
     3681. Le fichier contient ensuite la déclaration des régions (dans `MEMORY{...}`) qui vont être remplies par les sections trouvées dans les fichiers objets.  Comment modifier cette partie (la zone `[... question 2 ...]`) pour ajouter les lignes correspondant à la déclaration de la région `kdata_region` ?
     369{{{#!protected ------------------------------------------------------------------------------------
     370'''''''''''''''
     371{{{#!c
     372kdata_region : ORIGIN = __kdata_origin, LENGTH = __kdata_length
     373}}}
     374'''''''''''''''
     375}}}
     3761.  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.
     377{{{#!protected ------------------------------------------------------------------------------------
     378'''''''''''''''
     379{{{#!c
     380.ktext : {
     381    *(.text)
     382} > ktext_region
     383}}}
     384'''''''''''''''
     385}}}
     386 
     387Nous 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.
     388{{{#!make
     389# Tools and parameters definitions
     390# ------------------------------------------------------------------------------
     391NTTY   ?= 2 #                          default number of ttys
     392
     393CC      = mipsel-unknown-elf-gcc #     compiler
     394LD      = mipsel-unknown-elf-ld #      linker
     395OD      = mipsel-unknown-elf-objdump # desassembler
     396SX      = almo1.x #                    prototype simulator
     397
     398CFLAGS  = -c #                         stop after compilation, then produce .o
     399CFLAGS += -Wall -Werror #              gives almost all C warnings and considers them to be errors
     400CFLAGS += -mips32r2 #                  define of MIPS version
     401CFLAGS += -std=c99 #                   define of syntax version of C
     402CFLAGS += -fno-common #                do not use common sections for non-static vars (only bss)
     403CFLAGS += -fno-builtin #               do not use builtin functions of gcc (such as strlen)
     404CFLAGS += -fomit-frame-pointer #       only use of stack pointer ($29)
     405CFLAGS += -G0 #                        do not use global data pointer ($28)
     406CFLAGS += -O3 #                        full optimisation mode of compiler
     407CFLAGS += -I. #                        directories where include files like <file.h> are located
     408CFLAGS += -DNTTYS=$(NTTY) #            #define NTTYS with the number of ttys in the prototype   
     409
     410# Rules (here they are used such as simple shell scripts)
     411# ------------------------------------------------------------------------------
     412help:
     413    @echo "\nUsage : make <compil|exec|clean> [NTTY=num]\n"
     414    @echo "        compil  : compiles all sources"
     415    @echo "        exec    : executes the prototype"
     416    @echo "        clean   : clean all compiled files\n"
     417
     418compil:
     419    $(CC) -o hcpu.o $(CFLAGS) hcpu.S
     420    @$(OD) -D hcpu.o > hcpu.o.s
     421    $(LD) -o kernel.x -T kernel.ld hcpu.o
     422    @$(OD) -D kernel.x > kernel.x.s
     423
     424exec: compil
     425    $(SX) -KERNEL kernel.x -NTTYS $(NTTY)
     426
     427clean:
     428    -rm *.o* *.x* *~ *.log.* proc?_term? 2> /dev/null || true
     429}}}
     4304. Au début du fichier se trouve la déclaration des variables du Makefile, quelle est la différence entre `=`, `?=` et `+=` ?
     431{{{#!protected ------------------------------------------------------------------------------------
     432'''''''''''''''
     433* `=`  fait une affectation simple
     434* `?=` 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`
     435* `+=` concatène la valeur courante à la valeur actuelle, c'est une concaténation de chaîne de caractères.
     436'''''''''''''''
     437}}}
     4381. Où est utilisé `CFLAGS` ? Que fait `-DNTTYS=$(NTTY)` et pourquoi est-ce utile ici ?
     439{{{#!protected ------------------------------------------------------------------------------------
     440'''''''''''''''
     441* 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 !
     442* `-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.
     443'''''''''''''''
     444}}}
     4451. Si on exécute `make` sans cible, que se passe-t-il ?
     446{{{#!protected ------------------------------------------------------------------------------------
     447'''''''''''''''
     448* 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.
     449'''''''''''''''
     450}}}
     4511. à quoi servent  `@` et `-` au début de certaines commandes ?
     452{{{#!protected ------------------------------------------------------------------------------------
     453'''''''''''''''
     454* `@` 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.
     455* `-`  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.
     456'''''''''''''''
     457}}}
     458
     459
     460== A5. Les modes d'exécution du MIPS
     461
     462
     463Dans cette section, nous allons nous intéresser à ce que propose le processeur MIPS concernant les modes d'exécution. Ce sont des questions portant sur l'usage des modes en général et le comportement du MIPS vis-à-vis de ces modes en particulier. Dans la section **A3**, nous verrons le code de gestion des changements de mode dans le noyau.
     464
     465
     466**Questions**
     467
     468
     4691. Le MIPS propose deux modes d'exécution, rappelez quels sont ces deux modes et à quoi ils servent? (''Nous l'avons dit dans le descriptif de la séance'').
     470{{{#!protected ------------------------------------------------------------------------------------
     471'''''''''''''''
     472- Il y a le mode kernel et le mode user.
     473- Le mode kernel est utilisé par le noyau alors que le mode user est utilisé par l'application.
     474'''''''''''''''
     475}}}
     4761. Commencez par rappeler ce qu'est l'espace d'adressage du MIPS et dîtes ce que signifie « une adresse X est mappée en mémoire ».\\Dîtes si une adresse X mappée en mémoire est toujours accessible (en lecture ou en écriture) quelque soit le mode d'exécution du MIPS.
     477{{{#!protected ------------------------------------------------------------------------------------
     478'''''''''''''''
     479- L'espace d'adressage du MIPS, c'est l'ensemble des adresses que peut produire le MIPS
     480- On dit qu'une adresse est mappée en mémoire, s'il y a bien une case mémoire pour cette adresse.
     481- Non X n'est pas toujours accessible, si X<`0x80000000` elle est bien accessible quelque-soit le mode d'exécution du MIPS, mais si X>=`0x80000000` alors X n'est accessible que si le MIPS est en mode kernel.
     482'''''''''''''''
     483}}}
     4841. Le MIPS propose des registres à usage général (GPR ''General Purpose Register'') pour les calculs ($0 à $31). Le MIPS propose un deuxième banc de registres à l'usage du système d'exploitation, ce sont les registres système (dit du coprocesseur 0).\\Comment sont-ils numérotés? Chaque registre porte un nom correspondant à son usage, quels sont ceux que vous connaissez: donner leur nom, leur numéro et leur rôle? Peut-on faire des calculs avec des registres? Quelles sont les instructions qui permettent de les manipuler?
     485{{{#!protected ------------------------------------------------------------------------------------
     486'''''''''''''''
     487- Les registres système sont numérotés de $0 à $31, comme les registres GPR, ce qui peut induire une certaine confusion
     488- Nous avons vu 6
     489   || `cr_sr`     || `$12` || contient essentiellement le mode d'exécution du MIPS et le bit d'autorisation des interruptions
     490   || `cr_cause`  || `$13` || contient la cause d'appel du noyau
     491   || `cr_epc`    || `$14` || contient l'adresse de l'instruction ayant provoqué l'appel du noyau ou l'adresse de l'instruction suivante
     492   || `cr_bar`    || `$8 ` || contient l'adresse mal formée si la cause est une exception due à un accès non aligné (p.ex. lw a une adresse non multiple de 4)
     493   || `cr_count`  || `$9 ` || contient le nombre de cycles depuis le démarrage du MIPS
     494   || `cr_procid` || `$15` || contient le numéro du processeur (utile pour les architectures multicores)
     495- non, il n'est pas possible de faire des calculs sur ces registres.
     496- On peut juste les lire et les écrire en utilisant les instructions `mtc0` et `mfc0`
     497'''''''''''''''
     498}}}
     4991. Le registre status est composé de plusieurs champs de bits qui ont chacun une fonction spécifique.\\Décrivez le contenu du registre status et le rôle des bits de l'octet 0 (seulement les bits vus en cours).
     500{{{#!protected ------------------------------------------------------------------------------------
     501'''''''''''''''
     502 || 0|| IE  ||Interrupt Enable||0 → interruptions masquées\\1 → interruptions autorisées
     503 || 1|| EXL ||EXception Level ||1 → MIPS en mode exception\\à l'entrée dans le kernel, le MIPS est en mode kernel, interruptions masquées
     504 || 2|| ERL ||ERror Level     ||0 → interruptions masquées\\1 → interruptions autorisées
     505 || 4|| UM  ||User Mode       ||0 → interruptions masquées\\1 → interruptions autorisées
     506'''''''''''''''
     507}}}
     5081. Le registre cause est contient la cause d'appel du kernel.\\Dites à quel endroit est stockée cette cause et donnez la signification des codes 0, 4 et 8
     509{{{#!protected ------------------------------------------------------------------------------------
     510'''''''''''''''
     511- Le champ `XCODE` qui contient le code de la cause d'entrée dans le noyau est codé sur 4 bits entre les bits 2 et 5.
     512- Les valeurs les plus importantes sont 0 (interruption et syscall). Les autres valeurs sont considérées comme des exceptions.
     513
     514  ||0|| 0000,,b,, || interruption || un contrôleur de périphérique à lever un signal IRQ
     515  ||4|| 0100,,b,, || ADEL         || lecture non-alignée (p. ex. `lw` a une adresse impaire)
     516  ||8|| 1000,,b,, || syscall      || exécution de l'instruction `syscall`
     517'''''''''''''''
     518}}}
     5191. Le registre `C0_EPC` est un registre 32 bits qui contient une adresse. Vous devriez l'avoir décrit dans la question 2.\\Expliquez pourquoi ce doit être l'adresse de l'instruction qui provoque une exception qui doit être stockée dans `C0_EPC`?
     520{{{#!protected ------------------------------------------------------------------------------------
     521'''''''''''''''
     522- Une exception, c'est une erreur du programme, telle qu'une division par 0, une lecture non alignée ou une instruction illégale. Il est important que le gestionnaire d'exception sache quelle est l'instruction fautive. C'est pour cette raison que EPC contient l'adresse de l'instruction fautive. Le gestionnaire pourra lire l'instruction et éventuellement corriger le problème.
     523- A titre indicatif, ce n'est pas la question, mais pour les syscall, c'est aussi l'adresse de l'instruction `syscall` qui est stockée dans `C0_EPC`, or pour le retour de `syscall`, on souhaite aller à l'instruction suivante. Il faut donc incrémenter la valeur de `C0_EPC` de 4 (les instructions font 4 octets) pour connaître l'adresse de retour.
     524'''''''''''''''
     525}}}
     5261. Nous avons vu trois instructions utilisables **seulement** lorsque le MIPS est en mode kernel, lesquelles? Que font-elles?\\Est-ce que l'instruction `syscall` peut-être utilisée en mode user?
     527{{{#!protected ------------------------------------------------------------------------------------
     528'''''''''''''''
     529- Les trois instructions sont
     530
     531  || `mtc0 $GPR, $C0` || `M`ove `T`o `C`oprocessor `0`   || `$GPR` → COPRO_0(`$C0`)
     532  || `mfc0 $GPR, $C0` || `M`ove `F`rom `C`oprocessor `0` || `$GPR` ←  COPRO_0(`$C0`)
     533  || `eret`           || `E`xpection `RET`urn            || `PC`  ←  `EPC` ; `c0_sr.EXL`  ←  `0`
     534- Bien sûr que `syscall` peut être utilisé en mode user, puisque c'est comme ça qu'on entre dans le kernel pour les demandes de services.
     535'''''''''''''''
     536}}}
     5371. Quelle est l'adresse d'entrée dans le noyau?
     538{{{#!protected ------------------------------------------------------------------------------------
     539'''''''''''''''
     540- C'est `0x80000180`. Il n'y a qu'une adresse pour toutes les causes `syscall`, exception et interruption.
     541- En fait, on peut considérer que `0xBFC00000` permet aussi d'entrer dans le noyau après un reset.
     542'''''''''''''''
     543}}}
     5441. Que se passe-t-il quand le MIPS entre dans le noyau, après l'exécution de l'instruction `syscall`?
     545{{{#!protected ------------------------------------------------------------------------------------
     546'''''''''''''''
     547- L'instruction `syscall` induit beaucoup d'opérations élémentaires dans le MIPS:
     548  - `EPC` ← `PC` (adresse de l'instruction `syscall`)
     549  - `c0_SR.EXL` ← `1`  (ainsi les bits `c0_SR.UM` et `c0_SR.IE` ne sont plus utilisés)
     550  - `c0_cause.XCODE` ← `8`
     551  - `PC` ← `0x80000180`
     552'''''''''''''''
     553}}}
     5541. Quelle instruction utilise-t-on pour sortir du noyau et entrer dans l'application ? Dîtes précisément ce que fait cette instruction dans le MIPS.
     555{{{#!protected ------------------------------------------------------------------------------------
     556'''''''''''''''
     557- C'est l'instruction `eret` qui permet de sortir du noyau.
     558  - `PC` ← `EPC`
     559  - `c0_SR.EXL` ← `0` (ainsi les bits `c0_SR.UM` et `c0_SR.IE` sont à nouveau utilisés)
     560'''''''''''''''
     561}}}
     562
     563
     564
     565== A6. Langage C pour la programmation système
     566
     567
     568La programmation en C, vous connaissez, mais quand on programme pour le noyau, c'est un peu différent.
     569Il y a des éléments de syntaxe ou des besoins spécifiques.
     570
     571
     572**Questions**
     573
     574
     5751. En assembleur, vous utilisez les sections prédéfinies `.data` et `.text` pour placer respectivement les data et le code ou alors vous pouvez créer vos propres sections avec la directive `.section` (nous avons utilisé cette possibilité pour la section `.boot`). Il est aussi possible d'imposer ou de créer des sections en langage C avec le mot clé `__attribute__`. Ce mot clé du C permet de demander certains comportements au compilateur. Il y a en a beaucoup  (si cela vous intéresse vous pouvez regarder dans la [https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Variable-Attributes.html doc de GCC sur les attributs]. En cours, nous avons vu un attribut permettant de désigner ou créer une section dans laquelle est mise la fonction concernée. Quelle était la syntaxe de cet attribut (regardez sur le slide 37).
     576{{{#!protected ------------------------------------------------------------------------------------
     577'''''''''''''''
     578- `__attribute__ ((section (".crt0")))`\\Remarquez la syntaxe un peu curieuse avec les doubles underscore et les doubles parenthèses.
     579'''''''''''''''
     580}}}
     5811. En C, vous savez que les variables globales sont toujours initialisées, soit explicitement dans le programme lui-même, soit implicitement à la valeur `0`. Les variables globales initialisées sont placées dans la section `.data` (ou plutôt dans l'une des sections `data` : `.data`, `.sdata`, `.rodata`, etc.) et elles sont présentes dans le fichier objet (`.o`) produit pas le compilateur. En revanche, les variables globales non explicitement initialisées ne sont pas présentes dans le fichier objet. Ces dernières sont placées dans un segment de la famille `.bss`. Le fichier ldscript permet de mapper l'ensemble des segments en mémoire. Pour pouvoir initialiser à `0` les segments `bss` par programme, il nous faut connaître les adresses de début et de fin où ils sont placés en mémoire.\\ \\Le code ci-dessous est le fichier ldscript du kernel `kernel.ld` (nous avons retier les commentaires mais ils sont dans les fichiers).\\Expliquez ce que font les lignes 11, 12 et 15.
     582{{{#!java
     583  1 SECTIONS
     584  2 {
     585  3     .boot : {
     586  4         *(.boot)           
     587  5     } > boot_region
     588  6     .ktext : {
     589  7         *(.text*)           
     590  8     } > ktext_region
     591  9     .kdata : {
     592 10         *(.*data*)         
     593 11         . = ALIGN(4);       
     594 12         __bss_origin = .;   
     595 13         *(.*bss*)           
     596 14         . = ALIGN(4);       
     597 15         __bss_end = .;     
     598 16     } > kdata_region
     599 17 }
     600}}}
     601{{{#!protected ------------------------------------------------------------------------------------
     602'''''''''''''''
     603- La ligne 11 contient `. = ALIGN(4)`, c'est équivalent à la directive `.align 4` de l'assembleur.
     604  Cela permet de déplacer le pointeur de remplissage de la section de sortie courante (c'est-à-dire ici `.kdata`) sur une
     605  frontière de 2^4^ octets (une adresse multiple de 16). Cette contrainte est liée aux caches que nous ne verrons pas ici.
     606- La ligne 12 permet de créer la variable de ldscript `__bss_origin` et de l'initialiser à l'adresse courante,
     607  ce sera donc l'adresse de début de la zone `bss`.
     608- La ligne 15 permet de créer la variable `__bss_end` qui sera l'adresse de fin de la zone `bss`
     609  (en fait c'est la première adresse qui suit juste `bss`.
     610'''''''''''''''
     611}}}
     6121. Nous connaissons les adresses des registres de périphériques. Ces adresses sont déclarées dans le fichier ldscript `kernel.ld`. Ci-après, nous avons la déclaration de la variable de ldscript `__tty_regs_map`. Cette variable est aussi utilisable dans les programmes C, mais pour être utilisable par le compilateur C, il est nécessaire de lui dire quel type de variable c'est, par exemple une adresse d'entier ou une adresse de tableau d'entiers, Ou encore, une adresse de structure.\\ \\Dans le fichier `kernel.ld`:
     613{{{#!c
     614__tty_regs_map   = 0xd0200000 ; /* tty's registers map, described in devices.h */
     615}}}
     616   Dans le fichier `harch.c` :
     617{{{#!c
     618 12 struct tty_s {
     619 13     int write;          // tty's output address
     620 14     int status;         // tty's status address something to read if not null)
     621 15     int read;           // tty's input address
     622 16     int unused;         // unused address
     623 17 };
     624 18
     625 19 extern volatile struct tty_s __tty_regs_map[NTTYS];
     626}}}
     627  À quoi servent les mots clés `extern` et `volatile` ?\\Si `NTTYS` est une macro dont la valeur est `2`, quelle est l'adresse en mémoire `__tty_regs_map[1].read` ?
     628{{{#!protected ------------------------------------------------------------------------------------
     629'''''''''''''''
     630- `extern` : informe le compilateur que la variable définie existe ailleurs. Grâce à son type, le compilateur sait s'en servir.
     631- `volatile` : informe le compilateur que la variable peut changer de valeur toute seule et que donc il doit toujours accéder en mémoire à chaque fois que le programme le demande. Il ne peut donc pas optimiser les accès mémoire en utilisant les registres.
     632- `__tty_regs_map` est un tableau à 2 cases (puisque `NTTYS`=`2`). Chaque case est une structure de 4 entiers, donc `0x10` octets.\\`read` est le troisième champ, c'est le troisième entier de la structure, donc en `+8` par rapport au début.\\En conséquence `__tty_regs_map[1].read` est en `0xd0200018`
     633'''''''''''''''
     634}}}
     6351. Certaines parties du noyau sont en assembleur. Il y a au moins les toutes premières instructions du code de boot (démarrage de l'ordinateur) et l'entrée dans le noyau après l'exécution d'un syscall. Le gestionnaire de syscall est écrit en assembleur et il a besoin d'appeler une fonction écrite en langage C. Ce que fait le gestionnaire de syscall est:
     636 - trouver l'adresse de la fonction C qu'il doit appeler pour exécuter le service demandé;
     637 - placer cette adresse dans un registre, par exemple `$2`;
     638 - exécuter l'instruction `jal` (ici, `jal $2`) pour appeler la fonction.
     639
     640 Que doivent contenir les registres `$4` à `$7` et comment doit-être la pile?
     641{{{#!protected ------------------------------------------------------------------------------------
     642'''''''''''''''
     643- C'est un appel de fonction, il faut donc respecter la convention d'appel des fonctions
     644  - Les registres `$4`à `$7` contiennent les arguments de la fonction
     645  - Le pointeur de pile doit pointer sur la case réservée pour le premier argument et les cases suivantes sont réservées
     646    arguments suivants.
     647  - Ce n'est pas rappelé ici, mais il y a **au plus** 4 arguments (entier ou pointeur) pour tous les syscalls.
     648    En conséquence, le pointeur de pile pointe au début d'une zone vide de 4 entiers.
     649'''''''''''''''
     650}}}
     6511. Vous avez appris à écrire des programmes assembleur, mais parfois il est plus simple, voire nécessaire, de mélanger le code C et le code assembleur. Dans l'exemple ci-dessous, nous voyons comment la fonction `kinit()` procède pour entrer dans la fonction placée à l'adresse `__crt0` définie dans le fichier `kernel.ld`.\\Remarquez la syntaxe, ici `volatile` permet de dire au compilateur d'insérer le code tel que sans le modifier. Notez aussi l'absence de `,` entre les chaînes de caractères. Le premier argument de `__asm__` est une chaîne de caractères **unique** dans laquelle les instructions sont séparées par de `\n`. Il peut y avoir d'autres arguments, nous n'en aurons pas besoin.\\ \\Dans quelle section se trouve l'adresse `__crt0`? Combien vaut-elle? Est-ce que cette valeur est imposée par le processeur MIPS comme l'adresse de boot ou d'entrée dans le kernel? Quelle fonction est à cette adresse? Pourquoi doit-on écrire ce code en assembleur?
     652{{{#!c
     653  9 void kinit (void)
     654 10 {
     655 11     kprintf (0, banner);
     656 12
     657 13     // put bss sections to zero. bss contains uninitialised global variables
     658 14     extern int __bss_origin;    // first int of bss section
     659 15     extern int __bss_end;       // first int of above bss section
     660 16     for (int *a = &__bss_origin; a != &__bss_end; *a++ = 0);
     661 17
     662 18     // this code allows to exit the kernel to go to user code
     663 19     __asm__ volatile (   "la     $26,    __text_origin  \n"     // get first address of user code
     664 20                          "mtc0   $26,    $14            \n"     // put it in c0_EPC
     665 21                          "li     $26,    0b00010010     \n"     // next status [UM,0,ERL,EXL,IE]
     666 22                          "mtc0   $26,    $12            \n"     // UM <- 1, IE <- 0, EXL <- 1
     667 23                          "la     $29,    __data_end     \n"     // define new user stack pointer
     668 24                          "eret                          \n");   // j EPC and EXL <- 0
     669 25 }
     670}}}
     671{{{#!protected ------------------------------------------------------------------------------------
     672'''''''''''''''
     673- L'adresse `__crt0` est la première adresse de la section `text` dans laquelle se trouve le code de l'application.
     674- Elle vaut `0X7F400000`.
     675- Cette adresse n'est pas imposée par le MIPS. C'est le choix des architectes du SoC. La seule condition est que cette adresse soit
     676  dans la partie accessible en mode user.
     677- A cette adresse, on place la fonction `__start()`.
     678- On est obligé d'écrire ce code en assembleur parce que la manière de changer de mode (de `kernel` à `user`) est propre à chaque processeur. Il n'y a aucun moyen de le faire en C.
     679'''''''''''''''
     680}}}
     6811. Dans le code C de la question précédente, à quoi servent les lignes 14 à 16? Pourquoi faire des déclarations `extern`?
     682{{{#!protected ------------------------------------------------------------------------------------
     683'''''''''''''''
     684- Ces lignes servent à mettre à 0 la zone des variables globales non initialisées explicitement par le programme.
     685- Les déclarations `extern` permettent d'informer le compilateur que les adresses `__bss_orgin` et `__bss_end`
     686  existent ailleurs. De fait, elles sont définies dans le fichier `kernel.ld`.
     687'''''''''''''''
     688}}}
     689
     690
     691== A7. Passage entre les modes kernel et user
     692
     693
     694Le noyau et l'application sont deux exécutables compilés indépendamment mais pas qui ne sont pas indépendants. Vous savez déjà que l'application appelle les services du noyau avec l'instruction `syscall`, voyons comment cela se passe vraiment depuis le code C. Certaines questions sont proches de celles déjà posées, c'est volontaire.
     695
     696
     697**Questions**
     698
     699
     7001. Comment imposer le placement d'adresse d'une fonction ou d'une variable en mémoire?
     701{{{#!protected ------------------------------------------------------------------------------------
     702'''''''''''''''
     703- C'est l'éditeur de lien qui est en charge du placement en mémoire du code et des données, et c'est dans le fichier ldscript `kernel.ld` ou `user.ld` que le programmeur peut imposer ses choix.
     704- Pour placer une fonction à une place, la méthode que vous avez vu consiste
     705  - à créer une section grâce à la directive `.section` en assembleur ou à la directive `__attribute__((section()))` en C
     706  - puis à positionner la section créée dans la description des `SECTIONS` du ldscript.
     707'''''''''''''''
     708}}}
     7091. Dans la question **A2.5**, nous avons vu comment la fonction `kinit()` appelle la fonction `__start()` grâce à un bout de code en assembleur placé au début de la section `.text`. Nous allons voir maintenant quelles sont les conditions de cet appel. Dans le code de la question **A2.5**, `$26` est un registre de travail pour le kernel. Quels sont les autres registres modifiés? Expliquez pour chacun la valeur affectée.
     710{{{#!protected ------------------------------------------------------------------------------------
     711'''''''''''''''
     712- Il y a 3 registres affectés, dans l'ordre :
     713  - Le registre système `$14` nommé `c0_epc`, il reçoit l'adresse `__crt0`, c'est-à-dire l'adresse de la fonction `__start()`.
     714  - Le registre système `$12` nommé `c0_sr`, il reçoit la valeur `0x12`, donc les bits `UM`, `EXL` et `IE` prennent respectivement les valeurs `1`, `1` et `0`
     715    - UM = 1 et IE = 0, signifie que l'on est normalement en mode `user` avec les interruptions masquées,
     716      **mais** comme `EXL`=`1`, alors on reste en mode `kernel` avec interruptions masquées. L'exécution de l'instruction `eret` mettra `EXL` à `0` pour rendre les bits `UM` et `IE` actifs et passer en mode `user` (ici avec interruptions masquées).
     717  - Le registre GPR `$29` reçoit l'adresse de la première adresse après la section `.data`. C'est le haut de la pile. 
     718'''''''''''''''
     719}}}
     7201. Que faire avant l'exécution de la fonction `main()` du point de vue de l'initialisation? Et au retour de la fonction `main()`?
     721{{{#!protected ------------------------------------------------------------------------------------
     722'''''''''''''''
     723- Comme dans la fonction `kinit()`, il faut explicitement initialiser les variables globales non initialisées dans le programme C.
     724- Si on sort de la fonction `main()`, l'application s'achève. Cela signifie qu'il faut appeler la fonction `exit()` qui effectue l'appel système EXIT. Cette appel est réalisé au cas où l'application n'aurait pas explicitement exécuté `exit()`.
     725'''''''''''''''
     726}}}
     7271. Nous avons vu que le noyau est sollicité par des événements, quels sont-ils? Nous rappelons que l'instruction `syscall` initialise le registre `c0_cause`, comment le noyau fait-il pour connaître la cause de son appel?
     728{{{#!protected ------------------------------------------------------------------------------------
     729'''''''''''''''
     730- Il y en a 3 (si on excepte le signal `reset` qui redémarre tout le système:
     731  1. Les appels système donc l'exécution de l'instruction `syscall`.
     732  1. Les exceptions donc les "erreur" de programmation (division par 0, adressage mémoire incorrect, etc.).
     733  1. Les interruptions qui sont des demandes d'intervention provenant des périphériques.
     734- L'instruction `syscall` initialise les 4 bits `XCODE` du registre `c0_cause` avec un code indiquant la raison de l'entrée dans le noyau. Le noyau doit analyser ce champ `XCODE`.
     735'''''''''''''''
     736}}}
     7371. `$26` et `$27` sont deux registres temporaires que le noyau se réserve pour faire des calculs sans qu'il ait besoin de les sauvegarder dans la pile. Ce ne sont pas des registres système comme `c0_sr` ou `c0_epc`. En effet, l'usage de ces registres (`$26` et `$27`) par l'utilisateur ne provoque pas d'exception du MIPS. Toutefois si le noyau est appelé alors il modifie ces registres et donc l'utilisateur perd leur valeur.\\Le code assembleur ci-après contient les instructions exécutées à l'entrée dans le noyau, quelle que soit la cause. Les commentaires présents dans le code ont été volontairement retirés (ils sont dans les fichiers du TP). La section `.kentry` est placée à l'adresse `0x80000000` par l'éditeur de lien. La directive `.org` (ligne 16) permet de déplacer le pointeur de remplissage de la section courante du nombre d'octets donnés en argument, ici `0x180`. Pouvez-vous dire pourquoi ? Expliquer les lignes 25 à 28.\\ \\**`kernel/hcpu.S`**
     738{{{#!c
     739 15 .section    .kentry,"ax"     
     740 16 .org        0x180           
     741 22
     742 23 kentry:                               
     743 24
     744 25     mfc0    $26,    $13                     
     745 26     andi    $26,    $26,    0x3C         
     746 27     li      $27,    0x20                   
     747 28     bne     $26,    $27,    not_syscall     
     748}}}
     749{{{#!protected ------------------------------------------------------------------------------------
     750'''''''''''''''
     751- La section `kentry` est placée à l'adresse `0x80000000` or l'entrée du noyau est `0x80000180`, il faut donc déplacer le pointeur de remplissage de la section `ktentry` de `0x180`. Remarquez qu'on aurait pu utiliser une directive `.space 0x180`.
     752- Commentaire du code
     753  - Ligne 25 : `$26` **←**  `c0_cause`\\⟶ donc le registre `$26`GPR réservé au kernel prend la valeur du registre de cause.
     754  - Ligne 26 : `$26` **←**  `$26 & 0b00111100`\\⟶ C'est un masque qui permet de ne conserver que les 4 bits du champ `XCODE`.
     755  - Ligne 27 : `$27` **←**  `0b00100000`\\⟶ On initialise le registre GPR réservé au kernel $27 avec la valeur attendue dans $26 s'il s'agit d'une cause `syscall`.
     756  - Ligne 28 : si `$26` ≠ `$27` goto not_syscall\\⟶ Si ce n'est pas un `syscall`, on va plus loin, sinon on continue en séquence.
     757'''''''''''''''
     758}}}
     7591. Le gestionnaire de `syscall` est la partie du code qui gère le comportement du noyau lors de l'exécution de l'instruction `syscall`. C'est un code en assembleur présent dans le fichier `kernel/hcpu.S` que nous allons observer. Pour vous aider dans la compréhension de ce code, vous devez imaginer que l'instruction `syscall` est un peu comme un appel de fonction. Ce code utilise un tableau de pointeurs de fonctions nommé `syscall_vector` définit dans le fichier `kernel/ksyscalls.c`. Les lignes `36` à `43` sont chargées d'allouer de la place dans la pile.\\Dessinez l'état de la pile après l'exécution de ces instructions. Que fait l'instruction ligne `44` et quelle conséquence cela a-t-il? Que font les lignes `46` à `51`? Et enfin que font les lignes `53` à `59` sans détailler ligne à ligne.\\ \\**`common/syscalls.h`**
     760{{{#!c
     761  1 #define SYSCALL_EXIT        0
     762  2 #define SYSCALL_TTY_WRITE   1
     763  3 #define SYSCALL_TTY_READ    2
     764  4 #define SYSCALL_CLOCK       3
     765  5 #define SYSCALL_NR          32
     766}}}
     767  **`kernel/ksyscalls.c`**
     768{{{#!c
     769void *syscall_vector[] = {
     770    [0 ... SYSCALL_NR - 1] = unknown_syscall,
     771    [SYSCALL_EXIT]      = exit,
     772    [SYSCALL_TTY_READ]  = tty_read,
     773    [SYSCALL_TTY_WRITE] = tty_write,
     774    [SYSCALL_CLOCK]     = clock,
     775};
     776}}}
     777  **`kernel/hcpu.S`**
     778{{{#!xml
     779 34 ksyscall:
     780 35
     781 36     addiu   $29,    $29,    -8*4           
     782 37     mfc0    $27,    $14                     
     783 38     mfc0    $26,    $12                     
     784 39     addiu   $27,    $27,    4               
     785 40     sw      $31,    7*4($29)               
     786 41     sw      $27,    6*4($29)               
     787 42     sw      $26,    5*4($29)               
     788 43     sw      $2,     4*4($29)               
     789 44     mtc0    $0,     $12                     
     790 45
     791 46     la      $26,    syscall_vector         
     792 47     andi    $2,     $2,     SYSCALL_NR-1   
     793 48     sll     $2,     $2,     2               
     794 49     addu    $2,     $26,    $2             
     795 50     lw      $2,     0($2)                   
     796 51     jalr    $2                             
     797 52
     798 53     lw      $26,    5*4($29)               
     799 54     lw      $27,    6*4($29)               
     800 55     lw      $31,    7*4($29)               
     801 56     mtc0    $26,    $12                     
     802 57     mtc0    $27,    $14                     
     803 58     addiu   $29,    $29,    8*4             
     804 59     eret                       
     805}}}
     806{{{#!protected ------------------------------------------------------------------------------------
     807'''''''''''''''
     808- État de la pile après l'exécution des lignes 36 à 43
     809{{{#!xml
     810      +----------+
     811      |    $31   |  Nous allons exécuter jal un peu plus et perdre $31, il faut le sauver
     812      +----------+
     813      |  C0_EPC  |  C'est l'adresse de retour du syscall
     814      +----------+
     815      |  C0_SR   |  le registre status est modifié plus loin, il faut le sauver pour le restaurer
     816      +----------+
     817      |    $2    |  C'est le numéro de syscall qui pourra être accédé par la fonction appelée en 5e argument
     818      +----------+
     819      |          |  place réservée pour le 4e argument actuellement dans $7
     820      +----------+
     821      |          |  place réservée pour le 3e argument actuellement dans $6
     822      +----------+
     823      |          |  place réservée pour le 2e argument actuellement dans $5
     824      +----------+
     825$29 → |          |  place réservée pour le 1e argument actuellement dans $4
     826      +----------+
     827}}}
     828- L'instruction ligne 44 met `0` dans le registre `c0_sr`. Ce qui a pour conséquence de mettre à `0` les bits `UM`, `EXL` et `IE`. On est donc en mode kernel avec interruptions masquées.
     829  - ''Notez qu'interdire les interruptions pendant l'exécution des syscall est contraignant. Pour le moment, ce n'est pas important puisque nous ne traitons pas les interruptions, mais si nous les traitions, elles seraient masquées. En conséquence, il serait interdit aux fonctions qui traitent les appels système d'exécuter des attentes longues (comme une boucle qui attend le changement d'état d'un registre de périphérique) car sinon, le noyau serait bloqué (plus rien ne bougerait).''\\ \\
     830- Commentaire du code lignes 46 à 53
     831  - Ligne 46 : `$26` **←** l'adresse du tableau syscall_vector\\⟶ On s'apprête à y faire un accès indexé par le registre `$2`
     832  - Ligne 47 : `$2`  **←** `$2 & 0x1F`\\⟶ pour éviter de sortir du tableau si l'utilisateur à mis n'importe quoi dans `$2`
     833  - Ligne 48 : `$2`  **←** `$2 * 4`\\⟶ Les cases du tableau sont des pointeurs et font 4 octets
     834  - Ligne 49 : `$2`  **←** `$26 + $2`\\⟶ `$2` contient désormais l'adresse de la case contenant la fonction correspondante au service n°`$2`
     835  - Ligne 50 : `$2` **←** MEM[`$2`] \\⟶ $2 contient l'adresse de la fonction à appeler
     836  - Ligne 51 :  jal $2  \\⟶ appel de la fonction de service\\On rappelle que `$4` à `$7` et qu'il y a de place pour ces arguments dans la pile.\\ \\
     837- Les lignes 53 à 59 restaurent l'état des registres `$31`, `c0_status`, `c0_epc` et le pointeur de pile puis on sort du noyau avec l'instruction `eret`.
     838'''''''''''''''
     839}}}
     840
     841
     842== A8. Génération du code exécutable
     843
     844
     845Pour simuler le logiciel, il faut produire deux exécutables. Nous utilisons, ici, un Makefile hiérarchique et des règles explicites.
     846Cela sort du cadre de l'architecture, mais vous avez besoin de ce savoir-faire pour comprendre le code, alors allons-y.
     847
     848
     849**Questions**
     850
     851
     8521. Rappelez à quoi sert un Makefile?
     853{{{#!protected ------------------------------------------------------------------------------------
     854'''''''''''''''
     855- Le rôle principal d'un Makefile est de décrire le mode d'emploi pour construire un fichier dit **`cible`** à partir d'un ou plusieurs fichiers **`source`** (dits de dépendance) en utilisant des commandes du `shell`. Ce rôle pourrait tout aussi bien être occupé par un script `shell` et d'ailleurs, dans le premier TP, nous avons vu un usage du Makefile dans lequel nous avions rassemblé plusieurs scripts `shell` sous forme de règles.
     856- Le second rôle d'un Makefile est de permettre la reconstruction efficace du fichier **`cible`** lorsqu'un seul fichier **`source`** change. Pour ce rôle, le Makefile exprime toutes les étapes de constructions de la **`cible`** finale et des **`cibles`** intermédiaires sous forme d'un arbre dont les feuilles sont les fichiers **`sources`**.
     857'''''''''''''''
     858}}}
     8591. Vous n'allez pas à avoir à écrire un Makefile complètement. Toutefois, si vous ajoutez des fichiers source, vous allez devoir les modifier en ajoutant des règles. Nous avons vu brièvement la syntaxe utilisée dans les Makefiles de ce TP au cours n°1. Les lignes qui suivent sont des extraits de `1_klibc/Makefile` (le Makefile de l'étape1). Dans cet extrait, quelles sont la `cible` finale, les `cibles` intermédiaires et les `sources`? A quoi servent les variables automatiques de make? Dans ces deux règles, donnez-en la valeur.
     860{{{#!make
     861kernel.x : kernel.ld obj/hcpu.o obj/kinit.o obj/klibc.o obj/harch.o
     862    $(LD) -o $@ -T $^
     863    $(OD) -D $@ > $@.s
     864
     865obj/hcpu.o : hcpu.S hcpu.h
     866    $(CC) -o $@ $(CFLAGS) $<
     867    $(OD) -D $@ > $@.s
     868}}}
     869{{{#!protected ------------------------------------------------------------------------------------
     870'''''''''''''''
     871- La `cible` finale est : `kernel.x`
     872- Les `cibles` intermédiaires sont : `kernel.ld`, `obj/hcpu.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`.
     873- La `source` est : `hcpu.S`
     874- Les variables automatiques servent à extraire des noms dans la définition de la dépendance (`cible : dépendances`)
     875  - dans la première règle :
     876    - `$@` = `cible` = `kernel.x`
     877    - `$^` = l'ensemble des dépendances = `kernel.ld`, `obj/hcpu.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`
     878  - dans la seconde règle :
     879    - `$@` = `cible` = `obj/hcpu.o`
     880    - `$<` = la première des dépendances = `hcpu.S`
     881'''''''''''''''
     882}}}
     8831. Dans le TP, à partir de la deuxième étape, nous avons trois répertoires de sources `kernel`, `ulib` et `uapp`. Chaque répertoire contient une fichier `Makefile` différent destiné à produire une `cible` différente grâce à une règle nommée `compil`, c.-à-d. si vous tapez `make compil` dans un de ces répertoires, cela compile les sources locales.\\Il y a aussi un Makefile dans le répertoire racine `4_libc`. Dans ce dernier Makefile, une des règles est destinée à la compilation de l'ensemble des sources dans les trois sous-répertoires. Cette règle appelle récursivement la commande `make` en donnant en argument le nom du sous-répertoire où descendre :\\`make -C <répertoire> [cible]` est équivalent à `cd <répertoire>; make [cible] ; cd ..`\\Ecrivez la règle `compil` du fichier `4_libc/Makefile`.
     884{{{#!xml
     8854_libc/
     886├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     887├── common ────────── répertoire des fichiers commun kernel / user
     888├── kernel ────────── Répertoire des fichiers composant le kernel
     889│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     890├── uapp ──────────── Répertoire des fichiers de l'application user seule
     891│   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     892└── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
     893    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     894}}}
     895{{{#!protected ------------------------------------------------------------------------------------
     896'''''''''''''''
     897{{{#!make
     898compil:
     899    make -C kernel compil
     900    make -C ulib   compil
     901    make -C uapp   compil
     902}}}
     903'''''''''''''''
     904}}}
     905
     906
     907
     908==
     909= B. Travaux pratiques
     910
     911
     912
     913Pour 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.
     914
     915Le code se trouve dans `$AS6/tp1/`, ouvrez un terminal et allez-y. Dans ce répertoire, vous avez 5 sous-répertoires et un Makefile. Le fichier `$AS6/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é.
     916
     917
     918
     919== B1. Premier programme en assembleur dans la seule section de boot
     920
     921
     922
     923Nous commençons par un petit programme de quelques lignes en assembleur, placé entièrement dans la région mémoire
     924du boot, qui réalise l'affichage du message "Hello World". C'est un tout tout petit programme, mais pour obtenir
     925l'exécutable, vous devrez utiliser tous les outils de la chaîne de cross-compilation MIPS et
     926pour l'exécuter vous devrez exécuter le simulateur du prototype. C'est simple, mais c'est nouveau pour
     927beaucoup d'entre vous
     928
     929**Objectifs**
     930
     931- produire un exécutable à partir d'un code en assembleur.
     932- savoir comment afficher un caractère sur un terminal.
     933- analyse d'une trace d'exécution
     934
     935**Fichiers**
     936
     937{{{
     9381_hello_boot
     939├── hcpu.S       : code dépendant du cpu matériel en assembleur
     940├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     941└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     942}}}
     943
     944
     945
     946**Questions**
     947
     948
     9491. Dans quel fichier se trouve la description de l'espace d'adressage du MIPS ? Que trouve-t-on dans ce fichier ?
     950{{{#!protected ------------------------------------------------------------------------------------
     951'''''''''''''''
     952- C'est dans le fichier kernel.ld. \\On y trouve:
     953  - la définition de variables du ldscript. Ce sont essentiellement des adresses dans l'espace d'adressage,
     954    mais pas seulement, il y a aussi la taille des régions.
     955  - On trouve ensuite la déclaration des régions mémoires.
     956  - 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.
     957'''''''''''''''
     958}}}
     9591. Dans quel fichier se trouve le code de boot et pourquoi, selon vous, avoir nommé ce fichier ainsi ?
     960{{{#!protected ------------------------------------------------------------------------------------
     961'''''''''''''''
     962- Le code de boot est dans le fichier `hcpu.S`. Il a a été nommé ainsi parce que c'est du code qui dépend du
     963  hardware et qu'il concerne le cpu.
     964'''''''''''''''
     965}}}
     9661. À quelle adresse démarre le MIPS ? Où peut-on le vérifier ?
     967{{{#!protected ------------------------------------------------------------------------------------
     968'''''''''''''''
     969- L'adresse de démarrage est `0xBFC00000`.
     970- On peut le vérifier dans le fichier `kernel.ld`.
     971  Il y a une définition des régions mémoires, dont une région commençant à cette adresse-là, et c'est dans
     972  cette région que l'on met le code de boot.
     973'''''''''''''''
     974}}}
     9751. Que produit `gcc` quand on utilise l'option `-c` ?
     976{{{#!protected ------------------------------------------------------------------------------------
     977'''''''''''''''
     978- L'option `-c` demande à `gcc` de s'arrêter après avoir produit le fichier objet.
     979- Il produit donc un fichier au format `.o`
     980'''''''''''''''
     981}}}
     9821. Que fait l'éditeur de liens ? Comment est-il invoqué ?
     983{{{#!protected ------------------------------------------------------------------------------------
     984'''''''''''''''
     985- 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`).
     986- L'éditeur de liens est appelé par `gcc` si on n'a pas l'option `-c`ou directement par `ld` (ici `mipsel_unknown_ld`)
     987'''''''''''''''
     988}}}
     9891. De quels fichiers a besoin l'éditeur de liens pour fonctionner ?
     990{{{#!protected ------------------------------------------------------------------------------------
     991'''''''''''''''
     992- L'éditeur de liens a besoin des fichiers objets `.o` et du fichier ldscript (ici, `kernel.ld`)
     993'''''''''''''''
     994}}}
     9951. Dans quelle section se trouve le code de boot pour le compilateur ? ''(la réponse est dans le code assembleur)''
     996{{{#!protected ------------------------------------------------------------------------------------
     997'''''''''''''''
     998- Le code de boot a été mis dans une section `.text`.
     999'''''''''''''''
     1000}}}
     10011. Dans quelle section se trouve le message hello pour le compilateur ? Ce choix est particulier, mais ce message est en lecture seule.
     1002{{{#!protected ------------------------------------------------------------------------------------
     1003'''''''''''''''
     1004- Le message est aussi la section `.text`.
     1005'''''''''''''''
     1006}}}
     10071. Dans quelle section se trouve le code de boot dans le code exécutable ?
     1008{{{#!protected ------------------------------------------------------------------------------------
     1009'''''''''''''''
     1010- Dans le programme exécutable, le code de boot est mis dans la section `.boot`.
     1011'''''''''''''''
     1012}}}
     10131. Dans quelle région de la mémoire le code de boot est-il placé ?
     1014{{{#!protected ------------------------------------------------------------------------------------
     1015'''''''''''''''
     1016- Le code de boot est placé dans la région `boot_region`
     1017'''''''''''''''
     1018}}}
     10191. Comment connaît-on l'adresse du registre de sortie du contrôleur de terminal `TTY` ?
     1020{{{#!protected ------------------------------------------------------------------------------------
     1021'''''''''''''''
     1022- Le fichier `kernel.ld` déclare une variable `__tty_regs_map` initialisée avec l'adresse de
     1023  où sont placés les registres de contrôles du `TTY`. Le premier registre à l'adresse `__tty_regs_map`
     1024  est l'adresse du registre de sortie `TTY_WRITE`.
     1025'''''''''''''''
     1026}}}
     10271. Le code de boot se contente d'afficher un message, comment sait-on que le message est fini
     1028   et que le programme doit s'arrêter ?
     1029{{{#!protected ------------------------------------------------------------------------------------
     1030'''''''''''''''
     1031- C'est quand la boucle d'affichage détecte le `0` terminateur de la chaîne de caractères.
     1032'''''''''''''''
     1033}}}
     10341. Pourquoi terminer le programme par un `dead: j dead` ?
     1035{{{#!protected ------------------------------------------------------------------------------------
     1036'''''''''''''''
     1037- If faut arrêter le programme, car il n'y a plus de code, mais on ne sait pas arrêter le processeur,
     1038  alors on le faire tourner en rond.
     1039'''''''''''''''
     1040}}}
     1041
     1042**Exercices**
     1043 
     1044- Exécutez le programme en lançant le simulateur avec `make exec`, qu'observez-vous ?
     1045- 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 un fichier `debug.log` contenant des informations par cycle.\\Ce fichier n'est pas exploitable directement par vous, il est nécessaire pour la génération de la trace d'exécution à l'étape suivante.
     1046- Exécutez alors la génération de la trace d'exécution avec `make trace`.\\Cela génère le fichier `trace.log.s` à partir du de l'exécutable désassemblé et du fichier debug.log. Que voyez-vous dans `trace.log.s` ? 
     1047- Modifiez le code de `hcpu.S` afin d'afficher le message "Au revoir\n" (''Hommage VGE'') après le message "Hello".\\
     1048  Vous devez avoir deux messages, et pas seulement étendre le premier.
     1049{{{#!protected ------------------------------------------------------------------------------------
     1050'''''''''''''''
     1051* Ils doivent dupliquer la boucle d'affichage et le message. Il faut juste faire attention aux labels en en créant des nouveaux.
     1052  Ils ne peuvent pas utiliser des fonctions parce qu'ils n'ont pas de pile.
     1053'''''''''''''''
     1054}}}
     1055
     1056
     1057
     1058== B2. Saut dans la fonction kinit() du noyau en langage C
     1059
     1060
     1061
     1062Dans ce second programme, le code de boot entre dans le noyau par la fonction C `kinit()`, c'est une fonction et donc il faut absolument une pile d'exécution.
     1063
     1064**Objectifs**
     1065
     1066- Savoir comment et où déclarer la pile d'exécution du noyau.
     1067- Savoir comment afficher un caractère sur un terminal depuis un programme C.
     1068
     1069**Fichiers**
     1070
     1071{{{
     10722_init_c/
     1073├── hcpu.S       : code dépendant du cpu matériel en assembleur
     1074├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     1075├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
     1076└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     1077}}}
     1078
     1079**Questions**
     1080
     10811. Quand faut-il initialiser la pile ? Dans quel fichier est-ce ? Quelle est la valeur du pointeur initial ?
     1082{{{#!protected ------------------------------------------------------------------------------------
     1083'''''''''''''''
     1084- Il faut initialiser le pointeur avant d'appeler `kinit()`
     1085- C'est dans le fichier `hcpu.S`
     1086- `$29` ← `__kdata_end`, c'est-à-dire `0x80400000`
     1087'''''''''''''''
     1088}}}
     10891. Dans quel fichier le mot clé `volatile` est-il utilisé ? Rappeler son rôle.
     1090{{{#!protected ------------------------------------------------------------------------------------
     1091'''''''''''''''
     1092* 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 doivent aussi toujours toutes 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.
     1093'''''''''''''''
     1094}}}
     1095
     1096**Exercices**
     1097
     1098- 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.
     1099{{{#!protected ------------------------------------------------------------------------------------
     1100'''''''''''''''
     1101* Dans kinit.o.s, l'adresse de `kinit` est `0` alors que `kernel.x.s` l'adresse est `0x80000000`.
     1102* Dans kinit.o.s, `kinit` est dans la section `.text` alors que dans `kernel.x.s` `kinit` est dans la section `.ktext`.
     1103* La raison est que
     1104  * 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.
     1105  * dans `kernel.x.s` `kinit` est placé et mis dans la section `.ktext` comme le fichier `kernel.ld` le demande.
     1106'''''''''''''''
     1107}}}
     1108- Modifiez le code de `kinit.c`, et comme pour l'étape 1, afficher un second message ?
     1109{{{#!protected ------------------------------------------------------------------------------------
     1110'''''''''''''''
     1111* 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 qu'ils ouvrent le code
     1112'''''''''''''''
     1113}}}
     1114
     1115
     1116== B3. Premier petit pilote pour le terminal
     1117
     1118
     1119Le 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, 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.
     1120
     1121**Objectifs**
     1122
     1123- Savoir comment créer un début de pilote pour le terminal `TTY`.
     1124- Savoir comment décrire une API en C
     1125- Savoir appeler une fonction en assembleur depuis le C
     1126 
     1127**Fichiers**
     1128
     1129{{{
     11303_driver/
     1131├── harch.c      : code dépendant de l'architecture du SoC, pour le moment c'est juste le pilote du TTY
     1132├── harch.h      : API du code dépendant de l'architecture
     1133├── hcpu.h       : prototype de la fonction clock()
     1134├── hcpu.S       : code dépendant du cpu matériel en assembleur
     1135├── kernel.ld    : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     1136├── kinit.c      : fichier en C contenant le code de démarrage du noyau, ici c'est la fonction kinit().
     1137└── Makefile     : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     1138}}}
     1139   
     1140**Questions**
     1141
     11421. 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_read()` et `tty_write()` sont présents. La structure décrivant la carte des registres du `TTY` est déclarée dans le .c. Pourquoi avoir fait ainsi ?
     1143{{{#!protected ------------------------------------------------------------------------------------
     1144'''''''''''''''
     1145- 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.
     1146'''''''''''''''
     1147}}}
     11481. 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 `hcpu.S` ? Rappeler, pourquoi avoir mis `.globl clock`
     1149{{{#!protected ------------------------------------------------------------------------------------
     1150'''''''''''''''
     1151* La fonction qui lit ce registre (`$9` qui 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 hcpu.S.
     1152* `.globl clock` permet de faire en sorte que la fonction soit visible par les autres fichiers C.
     1153'''''''''''''''
     1154}}}
     11551. 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()`
     1156{{{#!protected ------------------------------------------------------------------------------------
     1157'''''''''''''''
     1158* 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 code binaire l'adresse de `kinit()` mise dans le registre `$26` soit la bonne.
     1159'''''''''''''''
     1160}}}
     1161
     1162**Exercices**
     1163
     1164- Ecrire une fonction `void Capitalize(void)` appelée par la fonction `kinit()` qui lit une phrase terminée par un `\n` et la réécrit en ayant mis en majuscule la première lettre de chaque mot.
     1165
     1166
     1167== B4. Ajout d'une bibliothèque de fonctions standards pour le kernel (klibc)
     1168
     1169
     1170
     1171**Objectifs de l'étape**
     1172
     1173Le noyau gère les ressources matérielles et logicielles utilisées par les applications. Il a besoin de fonctions standards pour réaliser des opérations de base, telles qu'une fonction `print` ou une fonction `rand`. Ces fonctions ne sont pas très originales, mais elles recèlent des subtilités que vous ne connaissez peut-être pas encore, vous pouvez les regarder par curiosité. En outre, nous allons utiliser un Makefile définissant un graphe de dépendance explicite entre les fichiers cibles et les fichiers sources avec des règles de construction.
     1174
     1175
     1176**Fichiers**
     1177
     1178
     1179{{{#!xml
     11804_klibc/
     1181├── kinit.c         : fichier contenant la fonction de démarrage du noyau
     1182├── harch.h         : API du code dépendant de l'architecture
     1183├── harch.c         : code dépendant de l'architecture du SoC
     1184├── hcpu.h          : prototype de la fonction clock()
     1185├── hcpu.S          : code dépendant du cpu matériel en assembleur
     1186├── kernel.ld       : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     1187├── klibc.h         : API de la klibc
     1188├── klibc.c         : fonctions standards utilisées par les modules du noyau
     1189└── Makefile        : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     1190}}}
     1191
     1192
     1193**Questions**
     1194
     1195
     11961. Ouvrez le fichier Makefile, En ouvrant tous les fichiers dessiner le graphe de dépendance de `kernel.x` vis-à-vis de ses sources?
     1197{{{#!protected ------------------------------------------------------------------------------------
     1198'''''''''''''''
     1199{{{#!make
     1200kernel.x : kernel.ld obj/hcpu.o obj/kinit.o obj/klibc.o obj/harch.o
     1201obj/hcpu.o : hcpu.S
     1202obj/kinit.o : kinit.c klibc.h
     1203obj/klibc.o : klibc.c klibc.h harch.h
     1204obj/harch.o : harch.c harch.h
     1205}}}
     1206  [[Image(htdocs:img/make1.png, width=500, nolink)]]
     1207'''''''''''''''
     1208}}}
     12091. Dans quel fichier se trouvent les codes dépendant du MIPS ?
     1210{{{#!protected ------------------------------------------------------------------------------------
     1211'''''''''''''''
     1212- Ils sont dans le fichier `hcpu.S`
     1213'''''''''''''''
     1214}}}
     1215
     1216
     1217**Exercices
     1218
     1219
     1220* Le numéro du processeur est dans les 12 bits de poids faible du registre $15 (`c0_cpuid`) du coprocesseur système (à côté des registres `c0_epc`, `c0_sr`, etc.). Ajoutez la fonction `int cpuid(void)` qui lit le registre `c0_cpuid` et qui rend un entier contenant juste les 12 bits de poids faible.\\Vous pouvez vous inspirez fortement de la fonction `int clock(void)`. Comme il n'y a qu'un seul processeur dans cette architecture, `cpuid` rend toujours `0`.\\Ecrivez un programme de test (vous devrez modifier les fichiers `hcpu.h`, `hcpu.S` et `kinit.c`)
     1221
     1222{{{#!protected
     1223**hcpu.S**
     1224{{{#!asm
     1225.globl cpuid
     1226cpuid:
     1227    mfc0    $2, $15
     1228    andi    $2, $2, 0xFFF
     1229    jr      $31
     1230}}}
     1231**hcpu.h**
     1232{{{#!c
     1233/**
     1234 * \brief     cpu identifier
     1235 * \return    a number
     1236 */
     1237extern unsigned cpuid (void);
     1238}}}
     1239}}}
     1240
     1241
     1242
     1243
     1244== B5. Programme utilisateur utilisé en mode user mais sans libc
     1245
     1246
     1247
     1248**Objectifs de l'étape**
     1249
     1250Nous allons désormais avoir deux exécutables: le noyau et l'application. Dans cette étape, nous allons voir comment le noyau fait pour appeler l'application, alors même que celle-ci n'est pas compilée en même temps que le noyau. Nous allons passer du noyau à l'application à la fin de la fonction `kinit()`.
     1251
     1252Nous avons deux exécutables à compiler et donc deux `Makefile`s de compilation. Nous avons aussi un `Makefile` qui invoque récursivement les `Makefile`s de compilation.
     1253
     1254Nous allons donc entrer dans l'application depuis la fonction `kinit()`. Le programme utilisateur doit absolument s'exécuter en mode user et il doit passer par des appels système pour accéder aux services du noyau. Les services, ici, sont limités (l'accès au TTY, exit et clock), il n'empêche que pour gérer ces appels, il faut l'analyseur des causes d'appels à l'entrée du noyau et un gestionnaire de `syscall`. Il faut aussi le gestionnaire d'exceptions, parce que s'il y a une erreur de programmation, le noyau doit afficher quelque chose pour aider le programmeur.
     1255
     1256Le passage de l'application au noyau par le biais de l'instruction `syscall` impose que les numéros de services soient identiques pour le noyau et pour l'application. Ces numéros de service (comme `SYSCALL_TTY_WRITE`, `SYSCALL_EXIT` sont définis dans le fichier `syscall.h` communs au noyau et à l'application. Ce fichier est mis dans un répertoire à part nommé `common`. Il n'y a qu'un seul fichier ici, mais dans un système plus élaboré, il y en a d'autres.
     1257
     1258
     1259**Fichiers**
     1260
     1261
     1262{{{#!xml
     12635_syscalls/
     1264├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     1265├── common ────────── répertoire des fichiers commun kernel / user
     1266│   └── syscalls.h  : API la fonction syscall et des codes de syscalls
     1267├── kernel ────────── Répertoire des fichiers composant le kernel
     1268│   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
     1269│   ├── harch.h     : API du code dépendant de l'architecture
     1270│   ├── harch.c     : code dépendant de l'architecture du SoC
     1271│   ├── hcpu.h      : prototype de la fonction clock()
     1272│   ├── hcpu.S      : code dépendant du cpu matériel en assembleur
     1273│   ├── klibc.h     : API de la klibc
     1274│   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
     1275│   ├── kpanic.h    : déclaration du tableau de dump des registres en cas d'exception
     1276│   ├── kpanic.c    : fonction d'affichage des registres avant l'arrêt du programme
     1277│   ├── ksyscalls.c : Vecteurs des syscalls
     1278│   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
     1279│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     1280└── user ──────────── Répertoire des fichiers composant l'application user
     1281    ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : crt0()
     1282    ├── main.c      : fonction principale de l'application
     1283    ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
     1284    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     1285}}}
     1286
     1287
     1288**Questions**
     1289
     1290
     12911. Dans quel fichier se trouve la définition des numéros de services tels que `SYSCALL_EXIT` ?
     1292{{{#!protected ------------------------------------------------------------------------------------
     1293'''''''''''''''
     1294- Ils sont dans le fichier `common/syscall.h`.
     1295'''''''''''''''
     1296}}}
     12971. Dans quel fichier se trouve le vecteur de syscall, c'est-à-dire le tableau `syscall_vector[]` contenant les pointeurs sur les fonctions qui réalisent les services correspondants aux syscall ?
     1298{{{#!protected ------------------------------------------------------------------------------------
     1299'''''''''''''''
     1300- Il est dans le fichier `kernel/ksyscall.c`.
     1301'''''''''''''''
     1302}}}
     13031. Dans quel fichier se trouve le gestionnaire de syscalls ?
     1304{{{#!protected ------------------------------------------------------------------------------------
     1305'''''''''''''''
     1306- Il est dans le fichier `kernel/hcpu.S`.
     1307'''''''''''''''
     1308}}}
     1309
     1310
     1311**Exercice**
     1312
     1313
     1314- Vous allez ajouter un appel système nommé `SYSCALL_CPUID` qui rend le numéro du processeur. Nous allons lui attribuer le numéro 4 (notez que ces numéros de services n'ont rien à voir avec les numéros utilisés pour le simulateur MARS). Pour ajouter un appel système, vous devez modifier les fichiers : `common/syscalls.h`, `kernel/ksyscall.c`, `kernel/hcpu.S` et `kernel/hcpu.h`.cpuid(void)`.
     1315
     1316
     1317
     1318== B6.  Ajout de la librairie C pour l'utilisateur
     1319
     1320
     1321
     1322**Objectifs de l'étape**
     1323
     1324
     1325L'application utilisateur n'est pas censée utiliser directement les appels système. Elle utilise une librairie de fonctions standards (la `libc` POSIX, mais pas seulement) et ce sont ces fonctions qui réalisent les appels système. Toutes les fonctions de la `libc` n'utilisent pas les appels système. Par exemple, les fonctions `int rand(void)` ou `int strlen(char *)` (rendent, respectivement, un nombre pseudoaléatoire et la longueur d'une chaîne de caractères) n'ont pas besoin du noyau. Les librairies font partie du système d'exploitation mais elles ne sont pas dans le noyau.
     1326
     1327 ''Le terme « librairie » vient de l'anglais « library » qui signifie bibliothèque. On utilise souvent le mot librairie même si le sens en français n'est pas le même que celui en anglais. Disons que, dans notre contexte, les deux mots sont synonymes.''
     1328
     1329Normalement, les librairies système sont des « vraies » librairies au sens `gcc` du terme. C'est-à-dire des archives de fichiers objet (`.o`). Ici, nous allons simplifier et ne pas créer une ''vraie'' librairie, mais seulement un fichier objet `libc.o` contenant toutes les fonctions. Ce fichier objets doit être lié avec le code de l'application.
     1330
     1331L'exécutable de l'application utilisateur est donc composé de deux parties : d'un côté, le code de l'application et, de l'autre, le code de la librairie `libc` (+ `crt0`). Nous allons répartir le code dans deux répertoires `uapp` pour les fichiers de l'application et `ulib` pour les fichiers qui ne sont pas l'application, c'est-à-dire la `libc`, le fichier `crt0.c` mais aussi le fichier ldscript `user.ld`.
     1332
     1333 
     1334**Fichiers**
     1335
     1336
     1337{{{#!xml
     13386_libc/
     1339├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     1340├── common ────────── répertoire des fichiers commun kernel / user
     1341│   └── syscalls.h  : API la fonction syscall et des codes de syscalls
     1342├── kernel ────────── Répertoire des fichiers composant le kernel
     1343│   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
     1344│   ├── harch.h     : API du code dépendant de l'architecture
     1345│   ├── harch.c     : code dépendant de l'architecture du SoC
     1346│   ├── hcpu.h      : prototype de la fonction clock()
     1347│   ├── hcpu.S      : code dépendant du cpu matériel en assembleur
     1348│   ├── klibc.h     : API de la klibc
     1349│   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
     1350│   ├── kpanic.h    : déclaration du tableau de dump des registres en cas d'exception
     1351│   ├── kpanic.c    : fonction d'affichage des registres avant l'arrêt du programme
     1352│   ├── ksyscalls.c : Vecteurs des syscalls
     1353│   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
     1354│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     1355├── uapp ──────────── Répertoire des fichiers de l'application user seule
     1356│   ├── main.c      : fonction principale de l'application
     1357│   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     1358└── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
     1359    ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : crt0()
     1360    ├── libc.h      : API pseudo-POSIX de la bibliothèque C
     1361    ├── libc.c      : code source de la libc
     1362    ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
     1363    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     1364}}}
     1365
     1366**Questions**
     1367
     13681. Pour ce petit système, dans quel fichier sont placés tous les prototypes des fonctions de la libc? Est-ce ainsi pour POSIX sur LINUX?
     1369{{{#!protected ------------------------------------------------------------------------------------
     1370'''''''''''''''
     1371- Ils sont tous dans le fichier `libc.h`.
     1372- Non, pour POSIX, les prototypes de fonctions de la libc sont répartis dans plusieurs fichiers suivant leur rôle. Il y `stdio.h`, `string.h`, `stdlib.h`, etc. Nous n'avons pas voulu ajouter cette complexité.
     1373'''''''''''''''
     1374}}}
     1375
     1376
     1377**Exercice**
     1378
     1379
     1380- Vous allez juste ajouter la fonction `int cpuid()` dans la librairie `libc`.