Changes between Version 119 and Version 120 of Archi-1-TP10


Ignore:
Timestamp:
Jan 15, 2022, 4:23:09 PM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Archi-1-TP10

    v119 v120  
    6363
    6464
    65 
    66 
    67 ==
    68 = A. Travaux dirigés
    69 
    70 
    71 
    72 == A1. Les modes d'exécution du MIPS
    73 
    74 
    75 Dans 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.
    76 
    77 
    78 **Questions**
    79 
    80 
    81 1. 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'').
    82 {{{#!protected ------------------------------------------------------------------------------------
    83 ''
    84 Cours 10 / slides 6 et 7
    85 - Il y a le mode kernel et le mode user.
    86 - Le mode kernel est utilisé par le noyau alors que le mode user est utilisé par l'application
    87 - 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.
    88 ''
    89 }}}
    90 1. 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.
    91 {{{#!protected ------------------------------------------------------------------------------------
    92 ''
    93 Cours 10 / slide 7
    94 - 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.
    95 - 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`.
    96 - 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.
    97 ''
    98 }}}
    99 1. 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?
    100 {{{#!protected ------------------------------------------------------------------------------------
    101 ''
    102 Cours 10 / slides 7, 8 et 9
    103 - 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`)
    104 - Nous avons vu 6
    105    || `c0_sr`     || `$12` || contient essentiellement le mode d'exécution du MIPS et le bit d'autorisation des interruptions
    106    || `c0_cause`  || `$13` || contient la cause d'appel du noyau
    107    || `c0_epc`    || `$14` || contient l'adresse de l'instruction ayant provoqué l'appel du noyau ou l'adresse de l'instruction suivante
    108    || `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)
    109    || `c0_count`  || `$9 ` || contient le nombre de cycles depuis le démarrage du MIPS
    110    || `c0_procid` || `$15` || contient le numéro du processeur (utile pour les architectures multicores)
    111 - non, il n'est pas possible de faire des calculs sur ces registres.
    112 - On peut juste les lire et les écrire en utilisant les instructions `mtc0` et `mfc0`
    113 ''
    114 }}}
    115 1. 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).
    116 {{{#!protected ------------------------------------------------------------------------------------
    117 ''
    118 Cours 10 / slides 10 et 11
    119  || 0|| IE  ||Interrupt Enable||0 → interruptions masquées\\1 → interruptions autorisées si ERL et EXL sont tous les deux à 0
    120  || 1|| EXL ||EXception Level ||1 → MIPS en mode exception à l'entrée dans le kernel\\le MIPS est en mode kernel, interruptions masquées
    121  || 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
    122  || 4|| UM  ||User Mode       ||0 → MIPS en mode kernel\\1 → MIPS en mode user si ERL et EXL sont tous les deux à 0
    123 ''
    124 }}}
    125 1. 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
    126 {{{#!protected ------------------------------------------------------------------------------------
    127 ''
    128 Cours 10 / slide 12
    129 - 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.
    130 - 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.
    131 
    132   ||0|| 0000,,b,, || interruption || un contrôleur de périphérique à lever un signal IRQ
    133   ||4|| 0100,,b,, || ADEL         || lecture non-alignée (p. ex. `lw` a une adresse impaire)
    134   ||8|| 1000,,b,, || syscall      || exécution de l'instruction `syscall`
    135 ''
    136 }}}
    137 1. 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`?
    138 {{{#!protected ------------------------------------------------------------------------------------
    139 ''
    140 Cours 10 / slide 13
    141 - 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.
    142 - 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.
    143 ''
    144 }}}
    145 1. 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?
    146 {{{#!protected ------------------------------------------------------------------------------------
    147 ''
    148 Cours 10 / slide 9
    149 - Les trois instructions sont
    150 
    151   || `mtc0 $GPR, $C0` || `M`ove `T`o `C`oprocessor `0`   || `$GPR` → COPRO_0(`$C0`)
    152   || `mfc0 $GPR, $C0` || `M`ove `F`rom `C`oprocessor `0` || `$GPR` ←  COPRO_0(`$C0`)
    153   || `eret`           || `E`xpection `RET`urn            || `PC`  ←  `EPC` ; `c0_sr.EXL`  ←  `0`
    154 
    155   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).
    156 - 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.
    157 ''
    158 }}}
    159 1. Quelle est l'adresse d'entrée dans le noyau?
    160 {{{#!protected ------------------------------------------------------------------------------------
    161 ''
    162 Cours 10 / slide 13
    163 - C'est `0x80000180`. Il n'y a qu'une adresse pour toutes les causes `syscall`, exception et interruption.
    164 - (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.
    165 ''
    166 }}}
    167 1. Que se passe-t-il quand le MIPS entre dans le noyau, lors de l'exécution de l'instruction `syscall`?
    168 {{{#!protected ------------------------------------------------------------------------------------
    169 ''
    170 Cours 10 / slide 13
    171 - L'instruction `syscall` induit beaucoup d'opérations élémentaires dans le MIPS:
    172   - `EPC` ← `PC` (adresse de l'instruction `syscall`)
    173   - `c0_SR.EXL` ← `1`  (ainsi les bits `c0_SR.UM` et `c0_SR.IE` ne sont plus utilisés)
    174   - `c0_cause.XCODE` ← `8`
    175   - `PC` ← `0x80000180`
    176 ''
    177 }}}
    178 1. 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.
    179 {{{#!protected ------------------------------------------------------------------------------------
    180 ''
    181 Cours 10 / slide 13\\
    182 - C'est l'instruction `eret` qui permet de sortir du noyau. C'est la seule instruction permettant de sortir du noyau.
    183   - `PC` ← `EPC`
    184   - `c0_SR.EXL` ← `0` (ainsi les bits `c0_SR.UM` et `c0_SR.IE` sont à nouveau utilisés)
    185 ''
    186 }}}
    187 
    188 
    189 
    190 == A2. Langage C pour la programmation système
    191 
    192 
    193 
    194 La programmation en C, vous connaissez, mais quand on programme pour le noyau, c'est un peu différent.
    195 Il 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.
    196 
    197 
    198 **Questions**
    199 
    200 
    201 1. 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 ?
    202 {{{#!protected ------------------------------------------------------------------------------------
    203 ''
    204 Cours 10 / slide 38
    205 - `__attribute__ ((section (".start")))`\\La syntaxe est un peu curieuse avec les doubles underscore et les doubles parenthèses.
    206 ''
    207 }}}
    208 1. 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.
    209 {{{#!java
    210   1 SECTIONS
    211   2 {
    212   3     .boot : {
    213   4         *(.boot)           
    214   5     } > boot_region
    215   6     .ktext : {
    216   7         *(.text*)           
    217   8     } > ktext_region
    218   9     .kdata : {
    219  10         *(.*data*)         
    220  11         . = ALIGN(4);       
    221  12         __bss_origin = .;   
    222  13         *(.*bss*)           
    223  14         . = ALIGN(4);       
    224  15         __bss_end = .;     
    225  16     } > kdata_region
    226  17 }
    227 }}}
    228 {{{#!protected ------------------------------------------------------------------------------------
    229 ''
    230 Cours 10 / slide 28
    231 - La ligne 11 contient `. = ALIGN(4)`, c'est équivalent à la directive `.align 4` de l'assembleur.
    232   Cela permet de déplacer le pointeur de remplissage de la section de sortie courante (c'est-à-dire ici `.kdata`) sur une
    233   frontière de 2^4^ octets (une adresse multiple de 16). Cette contrainte est liée aux caches que nous ne verrons pas ici.
    234 - La ligne 12 permet de créer la variable de ldscript `__bss_origin` et de l'initialiser à l'adresse courante,
    235   ce sera donc l'adresse de début de la zone `bss`.
    236 - La ligne 15 permet de créer la variable `__bss_end` qui sera l'adresse de fin de la zone `bss`
    237   (en fait c'est la première adresse qui suit juste `bss`.
    238 ''
    239 }}}
    240 1. 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`:
    241 {{{#!c
    242 __tty_regs_map   = 0xd0200000 ; /* tty's registers map, described in devices.h */
    243 }}}
    244    Dans le fichier `harch.c` :
    245 {{{#!c
    246  12 struct tty_s {
    247  13     int write;          // tty's output address
    248  14     int status;         // tty's status address something to read if not null)
    249  15     int read;           // tty's input address
    250  16     int unused;         // unused address
    251  17 };
    252  18
    253  19 extern volatile struct tty_s __tty_regs_map[NTTYS];
    254 }}}
    255   À 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` ?
    256 {{{#!protected ------------------------------------------------------------------------------------
    257 ''
    258 Cours 10 / slide 53
    259 - `extern` : informe le compilateur que la variable définie existe ailleurs. Grâce à son type, le compilateur sait s'en servir.
    260 - `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.
    261 - `__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`
    262 ''
    263 }}}
    264 1. 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:
    265  - trouver l'adresse de la fonction C qu'il doit appeler pour exécuter le service demandé;
    266  - placer cette adresse dans un registre, nous utilisons le registre `$2`;
    267  - exécuter l'instruction `jal` (ici, `jal $2`) pour appeler la fonction.
    268 
    269  Que doivent contenir les registres `$4` à `$7` et comment doit-être la pile et le pointeur de pile?
    270 {{{#!protected ------------------------------------------------------------------------------------
    271 ''
    272 Cours 10 / slide 42
    273 - C'est un appel de fonction, il faut donc respecter la convention d'appel des fonctions
    274   - Les registres `$4`à `$7` contiennent les arguments de la fonction
    275   - 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.
    276   - 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.
    277   - 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.
    278 ''
    279 }}}
    280 5. 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`
    281 {{{#!c
    282   1 int fprintf (int tty, char *fmt, ...)
    283   2 {
    284   3     int res;
    285   4     char buffer[PRINTF_MAX];
    286   5     va_list ap;
    287   6     va_start (ap, fmt);
    288   7     res = vsnprintf(buffer, sizeof(buffer), fmt, ap);
    289   8     res = syscall (tty, (int)buffer, 0, 0, SYSCALL_TTY_PUTS);
    290   9     va_end(ap);
    291  10     return res;
    292  11 }
    293  12
    294  13 void exit (int status)
    295  14 {
    296  15     syscall( status, 0, 0, 0, SYSCALL_EXIT);        // never returns
    297  16 }
    298 }}}
    299  
    300  Le code de cette fonction est dans le fichier `tp2/4_libc/ulib/crt0.c`
    301 {{{#!c
    302   1 //int syscall (int a0, int a1, int a2, int a3, int syscall_code)
    303   2 __asm__ (
    304   3 ".globl syscall     \n"         
    305   4 "syscall:           \n"         
    306   5 "   lw  $2,16($29)  \n"         
    307   6 "   syscall         \n"         
    308   7 "   jr  $31         \n"         
    309   8 );
    310 }}}
    311  Combien d'arguments a la fonction `syscall()`?
    312  Comment la fonction `syscall()` reçoit-elle ses arguments ?
    313  A quoi sert la ligne 3 de la fonction `syscall()` et que se passe-t-il si on la retire ?
    314  Expliquer la ligne 5 de la fonction `syscall()`.
    315  Aurait-il été possible de mettre le code de la fonction `syscall()` dans un fichier `.S` ?
    316 {{{#!protected ------------------------------------------------------------------------------------
    317 ''
    318 Cours 10 / slide 40
    319 - La fonction `syscall()` a 5 a arguments
    320 - Elle reçoit ses 4 premiers arguments dans les registres $4 à $7 et le 5e (le numéro de service) dans la pile.
    321 - 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.
    322 - 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.
    323 - 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.
    324 ''
    325 }}}
    326 
    327 
    328 == A3. Passage entre les modes kernel et user
    329 
    330 
    331 Le 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.
    332 
    333 
    334 **Questions**
    335 
    336 
    337 1. Comment imposer le placement d'adresse d'une fonction ou d'une variable en mémoire?
    338 {{{#!protected ------------------------------------------------------------------------------------
    339 ''
    340 Cours 9 / slide 24  et Cours 10 / slides 64 et 65
    341 - 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.
    342 - Pour placer une fonction à une place, la méthode que vous avez vu consiste
    343   - à créer une section grâce à la directive `.section` en assembleur ou à la directive `__attribute__((section()))` en C
    344   - puis à positionner la section créée dans la description des `SECTIONS` du ldscript.
    345 ''
    346 }}}
    347 1. 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.
    348 {{{#!c
    349 kinit.c:
    350     void kinit (void)
    351     {
    352         [...]
    353         extern int _start;   
    354         app_load (&_start);   
    355     }
    356 
    357 hcpua.S:
    358     .globl app_load
    359     app_load:                     
    360         mtc0   $4,      $14       
    361         li     $26,     0x12       
    362         mtc0   $26,     $12         
    363         la     $29,    __data_end   
    364         eret 
    365 }}}
    366  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 `?
    367 {{{#!protected ------------------------------------------------------------------------------------
    368 ''
    369 - 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`.
    370 - `.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`).
    371 - 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.
    372 - `$26` est un registre temporaire pour le noyau, il peut l'utiliser sans le sauver avant et donc sans le restaurer.
    373 - Il y a 4 registres affectés, dans l'ordre :
    374   - Le registre système `$14` nommé `c0_epc`, il reçoit l'adresse `_start`, c'est-à-dire l'adresse de la fonction `_start()`.
    375   - `$26` affecté à `0x12`, c'est un registre temporaire pour le noyau.
    376   - 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`
    377     - UM = 1 et IE = 0, signifie que l'on est normalement en mode `user` avec les interruptions masquées,
    378       **mais** comme `EXL`=`1`, alors on reste en mode `kernel` avec interruptions masquées.
    379   - 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.
    380 - 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).
    381 ''
    382 }}}
    383 1. Que faire avant l'exécution de la fonction `main()` du point de vue de l'initialisation? Et au retour de la fonction `main()`?
    384 {{{#!protected ------------------------------------------------------------------------------------
    385 ''
    386 Cours 10 / slide 38
    387 - Comme dans la fonction `kinit()`, il faut explicitement initialiser les variables globales non initialisées dans le programme C.
    388 - 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()`.
    389 ''
    390 }}}
    391 1. 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?
    392 {{{#!protected ------------------------------------------------------------------------------------
    393 ''
    394 Cours 10 / slide 17
    395 - Il y en a 3 (si on excepte le signal `reset` qui redémarre tout le système:
    396   1. Les appels système donc l'exécution de l'instruction `syscall`.
    397   1. Les exceptions donc les "erreur" de programmation (division par 0, adressage mémoire incorrect, etc.).
    398   1. Les interruptions qui sont des demandes d'intervention provenant des périphériques.
    399 - 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`.
    400 ''
    401 }}}
    402 1. `$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`**
    403 {{{#!c
    404  15 .section    .kentry,"ax"     
    405  16 .org        0x180           
    406  22
    407  23 kentry:                               
    408  24
    409  25     mfc0    $26,    $13                     
    410  26     andi    $26,    $26,    0x3C         
    411  27     li      $27,    0x20                   
    412  28     bne     $26,    $27,    not_syscall     
    413 }}}
    414 {{{#!protected ------------------------------------------------------------------------------------
    415 ''
    416 Cours 10 / slide 41 (mais il n'y a pas ces détails)
    417 - 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.
    418 - Commentaire du code
    419   - Ligne 25 : `$26` **←**  `c0_cause`\\⟶ donc le registre `$26`GPR réservé au kernel prend la valeur du registre de cause.
    420   - Ligne 26 : `$26` **←**  `$26 & 0b00111100`\\⟶ C'est un masque qui permet de ne conserver que les 4 bits du champ `XCODE`.
    421   - 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`.
    422   - 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.
    423 ''
    424 }}}
    425 1. 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`**
    426 {{{#!c
    427   1 #define SYSCALL_EXIT        0
    428   2 #define SYSCALL_TTY_PUTC    1
    429   3 #define SYSCALL_TTY_GETC    2
    430   4 #define SYSCALL_TTY_PUTS    3
    431   5 #define SYSCALL_TTY_GETS    4
    432   6 #define SYSCALL_CLOCK       5
    433   7 #define SYSCALL_NR          32
    434 }}}
    435   **`kernel/ksyscalls.c`**
    436 {{{#!c
    437 void *syscall_vector[] = {
    438     [0 ... SYSCALL_NR - 1] = unknown_syscall,
    439     [SYSCALL_EXIT]      = exit,
    440     [SYSCALL_TTY_PUTC]  = tty_putc,
    441     [SYSCALL_TTY_GETC]  = tty_getc,
    442     [SYSCALL_TTY_PUTS]  = tty_puts,
    443     [SYSCALL_TTY_GETS]  = tty_gets,
    444     [SYSCALL_CLOCK]     = clock,
    445 };
    446 }}}
    447   **`kernel/hcpua.S`**
    448 {{{#!xml
    449  34 ksyscall:
    450  35
    451  36     addiu   $29,    $29,    -8*4           
    452  37     mfc0    $27,    $14                     
    453  38     mfc0    $26,    $12                     
    454  39     addiu   $27,    $27,    4               
    455  40     sw      $31,    7*4($29)               
    456  41     sw      $27,    6*4($29)               
    457  42     sw      $26,    5*4($29)               
    458  43     sw      $2,     4*4($29)               
    459  44     mtc0    $0,     $12                     
    460  45
    461  46     la      $26,    syscall_vector         
    462  47     andi    $2,     $2,     SYSCALL_NR-1   
    463  48     sll     $2,     $2,     2               
    464  49     addu    $2,     $26,    $2             
    465  50     lw      $2,     0($2)                   
    466  51     jalr    $2                             
    467  52
    468  53     lw      $26,    5*4($29)               
    469  54     lw      $27,    6*4($29)               
    470  55     lw      $31,    7*4($29)               
    471  56     mtc0    $26,    $12                     
    472  57     mtc0    $27,    $14                     
    473  58     addiu   $29,    $29,    8*4             
    474  59     eret                       
    475 }}}
    476 {{{#!protected ------------------------------------------------------------------------------------
    477 ''
    478 Cours 10 / slide 42
    479 - État de la pile après l'exécution des lignes 36 à 43
    480 {{{#!xml
    481       +----------+
    482       |    $31   |  Nous allons exécuter jal un peu plus et perdre $31, il faut le sauver
    483       +----------+
    484       |  C0_EPC  |  C'est l'adresse de retour du syscall
    485       +----------+
    486       |  C0_SR   |  le registre status est modifié plus loin, il faut le sauver pour le restaurer
    487       +----------+
    488       |    $2    |  C'est le numéro de syscall qui pourra être accédé par la fonction appelée en 5e argument
    489       +----------+
    490       |          |  place réservée pour le 4e argument actuellement dans $7
    491       +----------+
    492       |          |  place réservée pour le 3e argument actuellement dans $6
    493       +----------+
    494       |          |  place réservée pour le 2e argument actuellement dans $5
    495       +----------+
    496 $29 → |          |  place réservée pour le 1e argument actuellement dans $4
    497       +----------+
    498 }}}
    499 - 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.
    500   - ''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.''\\ \\
    501 - Commentaire du code lignes 46 à 53
    502   - Ligne 46 : `$26` **←** l'adresse du tableau syscall_vector\\⟶ On s'apprête à y faire un accès indexé par le registre `$2`
    503   - 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 !
    504   - Ligne 48 : `$2`  **←** `$2 * 4`\\⟶ Les cases du tableau sont des pointeurs et font 4 octets
    505   - Ligne 49 : `$2`  **←** `$26 + $2`\\⟶ `$2` contient désormais l'adresse de la case contenant la fonction correspondante au service n°`$2`
    506   - Ligne 50 : `$2` **←** MEM[`$2`] \\⟶ $2 contient l'adresse de la fonction à appeler
    507   - 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.\\ \\
    508 - 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`.
    509 ''
    510 }}}
    511 
    512 
    513 == A4. Génération du code exécutable
    514 
    515 
    516 Pour simuler le logiciel, il faut produire deux exécutables. Nous utilisons, ici, un Makefile hiérarchique et des règles explicites.
    517 Cela sort du cadre de l'architecture, mais vous avez besoin de ce savoir-faire pour comprendre le code, alors allons-y.
    518 
    519 
    520 **Questions**
    521 
    522 
    523 1. Rappelez à quoi sert un Makefile?
    524 {{{#!protected ------------------------------------------------------------------------------------
    525 ''
    526 Cours 9 / slide 26
    527 - 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.
    528 - 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`**.
    529 ''
    530 }}}
    531 1. 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.
    532 {{{#!make
    533 kernel.x : kernel.ld obj/hcpua.o obj/kinit.o obj/klibc.o obj/harch.o
    534     $(LD) -o $@ -T $^
    535     $(OD) -D $@ > $@.s
    536 
    537 obj/hcpua.o : hcpua.S hcpu.h
    538     $(CC) -o $@ $(CFLAGS) $<
    539     $(OD) -D $@ > $@.s
    540 }}}
    541 {{{#!protected ------------------------------------------------------------------------------------
    542 ''
    543 Cours 9 / slides 27 et 56
    544 - La `cible` finale est : `kernel.x`
    545 - Les `cibles` intermédiaires sont : `kernel.ld`, `obj/hcpua.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`.
    546 - La `source` est : `hcpua.S`
    547 - Les variables automatiques servent à extraire des noms dans la définition de la dépendance (`cible : dépendances`)
    548   - dans la première règle :
    549     - `$@` = `cible` = `kernel.x`
    550     - `$^` = l'ensemble des dépendances = `kernel.ld`, `obj/hcpua.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`
    551   - dans la seconde règle :
    552     - `$@` = `cible` = `obj/hcpua.o`
    553     - `$<` = la première des dépendances = `hcpua.S`
    554 ''
    555 }}}
    556 1. 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`.
    557 {{{#!xml
    558 4_libc/
    559 ├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
    560 ├── common ────────── répertoire des fichiers commun kernel / user
    561 ├── kernel ────────── Répertoire des fichiers composant le kernel
    562 │   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
    563 ├── uapp ──────────── Répertoire des fichiers de l'application user seule
    564 │   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
    565 └── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
    566     └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
    567 }}}
    568 {{{#!protected ------------------------------------------------------------------------------------
    569 ''
    570 Ce n'est pas dit dans le cours, mais la question contient la réponse...
    571 {{{#!make
    572 compil:
    573     make -C kernel compil
    574     make -C ulib   compil
    575     make -C uapp   compil
    576 }}}
    577 ''
    578 }}}
    579 
    580 
    581 {{{#!comment
    582 Je retire cette partie, elle est trop hors sujet.
    583 
    584 == A5. Libc
    585 
    586 
    587 Cette 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.
    588 
    589 
    590 **Questions**
    591 
    592 
    593 1. fonction C à nombre d'arguments variables `fprintf`?
    594 {{{#!protected ------------------------------------------------------------------------------------
    595 ''
    596 -
    597 ''
    598 }}}
    599 1. génération de nombres pseudoaléatoires `rand`?
    600 {{{#!protected ------------------------------------------------------------------------------------
    601 ''
    602 -
    603 ''
    604 }}}
    605 1. traduction d'une chaîne de caractère en nombre `atoi`?
    606 {{{#!protected ------------------------------------------------------------------------------------
    607 ''
    608 -
    609 ''
    610 }}}
    611 }}}
    612 
    613 ----------------------------------------------------------------------------------------------------
    614 
    615 
    616 
    617 = B. Travaux pratiques
    618 
    619 
    620 
    62165Pour 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.
    62266
     
    62569
    62670
    627 == B1. Ajout d'une bibliothèque de fonctions standards pour le kernel (klibc)
     71= 1. Ajout d'une bibliothèque de fonctions standards pour le kernel (klibc)
    62872
    62973
     
    700144
    701145
    702 == B2. Programme utilisateur mais exécuté en mode kernel
     146= 2. Programme utilisateur mais exécuté en mode kernel
    703147
    704148
     
    777221
    778222
    779 == B3. Programme utilisateur utilisé en mode user mais sans libc
     223= 3. Programme utilisateur utilisé en mode user mais sans libc
    780224
    781225
     
    848292
    849293
    850 == B4.  Ajout de la librairie C pour l'utilisateur
     294= 4.  Ajout de la librairie C pour l'utilisateur
    851295
    852296