Changes between Version 44 and Version 45 of AS6-TME-B1


Ignore:
Timestamp:
Jan 31, 2023, 6:37:21 PM (17 months ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AS6-TME-B1

    v44 v45  
    11561156
    11571157
    1158 Le prototype de SoC que nous utilisons pour les TP est configurable. Il est possible par exemple de choisir le nombre de terminaux texte (TTY). Par défaut, il y en a un mais, nous pouvons en avoir jusqu'à 4. Nous allons modifier le code du noyau pour s'adapter à cette variabilité. En outre, nous allons ajouter un niveau d'abstraction qui représente un début de pilote de périphérique (device driver). Ce pilote, même tout petit constitue une couche logicielle avec une API.
     1158Le prototype de SoC que nous utilisons pour les TP est configurable. Il est possible par exemple de choisir le nombre de terminaux texte (TTY). Par défaut, il y en a un mais, nous pouvons en avoir jusqu'à 4. Nous allons modifier le code du noyau pour s'adapter à cette variabilité. En outre, nous allons ajouter un niveau d'abstraction qui représente un début de pilote de périphérique (device driver). Ce pilote, même tout petit, constitue une couche logicielle avec une API.
    11591159
    11601160**Objectifs**
     
    11881188{{{#!protected ------------------------------------------------------------------------------------
    11891189'''''''''''''''
    1190 * La fonction qui lit ce registre (`$9` qui ne désigne pas un registre GPR du processeur !) est nécessairement en assembleur car elle utilise des instructions particulières et dépend du matériel, elle est donc mise dans hcpua.S.
     1190* La fonction qui lit ce registre (`$9` qui ne désigne pas un registre GPR du processeur !) est nécessairement en assembleur car elle utilise des instructions particulières et dépend du matériel, elle est donc mise dans `hcpua.S`.
    11911191* `.globl clock` permet de faire en sorte que la fonction soit visible par les autres fichiers C.
    11921192'''''''''''''''
    11931193}}}
    1194 1. Compilez et exécutez le code avec `make exec`. Observez. Ensuite ouvrez le fichier `kernel.x.s` et regardez où a été placée la fonction `clock()`.\\Est-ce un problème si `kinit()` n'est plus au début du segment `ktext` ? Posez-vous la question de qui a besoin de connaître l'adresse de `kinit()`
    1195 {{{#!protected ------------------------------------------------------------------------------------
    1196 '''''''''''''''
    1197 * Non, ce n'est pas un problème puisque ça fonctionne. Le code de boot a besoin de l'adresse de `kinit()` mais on l'obtient avec la macro `la` - c'est l'éditeur de lien qui fera en sorte que dans les code binaire l'adresse de `kinit()` mise dans le registre `$26` soit la bonne.
     11941. Compilez et exécutez le code avec `make exec`. Observez. Ensuite ouvrez le fichier `kernel.x.s` et regardez où a été placée la fonction `clock()`.\\Est-ce un problème si `kinit()` n'est plus au début du segment `ktext` ? Pour répondre, posez-vous la question de qui a besoin de connaître l'adresse de `kinit()`
     1195{{{#!protected ------------------------------------------------------------------------------------
     1196'''''''''''''''
     1197* Non, ce n'est pas un problème puisque ça fonctionne. Le code de boot a besoin de l'adresse de `kinit()` mais on l'obtient avec la macro `la` . C'est l'éditeur de lien qui fera en sorte que dans les code binaire l'adresse de `kinit()` mise dans le registre `$26` soit la bonne. Notez que cela fonctionne parce qu'on fait l'édition de lien du noyau et du code de boot pour fabrique un seul binaire (`kernel.x`), ce qui n'est pas le cas dans un vrai système, savez-vous pourquoi ?
    11981198'''''''''''''''
    11991199}}}
     
    12021202
    12031203- Ecrire une fonction `void Capitalize(void)` appelée par la fonction `kinit()` qui lit une phrase terminée par un `\n` et la réécrit en ayant mis en majuscule la première lettre de chaque mot. Vous mettrez cette nouvelle fonction dans le fichier `kinit.c` (ce ne devrait pas être sa place mais c'est juste un exercice). Notez que vous ne pouvez pas utiliser la fonction [http://manpagesfr.free.fr/man/man3/toupper.3.html `toupper()`] parce que c'est une fonction de la `glibc` (la bibliothèque de la librairie de fonctions standards) et que là vous ne l'avez pas. Vous n'êtes pas sur Linux :-)
     1204{{{#!protected ------------------------------------------------------------------------------------
     1205'''''''''''''
     1206- Dans cette fonction `void Capitalize(void)`, pour chaque caractère lu au clavier, vous devez tester s'il est compris entre 'a' et 'z' et si oui, ajouter ('A' - 'a') ...
     1207'''''''''''''
     1208}}}
    12041209
    12051210
     
    12741279
    12751280* Le numéro du processeur est dans les 12 bits de poids faible du registre $15 (`c0_cpuid`) du coprocesseur système (à côté des registres `c0_epc`, `c0_sr`, etc.). Ajoutez la fonction `int cpuid(void)` qui lit le registre `c0_cpuid` et qui rend un entier contenant juste les 12 bits de poids faible.\\Vous pouvez vous inspirez fortement de la fonction `int clock(void)`. Comme il n'y a qu'un seul processeur dans cette architecture, `cpuid` rend toujours `0`.\\Ecrivez un programme de test (vous devrez modifier les fichiers `hcpu.h`, `hcpua.S` et `kinit.c`)
    1276 
    12771281{{{#!protected
    12781282**hcpua.S**
     
    13031307
    13041308
    1305 L'application utilisateur n'est pas censée utiliser directement les appels système. Elle utilise une librairie de fonctions standards (la `libc` POSIX, mais pas seulement) et ce sont ces fonctions qui réalisent les appels système. Toutes les fonctions de la `libc` n'utilisent pas les appels système. Par exemple, les fonctions `int rand(void)` ou `int strlen(char *)` (rendent, respectivement, un nombre pseudoaléatoire et la longueur d'une chaîne de caractères) n'ont pas besoin du noyau. Les librairies font partie du système d'exploitation mais elles ne sont pas dans le noyau.
     1309L'application utilisateur n'est pas censée utiliser directement les appels système. Elle utilise une librairie de fonctions standards (la `libc` POSIX, mais également d'autres) et ce sont ces fonctions qui réalisent les appels système. Toutes les fonctions de la `libc` n'utilisent pas les appels système. Par exemple, les fonctions `int rand(void)` ou `int strlen(char *)` (rendent, respectivement, un nombre pseudoaléatoire et la longueur d'une chaîne de caractères) n'ont pas besoin du noyau. Les librairies font partie du système d'exploitation mais elles ne sont pas dans le noyau.
    13061310
    13071311 ''Le terme « librairie » vient de l'anglais « library » qui signifie bibliothèque. On utilise souvent le mot librairie même si le sens en français n'est pas le même que celui en anglais. Disons que, dans notre contexte, les deux mots sont synonymes.''
     
    13101314
    13111315L'exécutable de l'application utilisateur est donc composé de deux parties : d'un côté, le code de l'application et, de l'autre, le code de la librairie `libc` (+ `crt0`). Nous allons répartir le code dans deux répertoires `uapp` pour les fichiers de l'application et `ulib` pour les fichiers qui ne sont pas l'application, c'est-à-dire la `libc`, le fichier `crt0.c` mais aussi le fichier ldscript `user.ld`.
     1316
     1317On rappelle que le fichier `crt0.c` contient le code d'entrée dans l'application avec la fonction `_start()` appelée par la fonction `kinit()`. C'est aussi, dans ce fichier que l'on met le code assembleur de la fonction `syscall_fct()` permettant de revenir dans le noyau. En conséquence, `crt0.c`, c'est le pont entre le noyau et l'application.
    13121318
    13131319 
     
    13501356'''''''''''''''
    13511357- Ils sont tous dans le fichier `libc.h`.
    1352 - Non, pour POSIX, les prototypes de fonctions de la libc sont répartis dans plusieurs fichiers suivant leur rôle. Il y `stdio.h`, `string.h`, `stdlib.h`, etc. Nous n'avons pas voulu ajouter cette complexité.
     1358- Non, pour POSIX, les prototypes de fonctions de la libc sont répart:q:qis dans plusieurs fichiers suivant leur rôle. Il y `stdio.h`, `string.h`, `stdlib.h`, etc. Nous n'avons pas voulu ajouter cette complexité.
    13531359'''''''''''''''
    13541360}}}
     
    13751381**Exercice**
    13761382
     1383
     1384Pour finir ce TME (un peu long 🤪 ), vous allez juste ajouter une boucle d'affichage des caractères ASCII au début de la fonction `main()` en utilisant la fonction de la libc `fputc(tty,c)` (avec `tty` à 0 pour un affichage sur le terminal `0`, et `c` la variable contenant le caractère à afficher, qui prendra toutes les valeurs entre 32 et 127.
     1385
     1386Je vous donne le code dans le corrigé, mais ça fait seulement 2 lignes, alors je pense que vous n'en aurez pas besoin ! 🙂
     1387{{{#!protected ------------------------------------------------------------------------------------
     1388'''''''''''''''
     1389{{{#!c
     1390for (int c = 32; c < 127; c++) {
     1391    fputc (0, c);
     1392}
     1393}}}
     1394'''''''''''''''
     1395}}}
     1396- Ensuite, quand ça marche, exécutez le programme en mode débug (`make debug` au lieu de `make exec`) et ouvrez le fichier `trace0.s`. A quel cycle, commence la fonction `main()` ?
     1397{{{#!protected ------------------------------------------------------------------------------------
     1398'''''''''''''''
     1399- 7351 (sur ma machine, peut-être que cela peut varier si, entre temps, j'ai mis une autre version du simulateur)
     1400'''''''''''''''
     1401}}}
     1402- Recompilez le kernel en utilisant le mode `-O0`(lettre 0 suivie du chiffre zéro), réexécutez l'application en mode debug et regardez à nouveau à quelle cycle commence la fonction `main()` ?
     1403{{{#!protected ------------------------------------------------------------------------------------
     1404'''''''''''''''
     1405- 20603 (sur ma machine) c'est presque 3 fois plus lent !!!
     1406'''''''''''''''
     1407}}}
     1408- Pour finir, recompilez à nouveau le noyau en utilisant le mode `-O3`, réexécutez encore l'application en mode debug et regardez combien de cycles sont nécessaire pour exécuter la fonction `fputc()`. Pour ça, vous ouvrez la fonction
     1409{{{#!comment
    13771410Vous allez utiliser le composant DMA. En cours, nous avons vu que le DMA réalise un copie de mémoire à partir de l'adresse contenu dans le registre DMA_SRC vers l'adresse contenu dans le registre DMA_DST du nombre d'octet contenu dans le registre DMA_LEN.
    13781411Dans l'ordre, on commence par écrire les adresses SRC, DST et IRQ_DISABLE (si besoin), puis on écrit LEN, ce qui provoque le démarrage de la copie par le DMA