Changes between Initial Version and Version 1 of App-TD


Ignore:
Timestamp:
Feb 23, 2026, 11:02:34 PM (7 months ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • App-TD

    v1 v1  
     1[[PageOutline]]
     2{{{#!html
     3<h1> <font size="+2"> Application simple en mode utilisateur
     4</font></h1>
     5}}}
     6
     7
     8Le schéma présenté rapidement au cours 6 (slides 26 à 31) et en détail dans l'annexe du cours 6 (slides 1 à 32) représente l'exécution d'une application utilisateur très simple dont le comportement est défini par la fonction `main()`.\\L'exécution part du démarrage du SoC et va jusqu'à l'exécution de la fonction `exit()` qui stoppe l'avancée du programme.\\L'objectif de ce schéma est de comprendre les interactions entre le code de boot, le noyau, l'application et les bibliothèques système. Le schéma ci-dessous ne contient pas l'intégralité du code pour des raisons évidentes de lisibilité, mais ce qui reste devrait suffire.
     9
     10 [[Image(htdocs:img/os_bigpicture.png,nolink,height=400)]]
     11
     12- En bas à gauche, c'est le code de boot qui, ici, se contente d'initialiser la pile d'exécution du noyau et d'entrer dans le noyau par la fonction `kinit()` (kernel init).  Ce code s'exécute en mode `kernel`, mais il ne fait pas partie du noyau car, dans un vrai système, il doit charger le noyau depuis le disque dur, mais, ici, le noyau est déjà en mémoire alors c'est plus simple.
     13- En bas, c'est le noyau avec la fonction `kinit()` qui initialise les structures de données internes du noyau. Ici, il s'agit juste de mettre les variables globales non initialisées à 0, puis d'appeler la routine `app_load` qui va entrer dans la première fonction de l'application utilisateur nommée `_start()`. Dans le noyau, sur la figure, on voit aussi la routine `kentry` qui est le point d'entrée du noyau pour la gestion des services. Actuellement, il n'y a que le gestionnaire d'appel système (`syscall`). Son comportement est succinctement résumé.
     14- En haut, c'est l'application utilisateur, décomposée en trois parties. La première à gauche est la fonction `_start()` appelée par le noyau au tout début de l'application. Cette fonction initialise à 0 les variables globales non initialisées dans le programme, puis elle appelle la fonction `main()`. Si on sort de la fonction `main()` avec un `return`, la fonction `_start` fait l'appel système `exit()`. La seconde partie au centre contient le code de l'utilisateur  ''(notez que la fonction `main()` ou l'une des fonctions appelées par la fonction `main()` peut demander une sortie anticipée de l'application en appelant directement `exit()`)''. Enfin, la troisième partie, à droite, c'est le code des bibliothèques système utilisées par l'application, ce sont elles qui font les appels système, ici, seule la fonction `clock()` est représentée.
     15
     16Le but de cette séance est de s'intéresser à des points particulier de ce schéma :
     17- D'abord, nous abordons les 2 modes d'exécution du MIPS, kernel et user, utilisés respectivement pour le noyau et l'application utilisateur.
     18- Puis, nous voyons les passages du noyau à l'application et de l'application au noyau.
     19- Ensuite, nous nous intéressons à comment écrire le code C et assembleur pour contrôler le placement en mémoire.
     20- Enfin, il y a quelques quelques questions sur comment compiler pour faciliter la compréhension des TPs.
     21
     22
     23
     24= 1. Les modes d'exécution du MIPS et les instructions ''système''
     25
     26
     27
     28Dans 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.
     29
     30**Questions**
     31
     32
     331. Le MIPS propose deux modes d'exécution, rappelez quels sont ces deux modes, quel est le mode utilisé par le noyau et quel est le mode utilisé par l'application ? (C10 S6+S7)
     34{{{#!protected ------------------------------------------------------------------------------------
     35''
     36- Il y a le mode kernel et le mode user.
     37- Le mode kernel est utilisé par le noyau alors que le mode user est utilisé par l'application
     38- 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.
     39''
     40}}}
     411. 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 du MIPS».\\Est-ce qu'une adresse `X` mappée dans l'espace d'adressage du MIPS est toujours accessible (en lecture ou en écriture) quelque soit le mode d'exécution du MIPS. (C10 S7)
     42{{{#!protected ------------------------------------------------------------------------------------
     43''
     44- 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.
     45- 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`.
     46- 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.
     47''
     48}}}
     491. Le MIPS propose des registres à usage général (GPR ''General Purpose Register'') pour les calculs ($0 à $31).\\
     50 Le MIPS propose un deuxième banc de registres **à l'usage du système d'exploitation** dans le coprocesseur 0.\\
     51 Chaque registre du coprocesseur `0` porte un nom correspondant à son usage, nous en avons vu 3 en cours (C10 S7+S10 à S14) : `c0_sr`, `c0_cause` et `c0_epc`.\\
     52 Donner leur numéro et leur rôle en une phrase ?
     53{{{#!protected ------------------------------------------------------------------------------------
     54''
     55- Les registres du coprocesseur `0` 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 ce qui se trouve dans le registres `$14` sans préciser 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`)\\\\
     56- Nous avons vu les 3 principaux
     57   || `c0_sr`     || `$12` || contient essentiellement le mode d'exécution du MIPS et le bit d'autorisation des interruptions
     58   || `c0_cause`  || `$13` || contient la cause d'appel du noyau
     59   || `c0_epc`    || `$14` || contient l'adresse de l'instruction ayant provoqué l'appel du noyau ou l'adresse de l'instruction suivante
     60- Il y en a d'autres, dont certains seront utilisés plus tard
     61   || `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)
     62   || `c0_count`  || `$9 ` || contient le nombre de cycles depuis le démarrage du MIPS
     63   || `c0_procid` || `$15` || contient le numéro du processeur (utile pour les architectures multicores)
     64''
     65}}}
     661. Les deux instructions qui permettent de manipuler les registres du coprocesseur `0` sont `mtc0` et `mfc0` (C10 S11).\\
     67 Quelle est leur syntaxe ? [wiki:Doc-MIPS-Archi-Asm-kernel#instprot réponse dans Documentation MIPS Architecture et assembleur (4.)]\\
     68 Est-ce qu'on peut manipuler ces registres de coprocesseur avec d'autres instructions ?\\
     69 Écrivez les instructions permettant de faire `c0_epc = c0_epc + 4` (vous utiliserez le registre GPR `$8`)
     70{{{#!protected ------------------------------------------------------------------------------------
     71''
     72  || `mtc0 $GPR, $C0` || `M`ove `T`o `C`oprocessor `0`   || `$GPR` → COPRO_0(`$C0`)
     73  || `mfc0 $GPR, $C0` || `M`ove `F`rom `C`oprocessor `0` || `$GPR` ←  COPRO_0(`$C0`)
     74- Attention à l'ordre des registres dans les instructions.\\
     75 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).\\
     76 `$C0`  peut être `c0_sr` (i.e. $12) ou `c0_cause` (i.e. $13) ou `c0_epc` (i.e. $14)
     77- non, il n'est pas possible d'utiliser d'autres instructions pour les manipuler, on peut juste les lire et les écrire en utilisant les instructions `mtc0` et `mfc0`\\ \\
     78- `c0_epc = c0_epc + 4`
     79{{{#!c
     80mfc0    $8, $14
     81addiu   $8, $8, 4
     82mtc0    $8, $14
     83}}}
     84''
     85}}}
     861. Le registre status (`c0_sr` ou `$12` du coprocesseur `0`) 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 0, 1 et 4 de l'octet 0. (C10 S12+S13+S15)\\[wiki:Doc-MIPS-Archi-Asm-kernel#c0sr réponse dans Documentation MIPS Architecture et assembleur (6.)]
     87{{{#!protected ------------------------------------------------------------------------------------
     88''
     89 || 0|| IE  ||Interrupt Enable||0 → interruptions masquées\\1 → interruptions autorisées si ERL et EXL sont tous les deux à 0
     90 || 1|| EXL ||EXception Level ||1 → MIPS en mode exception à l'entrée dans le kernel\\le MIPS est en mode kernel, interruptions masquées
     91 || 4|| UM  ||User Mode       ||0 → MIPS en mode kernel\\1 → MIPS en mode user, seulement si ERL et EXL sont tous les deux à 0
     92''
     93}}}
     941. Le registre cause (`c0_cause` ou `$13` du coprocesseur `0`) 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 (C10 S14+S15)\\[wiki:Doc-MIPS-Archi-Asm-kernel#c0cause réponse dans Documentation MIPS Architecture et assembleur (7.)]
     95{{{#!protected ------------------------------------------------------------------------------------
     96''
     97- 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.
     98- Les codes les plus importantes à connaitre sont 0 et 8 (interruption et syscall). Les autres codes sont pour les exceptions, c'est-à-dire des fautes faites par le programme.
     99
     100  ||0|| 0000,,b,, || interruption || un contrôleur de périphérique à lever un signal IRQ
     101  ||4|| 0100,,b,, || ADEL         || lecture non-alignée (p. ex. `lw` a une adresse impaire)
     102  ||8|| 1000,,b,, || syscall      || exécution de l'instruction `syscall`
     103''
     104}}}
     1051. Le registre `c0_epc` (`$14`du coprocesseur `0`) 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`? (C10 S15)
     106{{{#!protected ------------------------------------------------------------------------------------
     107''
     108- Une exception, c'est dû à une erreur du programme, telle que la lecture d'un mot à une adresse non mappée, 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 `c0_epc` contient l'adresse de l'instruction fautive. Le gestionnaire d'exceptions dans le noyau pourra lire l'instruction et éventuellement corriger le problème.
     109- 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 la vraie adresse de retour du `syscall`.
     110''
     111}}}
     1121. 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? (C10 S11)
     113{{{#!protected ------------------------------------------------------------------------------------
     114''
     115- Les trois instructions sont `mtc0`, `mfc0` (déjà vues au dessus) et `eret`
     116
     117  || `mtc0 $GPR, $C0` || `M`ove `T`o `C`oprocessor `0`   || `$GPR` → COPRO_0(`$C0`)
     118  || `mfc0 $GPR, $C0` || `M`ove `F`rom `C`oprocessor `0` || `$GPR` ←  COPRO_0(`$C0`)
     119  || `eret`           || `E`xpection `RET`urn            || `PC`  ←  `EPC` ; `c0_sr.EXL`  ←  `0`
     120
     121- 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.
     122''
     123}}}
     1241. Quelle est l'adresse d'entrée dans le noyau au démarrage (à la sortie du code de boot) et après (depuis l'application) ? (C10 S15 S20)
     125{{{#!protected ------------------------------------------------------------------------------------
     126''
     127- Au démarrage, le boot saute à l'adresse de la fonction `kinit()` pour entrer dans le noyau.
     128- En dehors du démarrage, c'est `0x80000180`. Il n'y a qu'une adresse pour toutes les causes `syscall`, exceptions et interruptions.
     129''
     130}}}
     1311. Que se passe-t-il lorsqu'on entre dans le noyau après de l'exécution de l'instruction `syscall`? (C10 S15)
     132{{{#!protected ------------------------------------------------------------------------------------
     133''
     134- L'instruction `syscall` induit 4 opérations élémentaires dans le MIPS:
     135  - `c0_epc` ← `PC` (sauvegarde dans `c0_epc` adresse de l'instruction `syscall`)
     136  - `c0_sr.EXL` ← `1`  (ainsi les bits `c0_sr.UM` et `c0_sr.IE` ne sont plus utilisés)
     137  - `c0_cause.XCODE` ← `8` (c'est la cause `syscall`)
     138  - `PC` ← `0x80000180` (c'est l'adresse d'entrée dans le noyau)
     139- Ces 4 opérations élémentaires sont réalisées par l'instruction `syscall` !
     140''
     141}}}
     1421. Quelle instruction utilise-t-on pour sortir du noyau afin d'entrer dans l'application ?\\Dîtes précisément ce que fait cette instruction dans le MIPS. (C10 S15)
     143{{{#!protected ------------------------------------------------------------------------------------
     144''
     145- C'est l'instruction `eret` qui permet de sortir du noyau.\\**C'est la seule instruction permettant de sortir du noyau.**
     146  - `PC` ← `c0_epc` (c'est ''l'équivalent'' du `jr $31` pour sortir d'une fonction)
     147  - `c0_sr.EXL` ← `0` (ainsi les bits `c0_sr.UM` et `c0_sr.IE` sont à nouveau utilisés)
     148- Ces 2 opérations élémentaires sont réalisées par l'instruction `eret` !
     149''
     150}}}
     151
     152
     153
     154= 2. Passage entre les modes kernel et user
     155
     156
     157
     158Le noyau et l'application sont deux exécutables compilés indépendamment mais qui ne sont pas indépendants puisqu'on doit passer du noyau à l'application et inversement. 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.
     159
     160
     161**Questions**
     162
     163
     1641. Comment imposer le placement d'adresse d'une fonction ou d'une variable en mémoire lorsqu'on produit un programme binaire exécutable, c'est-à-dire quel outil de la chaîne de compilation réalise ce placement en mémoire et avec quel fichier de configuration ? (C9 S18+S22+S23 C10 annexe S6+S8)
     165{{{#!protected ------------------------------------------------------------------------------------
     166''
     167- 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 les fichiers ldscript `kernel.ld` ou `user.ld` que le programmeur peut imposer ses choix de placement dans l'espace d'adressage.
     168- Pour placer une fonction à une adresse précise, la méthode que vous avez vu consiste
     169  - à créer une section grâce à la directive `.section` en assembleur ou grâce à la directive `__attribute__((section()))` pour les programmes C, dans les deux cas le programmeur choisit un nom de section.
     170  - puis à positionner la section ainsi créée dans la description des `SECTIONS` du fichier ldscript concerné.
     171''
     172}}}
     1731. La première fonction d'un programme utilisateur est la fonction `_start()`, c'est elle qui appelle la fonction `main()`. La fonction `_start()` est donc dans le code de l'application, et non pas dans le noyau. Cependant le noyau doit connaître son adresse afin de pouvoir y sauter et ainsi entrer dans l'application. \\Dans le code ci-après, nous voyons comment la fonction `kinit()` appelle cette fonction `_start()`. Deux fichiers sont impliqués : `kinit.c` dans lequel se trouve la fonction `void kinit(void)` et `hcpua.S` dans lequel se trouve la fonction `void app_load(void *)` en charge d'appeler la fonction `_start()`.
     174{{{#!c
     175kinit.c:
     176    void kinit (void)
     177    {
     178        [...]
     179        extern int _start;          # declaree ailleurs a une adresse connue de l'editeur de lien
     180        app_load (&_start);         # appel de la fonction app_load definie dans hcpua.S
     181    }
     182
     183hcpua.S:
     184    .globl app_load
     185    app_load:                     
     186        mtc0   $4,      $14        # $4 contient l'argument   
     187        li     $26,     0x12       # $26 <--  0x12 == 0b00010010
     188        mtc0   $26,     $12        # c0_sr <-- 0x12
     189        la     $29,    __data_end  # initialisation du pointeur de pile
     190        eret 
     191}}}
     192 Comme le noyau et l'application sont deux exécutables compilés indépendamment, il doit y avoir une convention permettant au noyau de savoir quelle est l'adresse de `start()`.\\ Où se trouve donc la fonction `_start()` et comment le kernel connaît-il son adresse ? (C10 S30+S32)\\
     193{{{#!protected ------------------------------------------------------------------------------------
     194''
     195- La fonction `_start()` est au début de la section `.text` (qui contient le code de l'utilisateur). Le noyau connait cette adresse parce qu'elle est définit dans son fichier ldscript `kernel.ld`.
     196''
     197}}}
     198 À quoi sert `.globl app_load `? (C9 S18 C10 S20)\\
     199{{{#!protected ------------------------------------------------------------------------------------
     200''
     201- `.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`). Il faut donc le rendre e`xtern`.
     202''
     203}}}
     204 Quels sont les registres utilisés dans le code de `app_load `?\\Que savez-vous de l'usage de `$26 `?
     205{{{#!protected ------------------------------------------------------------------------------------
     206''
     207- 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 du coprocesseur `0`.
     208- `$26` est un registre GPR temporaire pour le noyau, il peut l'utiliser sans le sauver avant et donc sans le restaurer.
     209'''
     210}}}
     211 Quels sont les registres modifiés ? Expliquez pour chacun la valeur affectée. \\
     212{{{#!protected ------------------------------------------------------------------------------------
     213''
     214- Il y a 4 registres affectés, dans l'ordre :
     215  - Le registre du coprocesseur 0 `$14` nommé `c0_epc`, il reçoit l'adresse `_start`, c'est-à-dire l'adresse de la fonction `_start()`.
     216  - `$26` affecté par `0x12` ($26 c'est un registre temporaire pour le noyau, on peut l'écraser sans sauver sa valeur).
     217  - Le registre du coprocesseur 0 `$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`
     218    - UM = 1 et IE = 0, signifie que l'on est normalement en mode `user` avec les interruptions masquées,
     219      **mais** comme `EXL`=`1`, alors on reste en mode `kernel` avec interruptions masquées.\\
     220      Ici les interruptions sont masquées même quand exécute l'application parce que le noyau ne contient pas encore le gestionnaire des interruptions. En effet, on construit le noyau par morceau et le gestionnaire des interruptions est vu au cours suivant.
     221  - Le registre GPR `$29` reçoit l'adresse de la première adresse en haut de la région `data_region`. C'est le haut de la pile pour l'utilisateur.
     222''
     223}}}
     224 Que fait l'instruction `eret `? (C10 S15)
     225{{{#!protected ------------------------------------------------------------------------------------
     226''
     227- 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).
     228''
     229}}}
     2301. Que doit-on faire dans la fonction `_start()` avant l'exécution de la fonction `main()` du point de vue de l'initialisation? Et que doit-on faire dans la fonction `_start()` au retour de la fonction `main()` ? (C10 S24)
     231{{{#!protected ------------------------------------------------------------------------------------
     232''
     233- Comme dans la fonction `kinit()`, il faut explicitement initialiser les variables globales non initialisées dans le programme C. En effet, le programmeur suppose que les variables globales non initialisées explicitement dans le programme C sont à 0. Comme ces variables ne sont pas explicitement initialisées, elles n'occupent pas de place dans le fichier exécutable, on sait juste ou elles sont placées en mémoire.
     234- 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é la fonction `exit()`. Dans ce cas, la valeur rendue par l'application est la valeur de retour de la fonction `main()`.
     235''
     236}}}
     2371. Nous avons vu que le noyau est sollicité par des demandes de service, quels sont-ils ? Nous rappelons que l'instruction `syscall` initialise le champs `xcode` du registre `c0_cause`, ainsi donc comment le noyau fait-il pour connaître la cause de son appel? (C10 S25)
     238{{{#!protected ------------------------------------------------------------------------------------
     239''
     240- Il y en a 3 (si on excepte le signal `reset` qui redémarre tout le système:
     241  1. Les appels système donc l'exécution de l'instruction `syscall`.
     242  1. Les exceptions donc les "erreur" de programmation (division par 0, adressage mémoire incorrect, etc.).
     243  1. Les interruptions qui sont des demandes d'intervention provenant des périphériques.
     244- 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`.
     245''
     246}}}
     2471. On rappelle que `$26` et `$27` sont deux registres GPR temporaires **''réservés''** pour le noyau pour faire des calculs sans qu'il ait besoin de les sauvegarder dans la pile. **Ce ne sont pas des registres du coprocesseur 0** 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 le cours et dans les fichiers du TP). La section `.kentry` est placée à l'adresse `0x80000000` par l'éditeur de lien, conformément à ce qui est demandé dans son fichier ldscript `kernel.ld`.\\ \\**`kernel/hcpua.S`**
     248{{{#!c
     249 15 .section    .kentry,"ax"     
     250 16 .org        0x180           
     251 22
     252 23 kentry:                               
     253 24
     254 25     mfc0    $26,    $13                     
     255 26     andi    $26,    $26,    0x3C         
     256 27     li      $27,    0x20                   
     257 28     bne     $26,    $27,    kpanic     
     258}}}
     259 Ligne 16, la directive `.org DEP` (`.org` pour ''origine'', `DEP` pour ''déplassement'') permet de placer le pointeur de remplissage de la section courante à `DEP` octets du début de la section, ici `DEP = 0x180`. Pourquoi faire ça ? Aurait-on pu remplacer le `.org 0x180` par `.space 0x180` ? (C10 S5 et connaissance de l'assembleur)
     260{{{#!protected ------------------------------------------------------------------------------------
     261''
     262- 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`.
     263- La directive `.space 0x180` réserve `0x180`, si on met cette directive au tout début de la section, c'est équivalent.
     264''
     265}}}
     266 Expliquer les lignes 25 à 28. (C10 S20+S26+S31)
     267{{{#!protected ------------------------------------------------------------------------------------
     268''
     269- Commentaire du code
     270  - Ligne 25 : `$26` **←**  `c0_cause`\\⟶ donc le registre GPR `$26`réservé au kernel prend la valeur du registre de cause.
     271  - Ligne 26 : `$26` **←**  `$26 & 0b00.1111.00`\\⟶ C'est un masque qui permet de ne conserver que les 4 bits du champ `XCODE`.
     272  - Ligne 27 : `$27` **←**  `0b 00.1000.00`\\⟶ On initialise le registre GPR réservé au kernel $27 avec la valeur attendue dans $26 s'il s'agit d'une cause `syscall`.
     273  - Ligne 28 : si `$26` ≠ `$27` goto 'kpanic'\\⟶ Si ce n'est pas un `syscall`, on va à la fonction `kpanic`, sinon on continue en séquence.
     274''
     275}}}
     2761. Le gestionnaire de `syscall` est la partie du code noyau qui gère l'exécution des services demandés par l'instruction `syscall`.\\Pour ce noyau, c'est un code en assembleur présent dans le fichier `kernel/hcpua.S` que nous allons détailler.\\Pour vous aider dans la compréhension du code, vous devez vous souvenir que l'instruction `syscall` réalise un peu un appel de fonction:\\- sauf que la fonction est définie par un ''numéro de syscall'' contenu dans le registre GPR `$2`;\\- les arguments sont bien dans les registres $4 à $7, mais il y en a 4 au maximum;\\- toutefois, la fonction appelante de syscall n'a pas réservé d'espace dans la pile pour les arguments, il faudra le faire;\\- enfin, le registre `$2` contient la valeur de retour du syscall.\\ \\Le numéro contenu dans le registre `$2` est utilisé par le noyau pour indexer un tableau de pointeurs de fonctions de ''syscall'' nommé `syscall_vector[]`, ou vecteur de syscalls en français. Ce vecteur de syscalls est défini dans le fichier `kernel/ksyscalls.c`.\\Les lignes `36` à `43` du code assembleur (`kernel/hcpua.S`) sont chargées d'allouer de la place dans la pile, nous allons voir pourquoi...\\ \\**`common/syscalls.h`**
     277{{{#!c
     278  1 #define SYSCALL_EXIT        0
     279  2 #define SYSCALL_READ        1
     280  3 #define SYSCALL_WRITE       2
     281  4 #define SYSCALL_CLOCK       3
     282  5 #define SYSCALL_NR          32
     283}}}
     284  **`kernel/ksyscalls.c`**
     285{{{#!c
     286void *syscall_vector[] = {
     287    [0 ... SYSCALL_NR - 1] = unknown_syscall,
     288    [SYSCALL_EXIT        ] = exit,
     289    [SYSCALL_READ        ] = tty_read,
     290    [SYSCALL_WRITE       ] = tty_write,
     291    [SYSCALL_CLOCK       ] = clock,
     292};
     293}}}
     294  **`kernel/hcpua.S`**
     295{{{#!xml
     296 34 syscall_handler:
     297 35
     298 36     addiu   $29,    $29,    -8*4           
     299 37     mfc0    $27,    $14                     
     300 38     mfc0    $26,    $12                     
     301 39     addiu   $27,    $27,    4               
     302 40     sw      $31,    7*4($29)               
     303 41     sw      $27,    6*4($29)               
     304 42     sw      $26,    5*4($29)               
     305 43     sw      $2,     4*4($29)               
     306 44     mtc0    $0,     $12                     
     307 45
     308 46     la      $26,    syscall_vector         
     309 47     andi    $2,     $2,     SYSCALL_NR-1   
     310 48     sll     $2,     $2,     2               
     311 49     addu    $2,     $26,    $2             
     312 50     lw      $2,     0($2)                   
     313 51     jalr    $2                             
     314 52
     315 53     lw      $26,    5*4($29)               
     316 54     lw      $27,    6*4($29)               
     317 55     lw      $31,    7*4($29)               
     318 56     mtc0    $26,    $12                     
     319 57     mtc0    $27,    $14                     
     320 58     addiu   $29,    $29,    8*4             
     321 59     eret                       
     322}}}
     323  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. (C10 S26+S31+S34)
     324{{{#!protected ------------------------------------------------------------------------------------
     325''
     326- État de la pile après l'exécution des lignes 36 à 43
     327{{{#!xml
     328      +----------+
     329      |    $31   |  Nous allons exécuter jal et perdre $31, il faut le sauver
     330      +----------+
     331      |  C0_EPC  |  C'est l'adresse de retour du syscall
     332      +----------+
     333      |  C0_SR   |  le registre status est modifié, il faut le sauver pour le restaurer
     334      +----------+
     335      |    $2    |  numéro de syscall qui peut être lu par la fonction appelée (5e arg)
     336      +----------+
     337      |          |  place réservée pour le 4e argument actuellement dans $7
     338      +----------+
     339      |          |  place réservée pour le 3e argument actuellement dans $6
     340      +----------+
     341      |          |  place réservée pour le 2e argument actuellement dans $5
     342      +----------+
     343$29 → |          |  place réservée pour le 1e argument actuellement dans $4
     344      +----------+
     345}}}
     346- 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.
     347  - ''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.''\\ \\
     348- Commentaire du code lignes 46 à 53
     349  - Ligne 46 : `$26` **←** l'adresse du tableau syscall_vector\\⟶ On s'apprête à y faire un accès indexé par le registre `$2`
     350  - 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 !
     351  - Ligne 48 : `$2`  **←** `$2 * 4`\\⟶ Les cases du tableau sont des pointeurs et font 4 octets
     352  - Ligne 49 : `$2`  **←** `$26 + $2`\\⟶ `$2` contient désormais l'adresse de la case contenant la fonction correspondante au service n°`$2`
     353  - Ligne 50 : `$2` **←** MEM[`$2`] \\⟶ $2 contient l'adresse de la fonction à appeler
     354  - 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.\\ \\
     355- 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`.
     356''
     357}}}
     358
     359
     360
     361
     362= 3. Langage C pour la programmation système
     363
     364
     365
     366
     367La programmation en C, vous connaissez, mais quand on programme pour le noyau il y a des éléments de syntaxe ou des besoins spécifiques que vous ne connaissez peut-être pas. Pour répondre aux questions, vous devez avoir lu les transparents de l'annexe du cours 6, dans lesquels une séquence complète de code est détaillée du boot à exit.
     368
     369
     370**Questions**
     371
     372
     3731. 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 ? (C10 S30 C10 annexe S8)
     374{{{#!protected ------------------------------------------------------------------------------------
     375''
     376- `__attribute__ ((section (".start")))`\\La syntaxe est un peu curieuse avec les doubles underscore et les doubles parenthèses.
     377''
     378}}}
     3791. 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).
     380{{{#!java
     381  1 SECTIONS
     382  2 {
     383  3     .boot : {
     384  4         *(.boot)           
     385  5     } > boot_region
     386  6     .ktext : {
     387  7         *(.text*)           
     388  8     } > ktext_region
     389  9     .kdata : {
     390 10         *(.*data*)         
     391 11         . = ALIGN(4);       
     392 12         __bss_origin = .;   
     393 13         *(.*bss*)           
     394 14         . = ALIGN(4);       
     395 15         __bss_end = .;     
     396 16     } > kdata_region
     397 17 }
     398}}}
     399 Expliquez ce que font les lignes 11, 12 et 15 ? (C10 S32)
     400{{{#!protected ------------------------------------------------------------------------------------
     401''
     402- La ligne 11 contient `. = ALIGN(4)`, c'est équivalent à la directive `.align 4` de l'assembleur.
     403  Cela permet de déplacer le pointeur de remplissage de la section de sortie courante (c'est-à-dire ici `.kdata`) sur une
     404  frontière de 2^4^ octets (une adresse multiple de 16). Cette contrainte est liée aux caches que nous ne verrons pas ici.
     405- La ligne 12 permet de créer la variable de ldscript `__bss_origin` et de l'initialiser à l'adresse courante,
     406  ce sera donc l'adresse de début de la zone `bss`.
     407- La ligne 15 permet de créer la variable `__bss_end` qui sera l'adresse de fin de la zone `bss`
     408  (en fait c'est la première adresse qui suit juste `bss`.
     409''
     410}}}
     4111. 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`:
     412{{{#!c
     413__tty_regs_map   = 0xd0200000 ; /* tty's registers map, described in devices.h */
     414}}}
     415   Dans le fichier `harch.c` :
     416{{{#!c
     417 12 struct tty_s {
     418 13     int write;          // tty's output address
     419 14     int status;         // tty's status address something to read if not null)
     420 15     int read;           // tty's input address
     421 16     int unused;         // unused address
     422 17 };
     423 18
     424 19 extern volatile struct tty_s __tty_regs_map[NTTYS];
     425}}}
     426  Si `NTTYS` est une macro dont la valeur est `2`, quelle est l'adresse en mémoire `__tty_regs_map[1].read` ?
     427{{{#!protected ------------------------------------------------------------------------------------
     428''
     429- `__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 de la structure, c'est un entier, donc en `+8` par rapport au début de la strucrure.\\En conséquence `__tty_regs_map[1].read` est en `0xd0200018`
     430''
     431}}}
     432 À quoi servent les mots clés `extern` et `volatile` ? (C10 annexe S23 et connaissance du C)
     433{{{#!protected ------------------------------------------------------------------------------------
     434''
     435- `extern` : informe le compilateur que la variable définie existe ailleurs. Grâce à son type, le compilateur sait s'en servir.
     436- `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.
     437''
     438}}}
     4391. 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:
     440 - trouver l'adresse de la fonction C qu'il doit appeler pour exécuter le service demandé;
     441 - placer cette adresse dans un registre, nous utilisons le registre `$2`;
     442 - exécuter l'instruction `jal` (ici, `jal $2`) pour appeler la fonction.
     443
     444 Que doivent contenir les registres `$4` à `$7` et comment doit-être la pile et le pointeur de pile? (Connaissance assembleur)
     445{{{#!protected ------------------------------------------------------------------------------------
     446''
     447- C'est un appel de fonction, il faut donc respecter la convention d'appel des fonctions
     448  - Les registres `$4`à `$7` contiennent les arguments de la fonction
     449  - 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.
     450  - 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.
     451  - 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.
     452''
     453}}}
     4545. 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`.
     455{{{#!c
     456  1 int fprintf (int tty, char *fmt, ...) // tty identifiant du terminal
     457  2 {                                     // fmt chaine format, suivie d'arguments optionnels
     458  3     int res;
     459  4     char buffer[PRINTF_MAX];
     460  5     va_list ap;
     461  6     va_start (ap, fmt);                               // définit le dernier argument non-optionnel
     462  7     res = vsnprintf(buffer, sizeof(buffer), fmt, ap); // remplit le buffer avec la chaîne à afficher
     463  8     res = syscall (tty, (int)buffer, 0, 0, SYSCALL_TTY_PUTS);  // ←  appel système
     464  9     va_end(ap);
     465 10     return res;
     466 11 }
     467 12
     468 13 void exit (int status)
     469 14 {
     470 15     syscall( status, 0, 0, 0, SYSCALL_EXIT);                   // ← appel système
     471 16 }
     472}}}
     473 
     474 Le code de la fonction `syscall()` en **assembleur** est dans le fichier **C** : `tp2/4_libc/ulib/crt0.c`
     475{{{#!c
     476  1 // int syscall (int a0, int a1, int a2, int a3, int syscall_code)
     477  2 __asm__ (
     478  3 ".globl syscall     \n"         
     479  4 "syscall:           \n"         
     480  5 "   lw  $2,16($29)  \n"         
     481  6 "   syscall         \n"         
     482  7 "   jr  $31         \n"         
     483  8 );
     484}}}
     485 Combien d'arguments a la fonction `syscall()`?\\
     486 Comment la fonction `syscall()` reçoit-elle ses arguments ?\\
     487 A quoi sert la ligne 3 de la fonction `syscall()` et que se passe-t-il si on la retire ?\\
     488 Expliquer la ligne 5 de la fonction `syscall()`.\\
     489 Aurait-il été possible de mettre le code de la fonction `syscall()` dans un fichier `.S` ? (C10 S31)
     490{{{#!protected ------------------------------------------------------------------------------------
     491''
     492- La fonction `syscall()` a 5 a arguments
     493- Elle reçoit ses 4 premiers arguments dans les registres $4 à $7 et le 5e (le numéro de service) dans la pile.
     494- 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.
     495- 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.
     496- 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 d'autres fonctions assembleur, alors on créera un fichier assembleur pour les réunir.
     497''
     498}}}
     499
     500
     501
     502
     503= 4. Génération du code exécutable (optionnel)
     504
     505
     506
     507
     508Pour simuler le logiciel, il faut produire deux exécutables. Nous utilisons, ici, un Makefile hiérarchique et des règles explicites.
     509Cela sort du cadre de l'architecture, mais vous avez besoin de ce savoir-faire pour comprendre le code, alors allons-y.
     510
     511
     512**Questions**
     513
     514
     5151. Rappelez à quoi sert un Makefile?  (C9 annexe S5 à S7)
     516{{{#!protected ------------------------------------------------------------------------------------
     517''
     518- 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.
     519- 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`**. Les commandes d'une règle ne sont exécutées que si la date de la cible est plus ancienne que la date de l'une des sources dont elle dépend.
     520''
     521}}}
     5221. 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. Les lignes qui suivent sont des extraits de `1_klibc/Makefile` (le Makefile de l'étape-1). 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. (C9 annexe S5 à S7)
     523{{{#!make
     524kernel.x : kernel.ld obj/hcpua.o obj/kinit.o obj/klibc.o obj/harch.o
     525    $(LD) -o $@ -T $^
     526    $(OD) -D $@ > $@.s
     527
     528obj/hcpua.o : hcpua.S hcpu.h
     529    $(CC) -o $@ $(CFLAGS) $<
     530    $(OD) -D $@ > $@.s
     531}}}
     532{{{#!protected ------------------------------------------------------------------------------------
     533''
     534- La `cible` finale est : `kernel.x`
     535- Les `cibles` intermédiaires sont : `kernel.ld`, `obj/hcpua.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`.
     536- La `source` est : `hcpua.S`
     537- Les variables automatiques servent à extraire des noms dans la définition de la dépendance (`cible : dépendances`)
     538  - dans la première règle :
     539    - `$@` = `cible` = `kernel.x`
     540    - `$^` = l'ensemble des dépendances = `kernel.ld`, `obj/hcpua.o`, `obj/kinit.o`, `obj/klibc.o` et `obj/harch.o`
     541  - dans la seconde règle :
     542    - `$@` = `cible` = `obj/hcpua.o`
     543    - `$<` = la première des dépendances = `hcpua.S`
     544''
     545}}}
     5461. 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`. (Ce n'est pas dit dans le cours, mais la question contient la réponse...)
     547{{{#!xml
     5484_libc/
     549├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     550├── common ────────── répertoire des fichiers commun kernel / user
     551├── kernel ────────── Répertoire des fichiers composant le kernel
     552│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     553├── uapp ──────────── Répertoire des fichiers de l'application user seule
     554│   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     555└── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
     556    └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     557}}}
     558{{{#!protected ------------------------------------------------------------------------------------
     559''
     560{{{#!make
     561compil:
     562    make -C kernel compil
     563    make -C ulib   compil
     564    make -C uapp   compil
     565}}}
     566''
     567}}}
     568
     569
     570{{{#!comment
     571Je retire cette partie, elle est trop hors sujet.
     572
     573
     574
     575= 5. Libc
     576
     577
     578
     579
     580Cette 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.
     581
     582
     583**Questions**
     584
     585
     5861. fonction C à nombre d'arguments variables `fprintf`?
     587{{{#!protected ------------------------------------------------------------------------------------
     588''
     589-
     590''
     591}}}
     5921. génération de nombres pseudoaléatoires `rand`?
     593{{{#!protected ------------------------------------------------------------------------------------
     594''
     595-
     596''
     597}}}
     5981. traduction d'une chaîne de caractère en nombre `atoi`?
     599{{{#!protected ------------------------------------------------------------------------------------
     600''
     601-
     602''
     603}}}
     604}}}