Projet système PC : 2019 - CHAMBONNEAU Jérôme, LENTILHAC Audrey, PLANAS MARQUILLÓ Carla, PULVÉRIC Arthur, RAPHALEN Yann, REY-RICORD Yoann

De Ensiwiki
Aller à : navigation, rechercher

Présentation

Le projet de système deuxième année second semestre consiste à poursuivre le cours de Projet de Conception de Système d'Exploitation - Fondements(PCSEF). Ce projet peut être effectué sur l'architecture Intel x86 ou sur l'architecture RISC-V. Nous avons choisi d'utiliser l'architecture RISC-V pour ce projet système. Ce projet consiste à réaliser un noyau de système d'exploitation plus avancé qu'au cours de PCSEF. L'objectif de ce projet est de mettre en oeuvre des concepts liés aux systèmes d'exploitation :

  • le bootloader
  • la gestion des processus
  • le passage du mode kernel au mode user
  • l'utilisation de files de messages
  • la mémoire virtuelle
  • le clavier
  • l'interprète de commande

Membres de l'équipe

Notre équipe est constituée des étudiants suivants :

Notre équipe est entièrement issue de la filière SEOC à l'ENSIMAG.

Gestion du projet

Le projet système est conduit sur 14 semaines. La répartition des tâches s'est effectuée de la manière suivante :

  • bootloader : Audrey Lentilhac
  • gestion des processus : Carla Planas Marquilló, Arthur Pulvéric, Yann Raphalen
  • ordonnancement : Arthur Pulvéric, Yann Raphalen
  • communication entre les processus : Audrey Lentilhac
  • mémoire virtuelle : Jerome Chambonneau, Yoann Rey-Ricord
  • gestion du clavier : Audrey Lentilhac
  • interprète de commandes : Audrey Lentilhac
  • intégration : Audrey Lentilhac
  • tests : Carla Planas Marquilló

Projet

Phase 1 : Mise en place du bootloader

100 %

Contrairement au projet système pour l'archictecture x86, il faut configurer le boot en RISC-V. Au moment du démarrage du noyau, celui-ci se trouve en mode machine soit le mode ayant le plus de privilèges. A cette étape, il est nécessaire d'effectuer les tâches suivantes dans cet ordre :

  • Vérifier que la mémoire virtuelle est désactivée (registre CSR SATP en mode bare).
  • Activer les interruptions timer en mode machine registre CSR MIE et mtimecmp).
  • Désactiver les interruptions machine (registre CSR MSTATUS).
  • Déléguer la gestion des interruptions (traps) au mode superviseur (mode moins privilégié que le mode machine) et configurer la gestion des interruptions (registre CSR STVEC).
  • Activer la protection de la mémoire physique (registre CSR pmpcfg et pmpaddr1)
  • Passer en mode supervisor (registre CSR MEPC, MSTATUS)

Phase 2 : Gestion des processus

90 %

Dans cette étape, nous devions construire les processus dans l'espace superviseur.

  • Nous avons mis en place une structure de processus relativement similaire à ce qui a été fait pour le cours de PCSEF, en ajoutant des champs permettant de gérer la notion de priorité pour l'ordonnancement. Partant de cela, nous avons pu construire la primitive start().
  • Nous avons mis en place le context switch adapté pour l'architecture RISC-V.
  • Après cela, nous avons écrit les primitives de l'horloge (clock_settings(), wait_clock(), current_clock()) ainsi que le traitant pour les interruptions timers.

Phase 3 : Ordonnancement

90 %

Après avoir construit les processus, nous devions nous intéresser à leur cycle de vie. Nous avons utilisé comme spécifié dans le projet, un ordonnancement de type FIFO avec niveau de priorité.

  • Nous avons implémenté l'ordonnancement, ainsi que les différentes transitions d'état des processus (Endormi, Actif,...).
  • Nous avons mis en place les primitives de terminaisons kill() et exit().
  • Nous avons cherché à mettre en place la gestion de l'attente (waitpid() et endormir_processus()).

Phase 4 : Communication entre les processus

0 %

Pour que les processus puissent communiquer entre eux, il est nécessaire de mettre en place une file de messages. Pour mettre en place la file de messages utilisée par les processus, il est nécessaire d'implémenter les fonctions suivantes :

  • int pcreate(int count) : crée une file de messages
  • int pdelete(int fid) : détruit une file de messages
  • int psend(int fid, int message) : envoi d'un message dans une file
  • int preceive(int fid,int *message) : reception d'un message sur une file
  • int preset(int fid) : réinitialisation d'une file de messages
  • int pcount(int fid, int *count) : envoi l'état courant d'une file

Par manque de temps, nous n'avons pas pu implémenter ces fonctions pour le projet.

Phase 5 : Mémoire virtuelle

70 %

Première étape: Division de la partie mémoire en une liste des Frames (appelé aussi blocs ou pages) de 4096 octets (0x1000).

 On commence à l'adresse _free_memory_start (0x000000008212b000), soit l'espace kernel + 256M, jusqu'à _free_memory_end (0x0000000090000000). Ces blocs composent à la fois les pages de la structure de pagination (les 3 étages), mais également les dernières, où l'on écrit nos données.

Seconde étape: Créer deux fonctions, pour récupérer un bloc et le reposer. Les blocs se suivent de la manière suivante:

 Le bloc (adresse) 0x000000008212b000 pointe sur le bloc suivant, 4096 octets plus loin, soit le bloc 0x000000008212c000. Toutefois d'autres implémentations sont tout à fait possibles

Troisième étape: Création de la "Giga page".

 Cette page est unique (adresse d'accès) pour chaque processus, mais contiendra la même structure de départ. Un bloc est visualisé telle une page de 512 lignes x 64 bits (4096o). Chaque ligne possède ou non un pte. Dans la giga page (créée pour chaque processus) une ligne peut être interprétée comme pointant sur 1G de la mémoire. On place alors dans la ligne 0 un pte composé d'un ppn de 0, qui pointera vers le début de mémoire IO. Enfin dans la ligne 2, un second pte vient se placer afin de rediriger l'adresse vers la partie kernel, soit avec un ppn de 0x80000 (la partie 0x000 manque provient de l'offset).

Quatrième étape: Intégration des autres pages.

 On vient ajouter nos 2 pages restant (la première comme pointant sur des blocs de 2M, enfin celle sur des pages de 4096) à notre giga page. Une fois fait, il ne reste qu'à lier autant de pages contiguës que nécessaire dans ce dernier étage. Un programme de 16999o prendra par exemple 6 pages, ajoutées successivement dans les 6 premières lignes. 

Nous considérons pour le projet qu'un programme ne dépasse pas une taille de 2M

une méthode de création de structure particulièrement utile peut être utilisé pour la création de certaines variables. En voici un exemple :

typedef union{

 struct {
   uint64_t A:5;
   uint64_t B:10;
   uint64_t C:8;
   uint64_t D:10;
 }bits;
 uint64_t ABCD;
}VariableABCD;

Il s'agit ici d'une variable sur 64 bits, décomposé en plusieurs partie. L'ordre est important. Les 5 bits de A sont les poids faibles. Utilisation : VariableABCD alphabet; alphabet.bits.A=0b10101; printf("%d",alphabet.ABCD);

Phase 6 : Console et gestion du clavier

90 %

Un caractère tapé au clavier sous Qemu est envoyé vers la plateforme Scifive à travers une liaison série.
Cette trame série est décodée en un caractère ASCII par le composant UART de la plateforme.
Il est nécessaire de prévenir le processeur de l'arrivée de cette nouvelle information afin que celui-ci la traite.
Pour ce faire, le composant UART est connecté sur une des sources du PLIC. Ce dernier est en charge d'orchestrer les différentes demande d'interruptions et, le cas échéant, de prévenir le processeur qu'une interruption externe est en attente.
Il faut donc dans le cas d'une interruption externe du composant UART, récupérer le caractère transmit. Ce traitement est géré en mode superviseur.
Si l'écho des caractères est activé (cons_echo) alors le système affiche le caractère à l'écran.
Si l'écho des caractères est désactivé alors le système n'affiche pas le caractère à l'écran.
Pour permettre de gérer le clavier, il est nécessaire d'implémenter un système de buffer pour récupérer les caractères et les traiter.

Phase 7 : Interprète de commandes : shell

100 %

Le shell implémenté permet de lancer un processus principal simulant un interprète de commandes.
Le shell permet ici d'effectuer les commandes suivantes :

  • echo on/off
  • exit
  • help : affichant la liste des commandes possibles
  • processus : lançant le processus

Conclusion

C'est la première année que le RISC-V apparait comme architecture dans le projet système de L'ENSIMAG. Contrairement à l'X86, il existe très peu de ressources sur Internet et dans les bibliothéques pour se documenter. La première grande étape de ce projet fut donc consacrée essentiellement à un travail de prospection et de recherche. Les différents membres de l'équipe ont collaboré afin de comprendre les rouages d'une telle architecture. Un mois plus tard, une fois le code source à leur disposition, il était alors possible de commencer l'implémentation des différentes phases présentées ici.

La plupart des phases prévues a été implémenté.
Cependant au vu de la contrainte temporelle, les membres ont fait le choix de se repartir ces tâches, en fonction de la difficulté apparente de chacune d'elles. Malheureusement, l'intégration des différentes phases entre elles est arrivée très tard dans le cycle du projet. Par conséquent, le projet n'est pas terminé à ce jour.

Le projet système étant déjà difficile en lui-même pour les étudiants de 2ème année, comprendre l'architecture RISC-V en plus de réaliser les fonctionnalités demandées, ne nous a pas permis de rendre le projet avec toutes ces fonctionnalités en temps voulu.
Ce projet a permis à l'ensemble des membres de notre équipe de comprendre l'architecture RISC-V, de comprendre la gestion des processus, la mémoire virtuelle, la gestion du buffer du clavier et la gestion des interruptions internes et externes.

Documentation