Changes between Version 50 and Version 51 of Archi-1-TP10


Ignore:
Timestamp:
Jan 3, 2021, 1:33:33 PM (4 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Archi-1-TP10

    v50 v51  
    301301
    302302
    303 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, mais comment ça se passe vraiment depuis le code C? Certaines questions sont proches de celles déjà posées, c'est volontaire.
     303Le 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.
    304304
    305305
     
    316316'''''''''''''''
    317317}}}
    318 1. Dans la question **A2.5**, nous avons vu comment la fonction `kinit` appelle la fonction `__start()` grâce à un bout de code en assembleur. Nous allons voir maintenant quelles sont les conditions de cet appel. Dans le code de la question **A2.5**, `$26` est un registre de travail pour le kernel. Quels sont les autres registres modifiés? Expliquez pour chacun la valeur affectée.
     3181. Dans la question **A2.5**, nous avons vu comment la fonction `kinit()` appelle la fonction `__start()` grâce à un bout de code en assembleur placé au début de la section `.text`. Nous allons voir maintenant quelles sont les conditions de cet appel. Dans le code de la question **A2.5**, `$26` est un registre de travail pour le kernel. Quels sont les autres registres modifiés? Expliquez pour chacun la valeur affectée.
    319319{{{#!protected ------------------------------------------------------------------------------------
    320320'''''''''''''''
    321321- Il y a 3 registres affectés, dans l'ordre :
    322   - Le registre système `$14` nommé `c0_EPC`, il reçoit l'adresse `__crt0`, c'est-à-dire l'adresse de la fonction `__start()`.
     322  - Le registre système `$14` nommé `c0_epc`, il reçoit l'adresse `__crt0`, c'est-à-dire l'adresse de la fonction `__start()`.
    323323  - 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`
    324     - UM = 1 et IE = 0, signifie que l'on est normalement en mode `user` interruptions masquées,
    325       **mais** comme `EXL` est à 1, on reste en mode `kernel` interruptions masquées. L'exécution de l'instruction `eret` mettra `EXL`
    326       à `0` pour rendre le bit `UM` actif et passer en mode `user`.
     324    - UM = 1 et IE = 0, signifie que l'on est normalement en mode `user` avec les interruptions masquées,
     325      **mais** comme `EXL`=`1`, alors on reste en mode `kernel` avec interruptions masquées. L'exécution de l'instruction `eret` mettra `EXL` à `0` pour rendre les bits `UM` et `IE` actifs et passer en mode `user` (ici avec interruptions masquées).
    327326  - 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. 
    328327'''''''''''''''
    329328}}}
    330 1. Que faire avant et après l'exécution de la fonction `main()` du point de vue de l'initialisation?
     3291. Que faire avant l'exécution de la fonction `main()` du point de vue de l'initialisation? Et au retour de la fonction `main()`?
    331330{{{#!protected ------------------------------------------------------------------------------------
    332331'''''''''''''''
    333332- Comme dans la fonction `kinit()`, il faut explicitement initialiser les variables globales non initialisées dans le programme C.
    334 '''''''''''''''
    335 }}}
    336 1. Nous avons vu que le noyau est sollicité par des événements, quels sont-ils?Quel est le comportement exact de l'instruction `syscall`?  Comment le noyau fait-il pour connaître la cause de son appel?
     333- Si on sort de la fonction `main()`, l'application s'achève. Cela signifie qu'il faut appeler la fonction `exit()` qui effectue l'appel système EXIT. Cette appel est réalisé au cas où l'application n'aurait pas explicitement exécuté `exit()`.
     334'''''''''''''''
     335}}}
     3361. 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?
    337337{{{#!protected ------------------------------------------------------------------------------------
    338338'''''''''''''''
     
    341341  1. Les exceptions donc les "erreur" de programmation (division par 0, adressage mémoire incorrect, etc.).
    342342  1. Les interruptions qui sont des demandes d'intervention provenant des périphériques.
    343 - L'instruction `syscall`sysc
    344 '''''''''''''''
    345 }}}
    346 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 des 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 par le noyau, quelle que soit la cause. Les commentaires présents dans le code ont été volontairement retirés (ils sont dans les fichiers du TP). La section `.kentry` est placée à l'adresse `0x80000000` par l'éditeur de lien. La directive `.org` (ligne 16) permet de déplacer le pointeur de remplissage de la section courante du nombre d'octets donnés en argument, ici `0x180`. Pouvez-vous dire pourquoi ? Expliquer les lignes 25 à 28.\\ \\**`kernel/hcpu.S`**
     343- 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 champs `XCODE`.
     344'''''''''''''''
     345}}}
     3461. `$26` et `$27` sont deux registres temporaires que le noyau se réserve pour faire des calculs sans qu'il ait besoin de les sauvegarder dans la pile. Ce ne sont pas des registres système comme `c0_sr` ou `c0_epc`. En effet, l'usage de ces registres (`$26` et `$27`) par l'utilisateur ne provoque pas d'exception du MIPS. Toutefois si le noyau est appelé alors il modifie ces registres et donc l'utilisateur perd leur valeur.\\Le code assembleur ci-après contient les instructions exécutées à l'entrée dans le noyau, quelle que soit la cause. Les commentaires présents dans le code ont été volontairement retirés (ils sont dans les fichiers du TP). La section `.kentry` est placée à l'adresse `0x80000000` par l'éditeur de lien. La directive `.org` (ligne 16) permet de déplacer le pointeur de remplissage de la section courante du nombre d'octets donnés en argument, ici `0x180`. Pouvez-vous dire pourquoi ? Expliquer les lignes 25 à 28.\\ \\**`kernel/hcpu.S`**
    347347{{{#!c
    348348 15 .section    .kentry,"ax"     
     
    360360- La section `kentry` est placée à l'adresse `0x80000000` or l'entrée du noyau est `0x80000180`, il faut donc déplacer le pointeur de remplissage de la section `ktentry` de `0x180`. Remarquez qu'on aurait pu utiliser une directive `.space 0x180`.
    361361- Commentaire du code
    362   - Ligne 25 : `$26` **←**  `c0_cause`\\⟶ donc le registre GPR réservé au kernel prend la valeur du registre de cause.
     362  - Ligne 25 : `$26` **←**  `c0_cause`\\⟶ donc le registre `$26`GPR réservé au kernel prend la valeur du registre de cause.
    363363  - Ligne 26 : `$26` **←**  `$26 & 0b00111100`\\⟶ C'est un masque qui permet de ne conserver que les 4 bits du champ `XCODE`.
    364364  - 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`.
    365   - 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.
    366 '''''''''''''''
    367 }}}
    368 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/hcpu.S` que nous allons observer. Pour vous aider dans la compréhension de ce code, vous devez imaginer que l'instruction `syscall` est un peu comme un appel de fonction. Ce code utilise un tableau de pointeurs de fonctions nommé `syscall_vector` définit dans le fichier `kernel/ksyscalls.c`. Les lignes `36` à `43` sont chargées d'allouer dans la pile. Dessinez l'état de la pile après l'exécution de ces instructions. Que fait l'instruction lige `44` et quelle conséquence cela a? Que font les lignes `46` à `51`? Et enfin que font les lignes `53` à `59` sans détailler ligne à ligne.\\ \\**`common/syscalls.h`**
     365  - 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.
     366'''''''''''''''
     367}}}
     3681. Le gestionnaire de `syscall` est la partie du code qui gère le comportement du noyau lors de l'exécution de l'instruction `syscall`. C'est un code en assembleur présent dans le fichier `kernel/hcpu.S` que nous allons observer. Pour vous aider dans la compréhension de ce code, vous devez imaginer que l'instruction `syscall` est un peu comme un appel de fonction. Ce code utilise un tableau de pointeurs de fonctions nommé `syscall_vector` définit dans le fichier `kernel/ksyscalls.c`. Les lignes `36` à `43` sont chargées d'allouer de la place dans la pile.\\Dessinez l'état de la pile après l'exécution de ces instructions. Que fait l'instruction ligne `44` et quelle conséquence cela a-t-il? Que font les lignes `46` à `51`? Et enfin que font les lignes `53` à `59` sans détailler ligne à ligne.\\ \\**`common/syscalls.h`**
    369369{{{#!c
    370370  1 #define SYSCALL_EXIT        0
     
    416416'''''''''''''''
    417417- État de la pile après l'exécution des lignes 36 à 43
    418 {{{
     418{{{#!xml
    419419      +----------+
    420420      |    $31   |  Nous allons exécuter jal un peu plus et perdre $31, il faut le sauver
     
    424424      |  C0_SR   |  le registre status est modifié plus loin, il faut le sauver pour le restaurer
    425425      +----------+
    426       |    $2    |  C'est le numéro de syscall qui pourra être accédé par la fonction appelé en 5e argument
     426      |    $2    |  C'est le numéro de syscall qui pourra être accédé par la fonction appelée en 5e argument
    427427      +----------+
    428428      |          |  place réservée pour le 4e argument actuellement dans $7
     
    435435      +----------+
    436436}}}
    437 - 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.\\ \\''Notez qu'interdire les interruptions pendant l'exécution des syscall est contraignant. Pour le moment, ce n'est pas important puisque nous ne traitons pas encore les interruptions, mais lorsque nous les traiterons, elles seront masquées. En conséquence, il sera 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 sera bloqué (plus rien ne bouge).''
    438 - Commentaire du code ligne 46 à 53
     437- 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.
     438  - ''Notez qu'interdire les interruptions pendant l'exécution des syscall est contraignant. Pour le moment, ce n'est pas important puisque nous ne traitons pas les interruptions, mais si nous les traitions, elles seraient masquées. En conséquence, il serait interdit aux fonctions qui traitent les appels système d'exécuter des attentes longues (comme une boucle qui attend le changement d'état d'un registre de périphérique) car sinon, le noyau serait bloqué (plus rien ne bougerait).''\\ \\
     439- Commentaire du code lignes 46 à 53
    439440  - Ligne 46 : `$26` **←** l'adresse du tableau syscall_vector\\⟶ On s'apprête à y faire un accès indexé par le registre `$2`
    440441  - Ligne 47 : `$2`  **←** `$2 & 0x1F`\\⟶ pour éviter de sortir du tableau si l'utilisateur à mis n'importe quoi dans `$2`
     
    442443  - Ligne 49 : `$2`  **←** `$26 + $2`\\⟶ `$2` contient désormais l'adresse de la case contenant la fonction correspondante au service n°`$2`
    443444  - Ligne 50 : `$2` **←** MEM[`$2`] \\⟶ $2 contient l'adresse de la fonction a appeler
    444   - Ligne 51 :  jal $2  \\⟶ appel de la fonction de service\\On rappelle que `$4` à `$7` et qu'il y a de place pour ces arguments dans la pile.
     445  - Ligne 51 :  jal $2  \\⟶ appel de la fonction de service\\On rappelle que `$4` à `$7` et qu'il y a de place pour ces arguments dans la pile.\\ \\
    445446- Les lignes 53 à 59 restorent l'état des registres `$31`, `c0_status`, `c0_epc` et le pointeur de pile puis on sort du noyau avec l'instruction `eret`.
    446447'''''''''''''''
     
    490491}}}
    4914921. 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`.
    492 {{{
     493{{{#xml
    4934944_libc/
    494495├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     
    571572
    572573
    573 {{{
     574{{{#xml
    5745751_klibc/
    575576├── kinit.c         : fichier contenant la fonction de démarrage du noyau
     
    630631**Fichiers**
    631632
    632 {{{
     633{{{#!xml
    6336342_appk/
    634635├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     
    705706**Fichiers**
    706707
    707 {{{
     708{{{#!xml
    7087093_syscalls/
    709710├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     
    780781**Fichiers**
    781782
    782 {{{
     783{{{#!xml
    7837844_libc/
    784785├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute