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


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

--

Legend:

Unmodified
Added
Removed
Modified
  • Archi-1-TD10

    v1 v1  
     1**
     2[ __[wiki:WikiStart Start]__ ]
     3[ __[wiki:Howto-TP Config]__ ]
     4[ __[htdocs:cours/doc_MIPS32.pdf MIPS User]__ ]
     5[ __[wiki:Doc-MIPS-Archi-Asm-kernel MIPS Kernel]__ ]
     6
     7[ __[htdocs:cours/AS5-9-4p.pdf Cours 9]__ ]
     8[ __[htdocs:cours/AS5-10-4p.pdf Cours 10]__ ]
     9[ __[htdocs:cours/AS5-11-4p.pdf Cours 11]__ ]
     10
     11[ __[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME9 TME 9]__ ]
     12[ ''TME 10'' ]
     13[ __[https://www-soc.lip6.fr/trac/archi-l3s5/wiki/AS5-TME11 TME 11]__ ]
     14**[[PageOutline]]**
     15Codes (tgz) →
     16[ __[htdocs:files/kO6bin.tgz gcc & simulateur]__ ]
     17[ __[htdocs:files/tp1.tgz TME 9]__ ]
     18[ __[htdocs:files/tp2.tgz TME 10]__ ]
     19[ __[htdocs:files/tp3.tgz TME 11]__ ]
     20**
     21{{{#!html
     22<h1> <font size="+2"> Application simple en mode utilisateur
     23}}}
     24
     25
     26
     27Cette page décrit la séance complète : partie TD et partie TP. Elle commence par la partie TD avec des questions ou des exercices à faire sur papier, réparties dans 4 sections. Certaines questions de sections différentes sont semblables, c'est normal, cela vous permet de réviser. Puis, dans la partie TP, il y a des questions sur le code avec quelques exercices de codage simples à écrire et à tester sur le prototype. La partie TP est découpée en 4 étapes. Pour chaque étape, nous donnons (1) une brève description avec une liste des objectifs principaux de l'étape, (2) une liste des fichiers avec un bref commentaire sur chaque fichier, (3) une liste de questions simples dont les réponses sont dans le code, le cours ou le TD et enfin (4) un petit exercice de codage.
     28
     29**IMPORTANT\\Avant de faire cette séance, vous devez avoir lu les documents suivants** :
     30* [wiki:AS5-TME9 Séance de TME sur le démarrage du prototype] : ''obligatoire''
     31* [htdocs:cours/AS5-10-4p.pdf Cours sur l'exécution d'une application en mode user] : ''obligatoire''
     32* [htdocs:cours/doc_MIPS32.pdf Document sur l'assembleur du MIPS et la convention d'appel des fonctions] : ''recommandé, mais déjà lu''
     33* [wiki:Doc-MIPS-Archi-Asm-kernel Documentation sur le mode kernel du MIPS32] : ''obligatoire''
     34
     35
     36
     37**Récupération du code du TP**
     38
     39
     40
     41* Téléchargez **[htdocs:files/tp2.tgz l'archive code du tp2]** et placez là dans le répertoire `$HOME/kO6` 
     42* Ouvrez un `terminal`
     43* Allez dans le répertoire `kO6` : **`cd ~/kO6`**
     44* Décompressez l'archive du tp2 : **`tar xvzf tp2.tgz`**
     45* Exécutez la commande : **`cd ; tree -L 1 kO6/tp2`**.\\
     46  Vous devriez obtenir ceci :
     47{{{#!bash
     48kO6/tp2
     49├── 1_klibc
     50├── 2_appk
     51├── 3_syscalls
     52├── 4_libc
     53└── Makefile
     54}}}
     55
     56
     57**Objectif de la séance**
     58
     59
     60Cette séance illustre le [htdocs:cours/AS5-10-4p.pdf cours2].
     61Les applications de l'utilisateur s'exécutent en mode user. Dans la séance précédente, nous avons vu que les registres de commande des contrôleurs de périphériques sont placés dans l'espace d'adressage du processeur. Les adresses de ces registres ont été placées dans la partie de l'espace d'adressage interdite en mode user. Ainsi, une application n'a pas un accès direct aux périphériques, elle doit utiliser des appels système (avec l'instruction `syscall`) pour demander au noyau du système d'exploitation de faire l'accès. C'est ce que nous allons voir.
     62
     63Le code est désormais découpé en 4 étapes :
     64- **`1_klibc`**\\⟶ Le code de boot et `kinit()` avec une librairie de fonctions standard pour le noyau;
     65- **`2_appk`**\\⟶ La fonction d'initialisation `kinit()` appelle une application mais le noyau n'a pas encore le gestionnaire des appels systèmes;
     66- **`3_syscalls`**\\⟶ Ajout du gestionnaire des appels système et une application **sans** la librairie de fonctions standards utilisateur (libc);
     67- **`4_libc`**\\⟶ Ajout de la libc (rudimentaire) et d'une application.
     68
     69
     70
     71
     72==
     73= A. Travaux dirigés
     74
     75
     76
     77== A1. Les modes d'exécution du MIPS
     78
     79
     80Dans 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.
     81
     82
     83**Questions**
     84
     85
     861. 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'').
     87{{{#!protected ------------------------------------------------------------------------------------
     88''
     89Cours 10 / slides 6 et 7
     90- Il y a le mode kernel et le mode user.
     91- Le mode kernel est utilisé par le noyau alors que le mode user est utilisé par l'application
     92- Le mode kernel permet d'accéder à tout l'espace d'adressage et donc aux périphériques dont les registres sont ''mappés'' à des adresses accessibles uniquement lorsque le processeur est en mode kernel.
     93''
     94}}}
     951. Commencez par rappeler ce qu'est l'espace d'adressage du MIPS et dîtes ce que signifie «une adresse X est mappée dans l'espace d'adressage».\\Dîtes si une adresse `X` mappée dans l'espace d'adressage est toujours accessible (en lecture ou en écriture) quelque soit le mode d'exécution du MIPS.
     96{{{#!protected ------------------------------------------------------------------------------------
     97''
     98Cours 10 / slide 7
     99- L'espace d'adressage du MIPS, c'est l'ensemble des adresses que peut produire le MIPS, il y a 2^32^ adresses d'octets.
     100- On dit qu'une adresse `X` est mappée dans l'espace d'adressage, si cette adresse 'X' est bien dans un segment d'adresses utilisables de l'espace d'adressage`. Autrement dit, le MIPS peut faire des lectures et des écritures à cette adresse, ou encore qu'il y a bien une case mémoire pour cette adresse `X`.
     101- 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.
     102''
     103}}}
     1041. 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 (dans le 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?
     105{{{#!protected ------------------------------------------------------------------------------------
     106''
     107Cours 10 / slides 7, 8 et 9
     108- Les registres système sont numérotés de $0 à $31, comme les registres GPR, ce qui peut induire une certaine confusion, parce qu'avec cette syntaxe, si on demande que trouve-t-on dans le registres `$14`? Si on ne précise pas qu'il s'agit du registre `$14` du coprocesseur 0, alors on ne peut pas répondre. C'est pour cette raison qu'il est préférable d'utiliser leur nom (`EPC` ou `c0_epc` pour `$14` par exemple ou alors `c0_$14`)
     109- Nous avons vu 6
     110   || `c0_sr`     || `$12` || contient essentiellement le mode d'exécution du MIPS et le bit d'autorisation des interruptions
     111   || `c0_cause`  || `$13` || contient la cause d'appel du noyau
     112   || `c0_epc`    || `$14` || contient l'adresse de l'instruction ayant provoqué l'appel du noyau ou l'adresse de l'instruction suivante
     113   || `c0_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)
     114   || `c0_count`  || `$9 ` || contient le nombre de cycles depuis le démarrage du MIPS
     115   || `c0_procid` || `$15` || contient le numéro du processeur (utile pour les architectures multicores)
     116- non, il n'est pas possible de faire des calculs sur ces registres.
     117- On peut juste les lire et les écrire en utilisant les instructions `mtc0` et `mfc0`
     118''
     119}}}
     1201. 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).
     121{{{#!protected ------------------------------------------------------------------------------------
     122''
     123Cours 10 / slides 10 et 11
     124 || 0|| IE  ||Interrupt Enable||0 → interruptions masquées\\1 → interruptions autorisées si ERL et EXL sont tous les deux à 0
     125 || 1|| EXL ||EXception Level ||1 → MIPS en mode exception à l'entrée dans le kernel\\le MIPS est en mode kernel, interruptions masquées
     126 || 2|| ERL ||ERror Level     ||1 → au démarrage du MIPS et certaines erreurs de la mémoire\\le MIPS est en mode kernel, interruptions masquées
     127 || 4|| UM  ||User Mode       ||0 → MIPS en mode kernel\\1 → MIPS en mode user si ERL et EXL sont tous les deux à 0
     128''
     129}}}
     1301. 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
     131{{{#!protected ------------------------------------------------------------------------------------
     132''
     133Cours 10 / slide 12
     134- 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.
     135- Les valeurs les plus importantes sont 0 et 8 (interruption et syscall). Les autres valeurs sont des exceptions, c'est-à-dire des fautes faites par le programme.
     136
     137  ||0|| 0000,,b,, || interruption || un contrôleur de périphérique à lever un signal IRQ
     138  ||4|| 0100,,b,, || ADEL         || lecture non-alignée (p. ex. `lw` a une adresse impaire)
     139  ||8|| 1000,,b,, || syscall      || exécution de l'instruction `syscall`
     140''
     141}}}
     1421. 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, dans le cas d'une exception, ce doit être l'adresse de l'instruction qui provoque une exception qui doit être stockée dans `C0_EPC`?
     143{{{#!protected ------------------------------------------------------------------------------------
     144''
     145Cours 10 / slide 13
     146- 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 le registre EPC contient l'adresse de l'instruction fautive. Le gestionnaire pourra lire l'instruction et éventuellement corriger le problème.
     147- 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.
     148''
     149}}}
     1501. 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?
     151{{{#!protected ------------------------------------------------------------------------------------
     152''
     153Cours 10 / slide 9
     154- Les trois instructions sont
     155
     156  || `mtc0 $GPR, $C0` || `M`ove `T`o `C`oprocessor `0`   || `$GPR` → COPRO_0(`$C0`)
     157  || `mfc0 $GPR, $C0` || `M`ove `F`rom `C`oprocessor `0` || `$GPR` ←  COPRO_0(`$C0`)
     158  || `eret`           || `E`xpection `RET`urn            || `PC`  ←  `EPC` ; `c0_sr.EXL`  ←  `0`
     159
     160  Attention à l'ordre des registres dans les instructions. L'ordre est toujours le même, c'est d'abord le registre $GPR puis le registre $C0, le sens de l'échange est défini par l'opcode de l'instruction (move `TO` ou move `FROM` coprocessor 0).
     161- 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.
     162''
     163}}}
     1641. Quelle est l'adresse d'entrée dans le noyau?
     165{{{#!protected ------------------------------------------------------------------------------------
     166''
     167Cours 10 / slide 13
     168- C'est `0x80000180`. Il n'y a qu'une adresse pour toutes les causes `syscall`, exception et interruption.
     169- (slides 16 et 17) Il y a aussi l'adresse de la fonction `kinit()` qui est la fonction appelée par le code de boot (à l'adresse `0xBFC00000`) pour entrer dans le noyau.
     170''
     171}}}
     1721. Que se passe-t-il quand le MIPS entre dans le noyau, lors de l'exécution de l'instruction `syscall`?
     173{{{#!protected ------------------------------------------------------------------------------------
     174''
     175Cours 10 / slide 13
     176- L'instruction `syscall` induit beaucoup d'opérations élémentaires dans le MIPS:
     177  - `EPC` ← `PC` (adresse de l'instruction `syscall`)
     178  - `c0_SR.EXL` ← `1`  (ainsi les bits `c0_SR.UM` et `c0_SR.IE` ne sont plus utilisés)
     179  - `c0_cause.XCODE` ← `8`
     180  - `PC` ← `0x80000180`
     181''
     182}}}
     1831. 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.
     184{{{#!protected ------------------------------------------------------------------------------------
     185''
     186Cours 10 / slide 13\\
     187- C'est l'instruction `eret` qui permet de sortir du noyau. C'est la seule instruction permettant de sortir du noyau.
     188  - `PC` ← `EPC`
     189  - `c0_SR.EXL` ← `0` (ainsi les bits `c0_SR.UM` et `c0_SR.IE` sont à nouveau utilisés)
     190''
     191}}}
     192
     193
     194
     195== A2. Langage C pour la programmation système
     196
     197
     198
     199La programmation en C, vous connaissez, mais quand on programme pour le noyau, c'est un peu différent.
     200Il y a des éléments de syntaxe ou des besoins spécifiques. Pour répondre aux questions, vous devez avoir lu les transparents 33 à 53 du cours 10, dans lesquels une séquence complète de code (du boot à exit) est détaillée.
     201
     202
     203**Questions**
     204
     205
     2061. En assembleur, vous utilisez les sections prédéfinies `.data` et `.text` pour placer respectivement les data et le code, mais 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 la directive `__attribute__((section("section-name")))`. La directive du C `__attribute__` permet de demander certains comportements au compilateur. Ici, c'est la création d'une section, mais il y a beaucoup d'attributs possibles (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]. Comment créer la section `.start` en C ?
     207{{{#!protected ------------------------------------------------------------------------------------
     208''
     209Cours 10 / slide 38
     210- `__attribute__ ((section (".start")))`\\La syntaxe est un peu curieuse avec les doubles underscore et les doubles parenthèses.
     211''
     212}}}
     2131. 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 [https://www.wikiwand.com/fr/Segment_BSS `.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 retiré les commentaires mais ils sont dans les fichiers).\\Expliquez ce que font les lignes 11, 12 et 15.
     214{{{#!java
     215  1 SECTIONS
     216  2 {
     217  3     .boot : {
     218  4         *(.boot)           
     219  5     } > boot_region
     220  6     .ktext : {
     221  7         *(.text*)           
     222  8     } > ktext_region
     223  9     .kdata : {
     224 10         *(.*data*)         
     225 11         . = ALIGN(4);       
     226 12         __bss_origin = .;   
     227 13         *(.*bss*)           
     228 14         . = ALIGN(4);       
     229 15         __bss_end = .;     
     230 16     } > kdata_region
     231 17 }
     232}}}
     233{{{#!protected ------------------------------------------------------------------------------------
     234''
     235Cours 10 / slide 28
     236- La ligne 11 contient `. = ALIGN(4)`, c'est équivalent à la directive `.align 4` de l'assembleur.
     237  Cela permet de déplacer le pointeur de remplissage de la section de sortie courante (c'est-à-dire ici `.kdata`) sur une
     238  frontière de 2^4^ octets (une adresse multiple de 16). Cette contrainte est liée aux caches que nous ne verrons pas ici.
     239- La ligne 12 permet de créer la variable de ldscript `__bss_origin` et de l'initialiser à l'adresse courante,
     240  ce sera donc l'adresse de début de la zone `bss`.
     241- La ligne 15 permet de créer la variable `__bss_end` qui sera l'adresse de fin de la zone `bss`
     242  (en fait c'est la première adresse qui suit juste `bss`.
     243''
     244}}}
     2451. 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`:
     246{{{#!c
     247__tty_regs_map   = 0xd0200000 ; /* tty's registers map, described in devices.h */
     248}}}
     249   Dans le fichier `harch.c` :
     250{{{#!c
     251 12 struct tty_s {
     252 13     int write;          // tty's output address
     253 14     int status;         // tty's status address something to read if not null)
     254 15     int read;           // tty's input address
     255 16     int unused;         // unused address
     256 17 };
     257 18
     258 19 extern volatile struct tty_s __tty_regs_map[NTTYS];
     259}}}
     260  À 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` ?
     261{{{#!protected ------------------------------------------------------------------------------------
     262''
     263Cours 10 / slide 53
     264- `extern` : informe le compilateur que la variable définie existe ailleurs. Grâce à son type, le compilateur sait s'en servir.
     265- `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.
     266- `__tty_regs_map` est un tableau à 2 cases (puisque `NTTYS`=`2`).\\Chaque case est une structure de 4 entiers, donc `0x10` octets (16 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`
     267''
     268}}}
     2691. 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 (kentry) 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:
     270 - trouver l'adresse de la fonction C qu'il doit appeler pour exécuter le service demandé;
     271 - placer cette adresse dans un registre, nous utilisons le registre `$2`;
     272 - exécuter l'instruction `jal` (ici, `jal $2`) pour appeler la fonction.
     273
     274 Que doivent contenir les registres `$4` à `$7` et comment doit-être la pile et le pointeur de pile?
     275{{{#!protected ------------------------------------------------------------------------------------
     276''
     277Cours 10 / slide 42
     278- C'est un appel de fonction, il faut donc respecter la convention d'appel des fonctions
     279  - Les registres `$4`à `$7` contiennent les arguments de la fonction
     280  - Le pointeur de pile doit pointer sur la case réservée pour le premier argument et les cases suivantes sont réservées arguments suivants.
     281  - Ce n'est pas rappelé ici, mais, **pour l'application user**, il y a **au plus** 4 arguments (entier ou pointeur) pour tous les syscalls. Le gestionnaire de syscall ajoute un cinquième argument avec le numéro de service qu'il a reçu dans `$2`. En conséquence, le pointeur de pile pointe au début d'une zone vide de 4 entiers suivi d'un 5e avec le numéro du service.
     282  - L'intérêt d'ajouter le numéro de service comme cinquième argument, c'est qu'il est possible de faire une fonction unique qui gère un ensemble de syscalls avec un `switch/case` sur le numéro de service. On ne le fait pas dans cette version.
     283''
     284}}}
     2855. 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 `syscall()` est écrite. Cette fonction utilise l'instruction `syscall`.\\Deux exemples d'usage de la fonction `syscall()` pris dans le fichier `tp2/4_libc/ulib/libc.c`
     286{{{#!c
     287  1 int fprintf (int tty, char *fmt, ...)
     288  2 {
     289  3     int res;
     290  4     char buffer[PRINTF_MAX];
     291  5     va_list ap;
     292  6     va_start (ap, fmt);
     293  7     res = vsnprintf(buffer, sizeof(buffer), fmt, ap);
     294  8     res = syscall (tty, (int)buffer, 0, 0, SYSCALL_TTY_PUTS);
     295  9     va_end(ap);
     296 10     return res;
     297 11 }
     298 12
     299 13 void exit (int status)
     300 14 {
     301 15     syscall( status, 0, 0, 0, SYSCALL_EXIT);        // never returns
     302 16 }
     303}}}
     304 
     305 Le code de cette fonction est dans le fichier `tp2/4_libc/ulib/crt0.c`
     306{{{#!c
     307  1 //int syscall (int a0, int a1, int a2, int a3, int syscall_code)
     308  2 __asm__ (
     309  3 ".globl syscall     \n"         
     310  4 "syscall:           \n"         
     311  5 "   lw  $2,16($29)  \n"         
     312  6 "   syscall         \n"         
     313  7 "   jr  $31         \n"         
     314  8 );
     315}}}
     316 Combien d'arguments a la fonction `syscall()`?
     317 Comment la fonction `syscall()` reçoit-elle ses arguments ?
     318 A quoi sert la ligne 3 de la fonction `syscall()` et que se passe-t-il si on la retire ?
     319 Expliquer la ligne 5 de la fonction `syscall()`.
     320 Aurait-il été possible de mettre le code de la fonction `syscall()` dans un fichier `.S` ?
     321{{{#!protected ------------------------------------------------------------------------------------
     322''
     323Cours 10 / slide 40
     324- La fonction `syscall()` a 5 a arguments
     325- Elle reçoit ses 4 premiers arguments dans les registres $4 à $7 et le 5e (le numéro de service) dans la pile.
     326- La ligne 3 sert à dire que syscall est une étiquette utilisée dans un autre fichier. `.globl` signifie **glob**al **l**abel. Si on la retire, il y aura un problème lors de l'édition de lien. `syscall()` ne sera pas trouvé par l'éditeur de liens.
     327- Le noyau attend le numéro de service dans `$2`. Or le numéro du service est le 5e argument de la fonction `syscall()`. La ligne 5 permet d'aller le chercher dans la pile.
     328- oui, ce code de la fonction `syscall()` qui fait appel à l'instruction `syscall` aurait pu être mis dans un fichier en assembleur, mais cela aurait demandé d'avoir un fichier de plus, pour une seule fonction. Dans une version plus évoluée du système, il y aura un d'autres fonctions assembleur, alors on créera un fichier assembleur pour les réunir.
     329''
     330}}}
     331
     332
     333== A3. Passage entre les modes kernel et user
     334
     335
     336Le 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.
     337
     338
     339**Questions**
     340
     341
     3421. Comment imposer le placement d'adresse d'une fonction ou d'une variable en mémoire?
     343{{{#!protected ------------------------------------------------------------------------------------
     344''
     345Cours 9 / slide 24  et Cours 10 / slides 64 et 65
     346- 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.
     347- Pour placer une fonction à une place, la méthode que vous avez vu consiste
     348  - à créer une section grâce à la directive `.section` en assembleur ou à la directive `__attribute__((section()))` en C
     349  - puis à positionner la section créée dans la description des `SECTIONS` du ldscript.
     350''
     351}}}
     3521. Regardons comment la fonction `kinit()` appelle la fonction `__start()`, il y a deux fichiers impliqués `kinit.c` et `hcpua.S`, les commentaires ont été rétirés.
     353{{{#!c
     354kinit.c:
     355    void kinit (void)
     356    {
     357        [...]
     358        extern int _start;   
     359        app_load (&_start);   
     360    }
     361
     362hcpua.S:
     363    .globl app_load
     364    app_load:                     
     365        mtc0   $4,      $14       
     366        li     $26,     0x12       
     367        mtc0   $26,     $12         
     368        la     $29,    __data_end   
     369        eret 
     370}}}
     371 Où se trouve la fonction `_start` et comment le kernel connaît-il son adresse ? À quoi sert `.globl app_load `? Quels sont les registres utilisés dans le code de `app_load `? Que savez-vous de l'usage de `$26 `? Quels sont les registres modifiés ? Expliquez pour chacun la valeur affectée. Que fait l'instruction `eret `?
     372{{{#!protected ------------------------------------------------------------------------------------
     373''
     374- La fonction `_start` est au début de la section `.text` (code de l'utilisateur). Le noyau connait cette adresse parce qu'elle est définit dans son fichier `ldscript`.
     375- `.globl app_load` est nécessaire parce que ce label de fonction est défini dans le fichier `hcpua.S` mais il est utilisé dans un autre (`kinit.c`).
     376- Les registres utilisés par `app_load` sont `$4`, `$26`, `$29` du banc GPR et `$12` (`c0_sr`) et `$14` (`c0_epc`) du banc de registres système.
     377- `$26` est un registre temporaire pour le noyau, il peut l'utiliser sans le sauver avant et donc sans le restaurer.
     378- Il y a 4 registres affectés, dans l'ordre :
     379  - Le registre système `$14` nommé `c0_epc`, il reçoit l'adresse `_start`, c'est-à-dire l'adresse de la fonction `_start()`.
     380  - `$26` affecté à `0x12`, c'est un registre temporaire pour le noyau.
     381  - 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`
     382    - UM = 1 et IE = 0, signifie que l'on est normalement en mode `user` avec les interruptions masquées,
     383      **mais** comme `EXL`=`1`, alors on reste en mode `kernel` avec interruptions masquées.
     384  - 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.
     385- 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).
     386''
     387}}}
     3881. Que faire avant l'exécution de la fonction `main()` du point de vue de l'initialisation? Et au retour de la fonction `main()`?
     389{{{#!protected ------------------------------------------------------------------------------------
     390''
     391Cours 10 / slide 38
     392- Comme dans la fonction `kinit()`, il faut explicitement initialiser les variables globales non initialisées dans le programme C.
     393- 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 SYSCALL_EXIT. Cette appel est réalisé au cas où l'application n'aurait pas explicitement exécuté `exit()`. Dans ce cas la valeur rendue par l'application est la valeur de retour de la fonction `main()`.
     394''
     395}}}
     3961. 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?
     397{{{#!protected ------------------------------------------------------------------------------------
     398''
     399Cours 10 / slide 17
     400- Il y en a 3 (si on excepte le signal `reset` qui redémarre tout le système:
     401  1. Les appels système donc l'exécution de l'instruction `syscall`.
     402  1. Les exceptions donc les "erreur" de programmation (division par 0, adressage mémoire incorrect, etc.).
     403  1. Les interruptions qui sont des demandes d'intervention provenant des périphériques.
     404- 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`.
     405''
     406}}}
     4071. `$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. Ligne 16, la directive `.org DEP` (`.org` pour `origine`) permet de placer le pointeur de remplissage de la section courante à `DEP` octets du début de la section, ici `DEP = 0x180`.  Aurait-on pu remplacer le `.org 0x180` par `.space 0x180` ? Expliquer les lignes 25 à 28.\\ \\**`kernel/hcpua.S`**
     408{{{#!c
     409 15 .section    .kentry,"ax"     
     410 16 .org        0x180           
     411 22
     412 23 kentry:                               
     413 24
     414 25     mfc0    $26,    $13                     
     415 26     andi    $26,    $26,    0x3C         
     416 27     li      $27,    0x20                   
     417 28     bne     $26,    $27,    not_syscall     
     418}}}
     419{{{#!protected ------------------------------------------------------------------------------------
     420''
     421Cours 10 / slide 41 (mais il n'y a pas ces détails)
     422- La section `kentry` est placée à l'adresse `0x80000000` or l'entrée du noyau est `0x80000180` (l'entrée du noyau est l'adresse à laquelle le processeur ''saute'' lors de l'exécution `syscall`), il faut donc déplacer le pointeur de remplissage de la section `ktentry` de `0x180`. La directive `.space 0x180` réserve `0x180`, si on met cette directive au tout début de la section, c'est équivalent.
     423- Commentaire du code
     424  - Ligne 25 : `$26` **←**  `c0_cause`\\⟶ donc le registre `$26`GPR réservé au kernel prend la valeur du registre de cause.
     425  - Ligne 26 : `$26` **←**  `$26 & 0b00111100`\\⟶ C'est un masque qui permet de ne conserver que les 4 bits du champ `XCODE`.
     426  - 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`.
     427  - 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.
     428''
     429}}}
     4301. 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/hcpua.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` du code assembleur 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`**
     431{{{#!c
     432  1 #define SYSCALL_EXIT        0
     433  2 #define SYSCALL_TTY_PUTC    1
     434  3 #define SYSCALL_TTY_GETC    2
     435  4 #define SYSCALL_TTY_PUTS    3
     436  5 #define SYSCALL_TTY_GETS    4
     437  6 #define SYSCALL_CLOCK       5
     438  7 #define SYSCALL_NR          32
     439}}}
     440  **`kernel/ksyscalls.c`**
     441{{{#!c
     442void *syscall_vector[] = {
     443    [0 ... SYSCALL_NR - 1] = unknown_syscall,
     444    [SYSCALL_EXIT]      = exit,
     445    [SYSCALL_TTY_PUTC]  = tty_putc,
     446    [SYSCALL_TTY_GETC]  = tty_getc,
     447    [SYSCALL_TTY_PUTS]  = tty_puts,
     448    [SYSCALL_TTY_GETS]  = tty_gets,
     449    [SYSCALL_CLOCK]     = clock,
     450};
     451}}}
     452  **`kernel/hcpua.S`**
     453{{{#!xml
     454 34 ksyscall:
     455 35
     456 36     addiu   $29,    $29,    -8*4           
     457 37     mfc0    $27,    $14                     
     458 38     mfc0    $26,    $12                     
     459 39     addiu   $27,    $27,    4               
     460 40     sw      $31,    7*4($29)               
     461 41     sw      $27,    6*4($29)               
     462 42     sw      $26,    5*4($29)               
     463 43     sw      $2,     4*4($29)               
     464 44     mtc0    $0,     $12                     
     465 45
     466 46     la      $26,    syscall_vector         
     467 47     andi    $2,     $2,     SYSCALL_NR-1   
     468 48     sll     $2,     $2,     2               
     469 49     addu    $2,     $26,    $2             
     470 50     lw      $2,     0($2)                   
     471 51     jalr    $2                             
     472 52
     473 53     lw      $26,    5*4($29)               
     474 54     lw      $27,    6*4($29)               
     475 55     lw      $31,    7*4($29)               
     476 56     mtc0    $26,    $12                     
     477 57     mtc0    $27,    $14                     
     478 58     addiu   $29,    $29,    8*4             
     479 59     eret                       
     480}}}
     481{{{#!protected ------------------------------------------------------------------------------------
     482''
     483Cours 10 / slide 42
     484- État de la pile après l'exécution des lignes 36 à 43
     485{{{#!xml
     486      +----------+
     487      |    $31   |  Nous allons exécuter jal un peu plus et perdre $31, il faut le sauver
     488      +----------+
     489      |  C0_EPC  |  C'est l'adresse de retour du syscall
     490      +----------+
     491      |  C0_SR   |  le registre status est modifié plus loin, il faut le sauver pour le restaurer
     492      +----------+
     493      |    $2    |  C'est le numéro de syscall qui pourra être accédé par la fonction appelée en 5e argument
     494      +----------+
     495      |          |  place réservée pour le 4e argument actuellement dans $7
     496      +----------+
     497      |          |  place réservée pour le 3e argument actuellement dans $6
     498      +----------+
     499      |          |  place réservée pour le 2e argument actuellement dans $5
     500      +----------+
     501$29 → |          |  place réservée pour le 1e argument actuellement dans $4
     502      +----------+
     503}}}
     504- 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.
     505  - ''Notez qu'interdire les interruptions pendant l'exécution des syscall est un choix important. Pour le moment, ce n'est pas un problème 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 figé (plus rien ne bougerait). Nous verrons comment faire au prochain cours.''\\ \\
     506- Commentaire du code lignes 46 à 53
     507  - Ligne 46 : `$26` **←** l'adresse du tableau syscall_vector\\⟶ On s'apprête à y faire un accès indexé par le registre `$2`
     508  - Ligne 47 : `$2`  **←** `$2 & 0x1F`\\⟶ pour éviter de sortir du tableau si l'utilisateur à mis n'importe quoi dans `$2`.\\On ne fait pas un modulo et donc `SYSCALL_NR` doit être une puissance de 2 !
     509  - Ligne 48 : `$2`  **←** `$2 * 4`\\⟶ Les cases du tableau sont des pointeurs et font 4 octets
     510  - Ligne 49 : `$2`  **←** `$26 + $2`\\⟶ `$2` contient désormais l'adresse de la case contenant la fonction correspondante au service n°`$2`
     511  - Ligne 50 : `$2` **←** MEM[`$2`] \\⟶ $2 contient l'adresse de la fonction à appeler
     512  - Ligne 51 :  `jal $2`  \\⟶ appel de la fonction de service\\On rappelle que `$4` à `$7` contiennent les 4 premiers argument, mais qu'il y a de place pour ces arguments dans la pile.\\ \\
     513- 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`.
     514''
     515}}}
     516
     517
     518== A4. Génération du code exécutable
     519
     520
     521Pour simuler le logiciel, il faut produire deux exécutables. Nous utilisons, ici, un Makefile hiérarchique et des règles explicites.
     522Cela sort du cadre de l'architecture, mais vous avez besoin de ce savoir-faire pour comprendre le code, alors allons-y.
     523
     524
     525**Questions**
     526
     527
     5281. Rappelez à quoi sert un Makefile?
     529{{{#!protected ------------------------------------------------------------------------------------
     530''
     531Cours 9 / slide 26
     532- 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.
     533- Le second rôle d'un Makefile est de permettre la reconstruction partielle du fichier **`cible`** lorsque quelques fichiers **`source`** changent (pas tous). Pour ce rôle, le Makefile exprime toutes les étapes de construction de la **`cible`** finale et des **`cibles`** intermédiaires sous forme d'un arbre dont les feuilles sont les fichiers **`sources`**.
     534''
     535}}}
     5361. 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.
     537{{{#!make
     538kernel.x : kernel.ld obj/hcpua.o obj/kinit.o obj/klibc.o obj/harch.o
     539    $(LD) -o $@ -T $^
     540    $(OD) -D $@ > $@.s
     541
     542obj/hcpua.o : hcpua.S hcpu.h
     543    $(CC) -o $@ $(CFLAGS) $<
     544    $(OD) -D $@ > $@.s
     545}}}
     546{{{#!protected ------------------------------------------------------------------------------------
     547''
     548Cours 9 / slides 27 et 56
     549- La `cible` finale est : `kernel.x`
     550- Les `cibles` intermédiaires sont : `kernel.ld`, `obj/hcpua.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`.
     551- La `source` est : `hcpua.S`
     552- Les variables automatiques servent à extraire des noms dans la définition de la dépendance (`cible : dépendances`)
     553  - dans la première règle :
     554    - `$@` = `cible` = `kernel.x`
     555    - `$^` = l'ensemble des dépendances = `kernel.ld`, `obj/hcpua.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`
     556  - dans la seconde règle :
     557    - `$@` = `cible` = `obj/hcpua.o`
     558    - `$<` = la première des dépendances = `hcpua.S`
     559''
     560}}}
     5611. 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`.
     562{{{#!xml
     5634_libc/
     564├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     565├── common ────────── répertoire des fichiers commun kernel / user
     566├── kernel ────────── Répertoire des fichiers composant le kernel
     567│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     568├── uapp ──────────── Répertoire des fichiers de l'application user seule
     569│   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     570└── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
     571    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     572}}}
     573{{{#!protected ------------------------------------------------------------------------------------
     574''
     575Ce n'est pas dit dans le cours, mais la question contient la réponse...
     576{{{#!make
     577compil:
     578    make -C kernel compil
     579    make -C ulib   compil
     580    make -C uapp   compil
     581}}}
     582''
     583}}}
     584
     585
     586{{{#!comment
     587Je retire cette partie, elle est trop hors sujet.
     588
     589== A5. Libc
     590
     591
     592Cette partie ne concerne pas vraiment le noyau, mais il y a peut-être des choses que vous ignorez sur le C, ou certaines opérations, qu'il est nécessaire de connaître pour ce petit système. Cela n'a pas été présenté en cours, alors les questions sont précédées d'une présentation du problème et sa solution.
     593
     594
     595**Questions**
     596
     597
     5981. fonction C à nombre d'arguments variables `fprintf`?
     599{{{#!protected ------------------------------------------------------------------------------------
     600''
     601-
     602''
     603}}}
     6041. génération de nombres pseudoaléatoires `rand`?
     605{{{#!protected ------------------------------------------------------------------------------------
     606''
     607-
     608''
     609}}}
     6101. traduction d'une chaîne de caractère en nombre `atoi`?
     611{{{#!protected ------------------------------------------------------------------------------------
     612''
     613-
     614''
     615}}}
     616}}}
     617
     618----------------------------------------------------------------------------------------------------
     619
     620
     621
     622= B. Travaux pratiques
     623
     624
     625
     626Pour 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 et vous guidez, un peu, pour l'exercice. 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.
     627
     628Le code se trouve dans `kO6/tp2/`, ouvrez un terminal et allez-y. Dans ce répertoire, vous avez 4 sous-répertoires et un Makefile. Le fichier `kO6/tp2/Makefile` permet de faire le ménage en appelant les Makefiles des sous-répertoires avec la cible `clean`.
     629
     630
     631
     632== B1. Ajout d'une bibliothèque de fonctions standards pour le kernel (klibc)
     633
     634
     635
     636**Objectifs de l'étape**
     637
     638Le 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.
     639
     640
     641**Fichiers**
     642
     643
     644{{{#!xml
     6451_klibc/
     646├── kinit.c         : fichier contenant la fonction de démarrage du noyau
     647├── harch.h         : API du code dépendant de l'architecture
     648├── harch.c         : code dépendant de l'architecture du SoC
     649├── hcpu.h          : prototype de la fonction clock()
     650├── hcpua.S         : code dépendant du cpu matériel en assembleur
     651├── kernel.ld       : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     652├── klibc.h         : API de la klibc
     653├── klibc.c         : fonctions standards utilisées par les modules du noyau
     654└── Makefile        : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     655}}}
     656
     657
     658**Questions**
     659
     660
     6611. Ouvrez le fichier Makefile (vous pouvez regarder les dépendances en ouvrant quelques fichiers sources), puis dessiner le graphe de dépendance de `kernel.x` vis-à-vis de ses sources?\\La réponse peut-être visible avec la commande `dot -Tpng Makefile.dot -oMakefile.png` à partir du fichier [htdocs:img/Makefile.dot Makefile.dot] (lien cliquable) en utilisant [https://www.graphviz.org graphviz] ... essayez c'est magique :-)
     662{{{#!protected ------------------------------------------------------------------------------------
     663''
     664{{{#!make
     665kernel.x : kernel.ld obj/hcpua.o obj/kinit.o obj/klibc.o obj/harch.o
     666obj/hcpua.o : hcpua.S hcpu.h
     667obj/kinit.o : kinit.c klibc.h harch.h hcpu.h
     668obj/klibc.o : klibc.c klibc.h harch.h hcpu.h
     669obj/harch.o : harch.c klibc.h harch.h hcpu.h
     670}}}
     671  [[Image(htdocs:img/Makefile.png, width=500, nolink)]]
     672''
     673}}}
     6741. Dans quel fichier se trouvent les codes dépendant du MIPS ?
     675{{{#!protected ------------------------------------------------------------------------------------
     676''
     677- Ils sont dans le fichier `hcpua.S`
     678''
     679}}}
     680
     681
     682**Exercices
     683
     684
     685* 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`, `hcpua.S` et `kinit.c`)
     686
     687{{{#!protected
     688**hcpua.S**
     689{{{#!asm
     690.globl cpuid
     691cpuid:
     692    mfc0    $2, $15
     693    andi    $2, $2, 0xFFF
     694    jr      $31
     695}}}
     696**hcpu.h**
     697{{{#!c
     698/**
     699 * \brief     cpu identifier
     700 * \return    a number
     701 */
     702extern unsigned cpuid (void);
     703}}}
     704}}}
     705
     706
     707== B2. Programme utilisateur mais exécuté en mode kernel
     708
     709
     710**Objectifs de l'étape**
     711
     712Nous 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()`.
     713
     714Nous allons donc entrer dans l'application, en revanche, dans cette étape, nous n'allons pas mettre en place la gestion des syscalls. **C'est-à-dire qu'il ne sera pas possible de revenir dans le noyau depuis l'application**. C'est bien entendu une étape intermédiaire, parce qu'il faut absolument pouvoir invoquer le noyau depuis l'application pour accéder aux périphériques.
     715Pour pouvoir quand même accéder aux registres de périphériques, nous allons **exceptionnellement** exécuter l'application en mode kernel. Ainsi, l'application pourra accéder aux adresses de l'espace d'adressage réservées au mode `kernel`.
     716
     717Nous 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.
     718
     719
     720**Fichiers**
     721
     722{{{#!xml
     7232_appk/
     724├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     725├── kernel ────────── Répertoire des fichiers composant le kernel
     726│   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
     727│   ├── harch.h     : API du code dépendant de l'architecture
     728│   ├── harch.c     : code dépendant de l'architecture du SoC
     729│   ├── hcpu.h      : prototype de la fonction clock()
     730│   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
     731│   ├── klibc.h     : API de la klibc
     732│   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
     733│   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
     734│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     735└── user ──────────── Répertoire des fichiers composant l'application user
     736    ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
     737    ├── main.c      : fonction principale de l'application
     738    ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
     739    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     740}}}
     741
     742**Questions**
     743
     7441. Combien de fichiers de type ldscript avons-nous ?
     745{{{#!protected ------------------------------------------------------------------------------------
     746''
     747- Il en faut deux, un pour le kernel `kernel/kernel.ld` et un pour l'application `user/user.ld`
     748''
     749}}}
     7501. Dans quel fichier se trouve la première fonction de l'application et comment s'appelle-t-elle?
     751{{{#!protected ------------------------------------------------------------------------------------
     752''
     753- Dans le fichier `user/crt0.c`, c'est la fonction `_start()`.
     754''
     755}}}
     7561. Quelle est la fonction du noyau qui appelle cette fonction et dans quel fichier?
     757{{{#!protected ------------------------------------------------------------------------------------
     758''
     759- C'est la fonction `kinit()`, dans le fichier `kernel/kinit.c`.
     760''
     761}}}
     7621. Comment le noyau fait-il pour démarrer l'application en mode `kernel`? (la réponse est dans la fonction de la question précédente).
     763{{{#!protected ------------------------------------------------------------------------------------
     764''
     765- Dans la fonction `kinit()`, on appelle `app_load(&_start)`, c'est une fonction écrite en assembleur, donc forcément dans `hcpua.S`. Dans cette fonction on initialise `c0_epc` avec l'adresse de `_start()`, on initialise le bit `UM` du registre status `c0_sr` avec `0`. Ainsi, après l'exécution de `eret`, nous serons en mode `kernel`.
     766{{{#!c
     767.globl app_load // ----------------- void app_load (void * fun) called by kinit()
     768app_load:                         // call when we exit kinit() function to go to user code
     769
     770    mtc0   $4,      $14           // put _start address in c0_EPC
     771    li     $26,     2             // define next status reg. value
     772    mtc0   $26,     $12           // UM <- 0, IE <- 0, EXL <- 1
     773    eret                          // j EPC and EXL <- 0
     774}}}
     775''
     776}}}
     777
     778**Exercice**
     779
     780- Vous n'allez pas faire grand-chose pour cette étape parce qu'elle n'est pas très utile du fait de l'impossibilité de revenir dans le noyau après l'entrée dans l'application. Affichez juste un second message depuis la fonction `main()`
     781
     782
     783
     784== B3. Programme utilisateur utilisé en mode user mais sans libc
     785
     786
     787
     788**Objectifs de l'étape**
     789
     790Le 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.
     791
     792Le 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_PUTS`, `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.
     793
     794
     795**Fichiers**
     796
     797
     798{{{#!xml
     7993_syscalls/
     800├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     801├── common ────────── répertoire des fichiers commun kernel / user
     802│   └── syscalls.h  : API la fonction syscall et des codes de syscalls
     803├── kernel ────────── Répertoire des fichiers composant le kernel
     804│   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
     805│   ├── harch.h     : API du code dépendant de l'architecture
     806│   ├── harch.c     : code dépendant de l'architecture du SoC
     807│   ├── hcpu.h      : prototype de la fonction clock()
     808│   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
     809│   ├── hcpuc.c     : code dépendant du cpu matériel en c
     810│   ├── klibc.h     : API de la klibc
     811│   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
     812│   ├── kpanic.h    : déclaration du tableau de dump des registres en cas d'exception
     813│   ├── kpanic.c    : fonction d'affichage des registres avant l'arrêt du programme
     814│   ├── ksyscalls.c : Vecteurs des syscalls
     815│   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
     816│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     817└── user ──────────── Répertoire des fichiers composant l'application user
     818    ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
     819    ├── main.c      : fonction principale de l'application
     820    ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
     821    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     822}}}
     823
     824
     825**Questions**
     826
     827
     8281. Dans quel fichier se trouve la définition des numéros de services tels que `SYSCALL_EXIT` ? (''Ces numéros sont communs au noyau et à l'application'')
     829{{{#!protected ------------------------------------------------------------------------------------
     830''
     831- Ils sont dans le fichier `common/syscall.h`. C'est dit dans l'énoncé !
     832''
     833}}}
     8341. 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 ?
     835{{{#!protected ------------------------------------------------------------------------------------
     836''
     837- Il est dans le fichier `kernel/ksyscall.c`.
     838''
     839}}}
     8401. Dans quel fichier se trouve le gestionnaire de syscalls ? (''c'est de l'assembleur'')
     841{{{#!protected ------------------------------------------------------------------------------------
     842''
     843- Il est dans le fichier `kernel/hcpua.S`.
     844''
     845}}}
     846
     847
     848**Exercice**
     849
     850
     851- Vous allez ajouter un appel système nommé `SYSCALL_CPUID` qui rend le numéro du processeur. Nous allons lui attribuer le numéro 6 (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/hcpua.S` et `kernel/hcpu.h`.cpuid(void)`.
     852
     853
     854
     855== B4.  Ajout de la librairie C pour l'utilisateur
     856
     857
     858
     859**Objectifs de l'étape**
     860
     861
     862L'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 pseudo alé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.
     863
     864''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.''
     865
     866Normalement, 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.
     867
     868L'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`.
     869
     870 
     871**Fichiers**
     872
     873
     874{{{#!xml
     8754_libc/
     876├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     877├── common ────────── répertoire des fichiers commun kernel / user
     878│   └── syscalls.h  : API la fonction syscall et des codes de syscalls
     879├── kernel ────────── Répertoire des fichiers composant le kernel
     880│   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
     881│   ├── harch.h     : API du code dépendant de l'architecture
     882│   ├── harch.c     : code dépendant de l'architecture du SoC
     883│   ├── hcpu.h      : prototype de la fonction clock()
     884│   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
     885│   ├── hcpuc.c     : code dépendant du cpu matériel en c
     886│   ├── klibc.h     : API de la klibc
     887│   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
     888│   ├── kpanic.h    : déclaration du tableau de dump des registres en cas d'exception
     889│   ├── kpanic.c    : fonction d'affichage des registres avant l'arrêt du programme
     890│   ├── ksyscalls.c : Vecteurs des syscalls
     891│   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
     892│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     893├── uapp ──────────── Répertoire des fichiers de l'application user seule
     894│   ├── main.c      : fonction principale de l'application
     895│   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     896└── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
     897    ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
     898    ├── libc.h      : API pseudo-POSIX de la bibliothèque C
     899    ├── libc.c      : code source de la libc
     900    ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
     901    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     902}}}
     903
     904**Questions**
     905
     9061. 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?
     907{{{#!protected ------------------------------------------------------------------------------------
     908''
     909- Ils sont tous dans le fichier `libc.h`.
     910- Non, pour POSIX, les prototypes de fonctions de la libc sont répartis dans plusieurs fichiers suivant leur rôle. Il y a `stdio.h`, `string.h`, `stdlib.h`, etc. Nous n'avons pas voulu ajouter cette complexité.
     911''
     912}}}
     913
     914
     915**Exercice**
     916
     917
     918- Vous allez juste ajouter la fonction `int cpuid()` dans la librairie `libc`.
     919- Au premier TP, vous deviez créer un petit jeu 'guess', vous pouvez en faire une application utilisateur, en utilisant cette fois les fonctions de la `libc`.