Changes between Version 3 and Version 4 of Archi-1-TD10


Ignore:
Timestamp:
Dec 18, 2021, 4:54:08 PM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Archi-1-TD10

    v3 v4  
    542542Je retire cette partie, elle est trop hors sujet.
    543543
    544 == A5. Libc
     544
     545
     546= 5. Libc
     547
     548
    545549
    546550
     
    570574}}}
    571575}}}
    572 
    573 ----------------------------------------------------------------------------------------------------
    574 
    575 
    576 
    577 = B. Travaux pratiques
    578 
    579 
    580 
    581 Pour les travaux pratiques, vous devez d'abord répondre aux questions, elles ont pour but de vous faire lire le code et revoir les points du cours et vous guidez, un peu, pour l'exercice. Les réponses sont dans le cours ou dans les fichiers sources. Certaines ont déjà été traitées en TD, c'est normal. Ensuite, vous passez aux exercices pratiques.
    582 
    583 Le code se trouve dans `kO6/tp2/`, ouvrez un terminal et allez-y. Dans ce répertoire, vous avez 4 sous-répertoires et un Makefile. Le fichier `kO6/tp2/Makefile` permet de faire le ménage en appelant les Makefiles des sous-répertoires avec la cible `clean`.
    584 
    585 
    586 
    587 == B1. Ajout d'une bibliothèque de fonctions standards pour le kernel (klibc)
    588 
    589 
    590 
    591 **Objectifs de l'étape**
    592 
    593 Le 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.
    594 
    595 
    596 **Fichiers**
    597 
    598 
    599 {{{#!xml
    600 1_klibc/
    601 ├── kinit.c         : fichier contenant la fonction de démarrage du noyau
    602 ├── harch.h         : API du code dépendant de l'architecture
    603 ├── harch.c         : code dépendant de l'architecture du SoC
    604 ├── hcpu.h          : prototype de la fonction clock()
    605 ├── hcpua.S         : code dépendant du cpu matériel en assembleur
    606 ├── kernel.ld       : ldscript décrivant l'espace d'adressage pour l'éditeur de lien
    607 ├── klibc.h         : API de la klibc
    608 ├── klibc.c         : fonctions standards utilisées par les modules du noyau
    609 └── Makefile        : description des actions possibles sur le code : compilation, exécution, nettoyage, etc.
    610 }}}
    611 
    612 
    613 **Questions**
    614 
    615 
    616 1. Ouvrez le fichier Makefile (vous pouvez regarder les dépendances en ouvrant quelques fichiers sources), puis dessiner le graphe de dépendance de `kernel.x` vis-à-vis de ses sources?\\La réponse peut-être visible avec la commande `dot -Tpng Makefile.dot -oMakefile.png` à partir du fichier [htdocs:img/Makefile.dot Makefile.dot] (lien cliquable) en utilisant [https://www.graphviz.org graphviz] ... essayez c'est magique :-)
    617 {{{#!protected ------------------------------------------------------------------------------------
    618 ''
    619 {{{#!make
    620 kernel.x : kernel.ld obj/hcpua.o obj/kinit.o obj/klibc.o obj/harch.o
    621 obj/hcpua.o : hcpua.S hcpu.h
    622 obj/kinit.o : kinit.c klibc.h harch.h hcpu.h
    623 obj/klibc.o : klibc.c klibc.h harch.h hcpu.h
    624 obj/harch.o : harch.c klibc.h harch.h hcpu.h
    625 }}}
    626   [[Image(htdocs:img/Makefile.png, width=500, nolink)]]
    627 ''
    628 }}}
    629 1. Dans quel fichier se trouvent les codes dépendant du MIPS ?
    630 {{{#!protected ------------------------------------------------------------------------------------
    631 ''
    632 - Ils sont dans le fichier `hcpua.S`
    633 ''
    634 }}}
    635 
    636 
    637 **Exercices
    638 
    639 
    640 * 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`)
    641 
    642 {{{#!protected
    643 **hcpua.S**
    644 {{{#!asm
    645 .globl cpuid
    646 cpuid:
    647     mfc0    $2, $15
    648     andi    $2, $2, 0xFFF
    649     jr      $31
    650 }}}
    651 **hcpu.h**
    652 {{{#!c
    653 /**
    654  * \brief     cpu identifier
    655  * \return    a number
    656  */
    657 extern unsigned cpuid (void);
    658 }}}
    659 }}}
    660 
    661 
    662 == B2. Programme utilisateur mais exécuté en mode kernel
    663 
    664 
    665 **Objectifs de l'étape**
    666 
    667 Nous allons désormais avoir deux exécutables: le noyau et l'application. Dans cette étape, nous allons voir comment le noyau fait pour appeler l'application, alors même que celle-ci n'est pas compilée en même temps que le noyau. Nous allons passer du noyau à l'application à la fin de la fonction `kinit()`.
    668 
    669 Nous allons donc entrer dans l'application, en revanche, dans cette étape, nous n'allons pas mettre en place la gestion des syscalls. **C'est-à-dire qu'il ne sera pas possible de revenir dans le noyau depuis l'application**. C'est bien entendu une étape intermédiaire, parce qu'il faut absolument pouvoir invoquer le noyau depuis l'application pour accéder aux périphériques.
    670 Pour pouvoir quand même accéder aux registres de périphériques, nous allons **exceptionnellement** exécuter l'application en mode kernel. Ainsi, l'application pourra accéder aux adresses de l'espace d'adressage réservées au mode `kernel`.
    671 
    672 Nous avons deux exécutables à compiler et donc deux `Makefile`s de compilation. Nous avons aussi un `Makefile` qui invoque récursivement les `Makefile`s de compilation.
    673 
    674 
    675 **Fichiers**
    676 
    677 {{{#!xml
    678 2_appk/
    679 ├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
    680 ├── kernel ────────── Répertoire des fichiers composant le kernel
    681 │   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
    682 │   ├── harch.h     : API du code dépendant de l'architecture
    683 │   ├── harch.c     : code dépendant de l'architecture du SoC
    684 │   ├── hcpu.h      : prototype de la fonction clock()
    685 │   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
    686 │   ├── klibc.h     : API de la klibc
    687 │   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
    688 │   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
    689 │   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
    690 └── user ──────────── Répertoire des fichiers composant l'application user
    691     ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
    692     ├── main.c      : fonction principale de l'application
    693     ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
    694     └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
    695 }}}
    696 
    697 **Questions**
    698 
    699 1. Combien de fichiers de type ldscript avons-nous ?
    700 {{{#!protected ------------------------------------------------------------------------------------
    701 ''
    702 - Il en faut deux, un pour le kernel `kernel/kernel.ld` et un pour l'application `user/user.ld`
    703 ''
    704 }}}
    705 1. Dans quel fichier se trouve la première fonction de l'application et comment s'appelle-t-elle?
    706 {{{#!protected ------------------------------------------------------------------------------------
    707 ''
    708 - Dans le fichier `user/crt0.c`, c'est la fonction `_start()`.
    709 ''
    710 }}}
    711 1. Quelle est la fonction du noyau qui appelle cette fonction et dans quel fichier?
    712 {{{#!protected ------------------------------------------------------------------------------------
    713 ''
    714 - C'est la fonction `kinit()`, dans le fichier `kernel/kinit.c`.
    715 ''
    716 }}}
    717 1. Comment le noyau fait-il pour démarrer l'application en mode `kernel`? (la réponse est dans la fonction de la question précédente).
    718 {{{#!protected ------------------------------------------------------------------------------------
    719 ''
    720 - Dans la fonction `kinit()`, on appelle `app_load(&_start)`, c'est une fonction écrite en assembleur, donc forcément dans `hcpua.S`. Dans cette fonction on initialise `c0_epc` avec l'adresse de `_start()`, on initialise le bit `UM` du registre status `c0_sr` avec `0`. Ainsi, après l'exécution de `eret`, nous serons en mode `kernel`.
    721 {{{#!c
    722 .globl app_load // ----------------- void app_load (void * fun) called by kinit()
    723 app_load:                         // call when we exit kinit() function to go to user code
    724 
    725     mtc0   $4,      $14           // put _start address in c0_EPC
    726     li     $26,     2             // define next status reg. value
    727     mtc0   $26,     $12           // UM <- 0, IE <- 0, EXL <- 1
    728     eret                          // j EPC and EXL <- 0
    729 }}}
    730 ''
    731 }}}
    732 
    733 **Exercice**
    734 
    735 - Vous n'allez pas faire grand-chose pour cette étape parce qu'elle n'est pas très utile du fait de l'impossibilité de revenir dans le noyau après l'entrée dans l'application. Affichez juste un second message depuis la fonction `main()`
    736 
    737 
    738 
    739 == B3. Programme utilisateur utilisé en mode user mais sans libc
    740 
    741 
    742 
    743 **Objectifs de l'étape**
    744 
    745 Le programme utilisateur doit absolument s'exécuter en mode user et il doit passer par des appels système pour accéder aux services du noyau. Les services, ici, sont limités (l'accès au TTY, exit et clock), il n'empêche que pour gérer ces appels, il faut l'analyseur des causes d'appels à l'entrée du noyau et un gestionnaire de `syscall`. Il faut aussi le gestionnaire d'exceptions, parce que s'il y a une erreur de programmation, le noyau doit afficher quelque chose pour aider le programmeur.
    746 
    747 Le passage de l'application au noyau par le biais de l'instruction `syscall` impose que les numéros de services soient identiques pour le noyau et pour l'application. Ces numéros de service (comme `SYSCALL_TTY_PUTS`, `SYSCALL_EXIT` sont définis dans le fichier `syscall.h` communs au noyau et à l'application. Ce fichier est mis dans un répertoire à part nommé `common`. Il n'y a qu'un seul fichier ici, mais dans un système plus élaboré, il y en a d'autres.
    748 
    749 
    750 **Fichiers**
    751 
    752 
    753 {{{#!xml
    754 3_syscalls/
    755 ├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
    756 ├── common ────────── répertoire des fichiers commun kernel / user
    757 │   └── syscalls.h  : API la fonction syscall et des codes de syscalls
    758 ├── kernel ────────── Répertoire des fichiers composant le kernel
    759 │   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
    760 │   ├── harch.h     : API du code dépendant de l'architecture
    761 │   ├── harch.c     : code dépendant de l'architecture du SoC
    762 │   ├── hcpu.h      : prototype de la fonction clock()
    763 │   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
    764 │   ├── hcpuc.c     : code dépendant du cpu matériel en c
    765 │   ├── klibc.h     : API de la klibc
    766 │   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
    767 │   ├── kpanic.h    : déclaration du tableau de dump des registres en cas d'exception
    768 │   ├── kpanic.c    : fonction d'affichage des registres avant l'arrêt du programme
    769 │   ├── ksyscalls.c : Vecteurs des syscalls
    770 │   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
    771 │   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
    772 └── user ──────────── Répertoire des fichiers composant l'application user
    773     ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
    774     ├── main.c      : fonction principale de l'application
    775     ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
    776     └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
    777 }}}
    778 
    779 
    780 **Questions**
    781 
    782 
    783 1. Dans quel fichier se trouve la définition des numéros de services tels que `SYSCALL_EXIT` ? (''Ces numéros sont communs au noyau et à l'application'')
    784 {{{#!protected ------------------------------------------------------------------------------------
    785 ''
    786 - Ils sont dans le fichier `common/syscall.h`. C'est dit dans l'énoncé !
    787 ''
    788 }}}
    789 1. 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 ?
    790 {{{#!protected ------------------------------------------------------------------------------------
    791 ''
    792 - Il est dans le fichier `kernel/ksyscall.c`.
    793 ''
    794 }}}
    795 1. Dans quel fichier se trouve le gestionnaire de syscalls ? (''c'est de l'assembleur'')
    796 {{{#!protected ------------------------------------------------------------------------------------
    797 ''
    798 - Il est dans le fichier `kernel/hcpua.S`.
    799 ''
    800 }}}
    801 
    802 
    803 **Exercice**
    804 
    805 
    806 - Vous allez ajouter un appel système nommé `SYSCALL_CPUID` qui rend le numéro du processeur. Nous allons lui attribuer le numéro 6 (notez que ces numéros de services n'ont rien à voir avec les numéros utilisés pour le simulateur MARS). Pour ajouter un appel système, vous devez modifier les fichiers : `common/syscalls.h`, `kernel/ksyscall.c`, `kernel/hcpua.S` et `kernel/hcpu.h`.cpuid(void)`.
    807 
    808 
    809 
    810 == B4.  Ajout de la librairie C pour l'utilisateur
    811 
    812 
    813 
    814 **Objectifs de l'étape**
    815 
    816 
    817 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 pseudo alé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.
    818 
    819 ''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.''
    820 
    821 Normalement, 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.
    822 
    823 L'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`.
    824 
    825  
    826 **Fichiers**
    827 
    828 
    829 {{{#!xml
    830 4_libc/
    831 ├── Makefile        : Makefile racine qui invoque les Makefiles des sous-répertoires et qui exécute
    832 ├── common ────────── répertoire des fichiers commun kernel / user
    833 │   └── syscalls.h  : API la fonction syscall et des codes de syscalls
    834 ├── kernel ────────── Répertoire des fichiers composant le kernel
    835 │   ├── kinit.c     : fichier contenant la fonction de démarrage du noyau
    836 │   ├── harch.h     : API du code dépendant de l'architecture
    837 │   ├── harch.c     : code dépendant de l'architecture du SoC
    838 │   ├── hcpu.h      : prototype de la fonction clock()
    839 │   ├── hcpua.S     : code dépendant du cpu matériel en assembleur
    840 │   ├── hcpuc.c     : code dépendant du cpu matériel en c
    841 │   ├── klibc.h     : API de la klibc
    842 │   ├── klibc.c     : fonctions standards utilisées par les modules du noyau
    843 │   ├── kpanic.h    : déclaration du tableau de dump des registres en cas d'exception
    844 │   ├── kpanic.c    : fonction d'affichage des registres avant l'arrêt du programme
    845 │   ├── ksyscalls.c : Vecteurs des syscalls
    846 │   ├── kernel.ld   : ldscript décrivant l'espace d'adressage pour l'édition de liens du kernel
    847 │   └── Makefile    : description des actions possibles sur le code kernel : compilation et nettoyage
    848 ├── uapp ──────────── Répertoire des fichiers de l'application user seule
    849 │   ├── main.c      : fonction principale de l'application
    850 │   └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
    851 └── ulib ──────────── Répertoire des fichiers des bibliothèques système liés avec l'application user
    852     ├── crt0.c      : fonctions d'interface entre kernel et user, pour le moment : _start()
    853     ├── libc.h      : API pseudo-POSIX de la bibliothèque C
    854     ├── libc.c      : code source de la libc
    855     ├── user.ld     : ldscript décrivant l'espace d'adressage pour l'édition de liens du user
    856     └── Makefile    : description des actions possibles sur le code user : compilation et nettoyage
    857 }}}
    858 
    859 **Questions**
    860 
    861 1. 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?
    862 {{{#!protected ------------------------------------------------------------------------------------
    863 ''
    864 - Ils sont tous dans le fichier `libc.h`.
    865 - Non, pour POSIX, les prototypes de fonctions de la libc sont répartis dans plusieurs fichiers suivant leur rôle. Il y a `stdio.h`, `string.h`, `stdlib.h`, etc. Nous n'avons pas voulu ajouter cette complexité.
    866 ''
    867 }}}
    868 
    869 
    870 **Exercice**
    871 
    872 
    873 - Vous allez juste ajouter la fonction `int cpuid()` dans la librairie `libc`.
    874 - Au premier TP, vous deviez créer un petit jeu 'guess', vous pouvez en faire une application utilisateur, en utilisant cette fois les fonctions de la `libc`.