Changes between Version 50 and Version 51 of AS6-TME-B1


Ignore:
Timestamp:
Feb 7, 2023, 6:25:09 PM (17 months ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AS6-TME-B1

    v50 v51  
    12051205{{{#!protected ------------------------------------------------------------------------------------
    12061206'''''''''''''''
    1207 * Non, ce n'est pas un problème puisque ça fonctionne. Le code de boot a besoin de l'adresse de `k
     1207* 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 ?
     1208'''''''''''''''
     1209}}}
     1210
     1211**Exercices**
     1212
     1213- 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 :-)
     1214{{{#!protected ------------------------------------------------------------------------------------
     1215'''''''''''''
     1216- 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') ...
     1217'''''''''''''
     1218}}}
     1219
     1220
     1221== B3. Ajout d'une bibliothèque de fonctions standards pour le kernel (klibc)
     1222
     1223
     1224
     1225**Objectifs de l'étape**
     1226
     1227Le noyau gère les ressources matérielles et logicielles utilisées par les applications. Il a besoin de fonctions standards pour réaliser des opérations de base, telles qu'une fonction `print` ou une fonction `rand`. Ces fonctions ne sont pas très originales, mais elles recèlent des subtilités que vous ne connaissez peut-être pas encore, vous pouvez les regarder par curiosité. En outre, nous allons utiliser un Makefile définissant un graphe de dépendance explicite entre les fichiers cibles et les fichiers sources avec des règles de construction.
     1228
     1229
     1230**Fichiers**
     1231
     1232
     1233{{{#!xml
     123403_klibc/
     1235├── kinit.c         : fichier contenant la fonction de démarrage du noyau
     1236├── harch.h         : API du code dépendant de l'architecture
     1237├── harch.c         : code dépendant de l'architecture du SoC
     1238├── hcpu.h          : prototype de la fonction clock()
     1239├── hcpua.S         : code dépendant du cpu matériel en assembleur
     1240├── kernel.ld       : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
     1241├── klibc.h         : API de la klibc
     1242├── klibc.c         : fonctions standards utilisées par les modules du noyau
     1243└── Makefile        : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
     1244}}}
     1245
     1246
     1247**Questions**
     1248
     1249
     12501. Ouvrez le fichier Makefile, En ouvrant tous les fichiers dessiner le graphe de dépendance de `kernel.x` vis-à-vis de ses sources?
     1251{{{#!make
     1252kernel.x : kernel.ld obj/hcpua.o obj/kinit.o obj/klibc.o obj/harch.o
     1253obj/hcpua.o : hcpua.S hcpu.h
     1254obj/kinit.o : kinit.c klibc.h harch.h hcpu.h
     1255obj/klibc.o : klibc.c klibc.h harch.h hcpu.h
     1256obj/harch.o : harch.c klibc.h harch.h hcpu.h
     1257}}}
     1258{{{#!protected ------------------------------------------------------------------------------------
     1259'''''''''''''''
     1260[[Image(htdocs:img/Makefile.png, width=500, nolink)]]
     1261Ce fichier est produit par [https://graphviz.org/ Graphviz] à partir du fichier `makefile.dot` et la commande `dot -T png Makefile.dot -oMakefile.png`
     1262{{{
     1263digraph G {
     1264node [shape=box color=brown]
     1265gcc1[label="gcc -c"];
     1266gcc2[label="gcc -c"];
     1267gcc3[label="gcc -c"];
     1268gcc4[label="gcc -c"];
     1269ld[label="ld"];
     1270node [shape=ellipse color=blue]
     1271"hcpua.S" , "hcpu.h"                          -> gcc1 -> "obj/hcpua.o" -> ld -> "kernel.x"
     1272"kinit.c" , "klibc.h" , "harch.h" , "hcpu.h"  -> gcc2 -> "obj/kinit.o" -> ld
     1273"klibc.c" , "klibc.h" , "harch.h" , "hcpu.h"  -> gcc3 -> "obj/klibc.o" -> ld
     1274"harch.c" , "klibc.h" , "harch.h" , "hcpu.h"  -> gcc4 -> "obj/harch.o" -> ld
     1275}}}
     1276'''''''''''''''
     1277}}}
     1278
     12791. Dans quel fichier se trouvent les codes dépendant du MIPS ?
     1280{{{#!protected ------------------------------------------------------------------------------------
     1281'''''''''''''''
     1282- Ils sont dans le fichier `hcpua.S`
     1283'''''''''''''''
     1284}}}
     1285
     1286
     1287**Exercices
     1288
     1289
     1290* 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`)
     1291{{{#!protected
     1292**hcpua.S**
     1293{{{#!asm
     1294.globl cpuid
     1295cpuid:
     1296mfc0    $2, $15
     1297andi    $2, $2, 0xFFF
     1298jr      $31
     1299}}}
     1300**hcpu.h**
     1301{{{#!c
     1302/**
     1303* \brief     cpu identifier
     1304* \return    a number
     1305*/
     1306extern unsigned cpuid (void);
     1307}}}
     1308}}}
     1309
     1310
     1311
     1312== B4.  Ajout de la librairie C pour l'utilisateur
     1313
     1314
     1315
     1316**Objectifs de l'étape**
     1317
     1318
     1319L'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.
     1320
     1321''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.''
     1322
     1323Normalement, les librairies système sont des « vraies » librairies au sens `gcc` du terme. C'est-à-dire des archives de fichiers objet (`.o`). Ici, nous allons simplifier et ne pas créer une ''vraie'' librairie, mais seulement un fichier objet `libc.o` contenant toutes les fonctions. Ce fichier objets doit être lié avec le code de l'application.
     1324
     1325L'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`.
     1326
     1327On 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.
     1328
     1329
     1330**Fichiers**
     1331
     1332
     1333{{{#!xml
     133404_libc/
     1335├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
     1336├── common ────────── répertoire des fichiers commun kernel / user
     1337│   └── syscalls.h  : API la fonction syscall et des codes de syscalls
     1338├── kernel ────────── Répertoire des fichiers composant le kernel
     1339│   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
     1340│   ├── harch.h     : API du code dépendant de l'architecture
     1341│   ├── harch.c     : code dépendant de l'architecture du SoC
     1342│   ├── hcpu.h      : prototype de la fonction clock()
     1343│   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
     1344│   ├── hcpuc.c     : code dépendant du cpu matériel en c
     1345│   ├── klibc.h     : API de la klibc
     1346│   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
     1347│   ├── ksyscalls.c : Vecteurs des syscalls
     1348│   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
     1349│   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
     1350├── uapp ──────────── Répertoire des fichiers de l'application user seule
     1351│   ├── main.c      : fonction principale de l'application
     1352│   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
     1353└── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
     1354├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
     1355├── libc.h      : API pseudo-POSIX de la bibliothèque C
     1356├── libc.c      : code source de la libc
     1357├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
     1358└── Makefile    : description des actions possibles sur le code user : compilation et nettoyage04_libc/
     1359
     1360}}}
     1361
     1362**Questions**
     1363
     13641. Pour ce petit système, dans quel fichier sont placés tous les prototypes des fonctions de la libc? Est-ce ainsi pour POSIX sur LINUX?
     1365{{{#!protected ------------------------------------------------------------------------------------
     1366'''''''''''''''
     1367- Ils sont tous dans le fichier `libc.h`.
     1368- 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é.
     1369'''''''''''''''
     1370}}}
     13711. Dans quel fichier se trouve la définition des numéros de services tels que `SYSCALL_EXIT` ?
     1372{{{#!protected ------------------------------------------------------------------------------------
     1373'''''''''''''''
     1374- Ils sont dans le fichier `common/syscall.h`.
     1375'''''''''''''''
     1376}}}
     13771. Dans quel fichier se trouve le vecteur de syscall, c'est-à-dire le tableau `syscall_vector[]` contenant les pointeurs sur les fonctions qui réalisent les services correspondants aux syscall ?
     1378{{{#!protected ------------------------------------------------------------------------------------
     1379'''''''''''''''
     1380- Il est dans le fichier `kernel/ksyscall.c`.
     1381'''''''''''''''
     1382}}}
     13831. Dans quel fichier se trouve le gestionnaire de syscalls ?
     1384{{{#!protected ------------------------------------------------------------------------------------
     1385'''''''''''''''
     1386- Il est dans le fichier `kernel/hcpua.S`.
     1387'''''''''''''''
     1388}}}
     1389
     1390
     1391**Exercice**
     1392
     1393
     1394Pour 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.
     1395
     1396Je vous donne le code dans le corrigé, mais ça fait seulement 2 lignes, alors je pense que vous n'en aurez pas besoin ! 🙂
     1397{{{#!protected ------------------------------------------------------------------------------------
     1398'''''''''''''''
     1399{{{#!c
     1400for (int c = 32; c < 127; c++) {
     1401fputc (0, c);
     1402}
     1403}}}
     1404'''''''''''''''
     1405}}}
     1406- 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()` ?
     1407{{{#!protected ------------------------------------------------------------------------------------
     1408'''''''''''''''
     1409- 7351 (sur ma machine, peut-être que cela peut varier si, entre temps, j'ai mis une autre version du simulateur)
     1410'''''''''''''''
     1411}}}
     1412- 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()` ?
     1413{{{#!protected ------------------------------------------------------------------------------------
     1414'''''''''''''''
     1415- 20603 (sur ma machine) c'est presque 3 fois plus lent !!!
     1416'''''''''''''''
     1417}}}
     1418- 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écessaires pour exécuter la fonction `fputc()`. Pour ça, vous ouvrez le fichier `trace0.s`, vous cherchez le premier appel de `fputc()` (vous notez le cycle) et vous cherchez l'instruction `eret` qui marque la sortie du kernel (vous notez le cycle) et vous faites la différence ? Profitez en pour voir l'entrée dans le kernel, l'analyse de la cause, l'utilisation du vecteur de syscall, etc.
     1419{{{#!protected ------------------------------------------------------------------------------------
     1420'''''''''''''''
     1421- 7391 : appel de `fputc()`
     1422- 7589 : exéction de `eret`
     1423- → 7589 - 7391 = 198 cycles (pour afficher 1 caractère !)
     1424'''''''''''''''
     1425}}}
     1426- Refaites le calcul pour le deuxième appel de `fputc()`, que constatez-vous ? Avez-vous une explication  ?
     1427{{{#!protected ------------------------------------------------------------------------------------
     1428'''''''''''''''
     1429- 7620 : appel de `fputc()`
     1430- 7686 : exéction de `eret`
     1431- → 7686 - 7620 = 66 cycles, c'est plus rapide, c'est à cause des caches que nous verrons plus tard !
     1432'''''''''''''''
     1433}}}
     1434
     1435
     1436{{{#!comment
     1437
     1438----------- TROP COMPLEXE !!!
     1439
     1440Vous 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.
     1441Dans 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
     1442A la fin de l'opération, le registre LEN contient le nombre d'octet non écrits donc 0.
     1443
     1444Les registres du DMA sont à l'adresse 0xd1200000
     1445{{{
     1446DMA_IRQ_DISABLE         (lecture/écriture)      masquage de la ligne IRQ
     1447DMA_RESET               (écriture seule)        acquittement de la ligne IRQ
     1448DMA_LEN                 (écriture/lecture)      taille en octets à déplacer
     1449DMA_DST                 (écriture seule)        adresse de destination
     1450DMA_SRC                 (écriture seule)        adresse source
     1451}}}
     1452
     1453- Vous allez ajouter un appel système nommé `SYSCALL_DMA_MEMCPY` qui réalise la copie d'une zone de la mémoire. Pour cela, vous devez:
     1454- Ajouter une fonction dans la libc que vous appellerez `dma_memcpy(int *dst, int *src, unsigned len)` qui a un comportement équivalent à `memcpy()` mais pour déplacer des tableaux d'entiers. Vous devez modifier les fichiers `ulib/libc.h` et `ulib/libc.c`.
     1455- Ajouter la déclaration de `SYSCALL_DMA_MEMCPY` dans le fichier `commun/syscall.h`.
     1456- Ajouter une entrée dans le tableau `syscall_vector[]` dans le fichier `kernel/ksyscalls.h` pour la fonction `dma_memcpy()`.
     1457- Ajouter la déclaration d'une variable `__dma_regs_map` dans le fichier `kernel/kernel.ld`.
     1458- Ajouter une fonction `dma_memcpy()`, vous devez modifier les fichiers `kernel/harch.h` et `kernel/harch.c`
     1459
     1460Idéalement, il faudrait vérifier que les adresses utilisées sont uniquement dans la partie de l'espace d'adressage autorisée pour l'utilisateur. Cette vérification serait faite dans une fonction intermédiaire dans `kernel/ksyscalls.c` qui ferait les vérifications et qui appellerait `dma_memcpy()` si les adresses sont correctes. Je vous laisse y réfléchir.
     1461
     1462{{{#!protected
     1463Ajouter le FB et écrire un damier qui s'inverse.
     1464}}}