Projet système PC : 2019 - GRAVALLON Guillaume, MOISSIARD Anael

De Ensiwiki
Aller à : navigation, rechercher
Morphos image.png
Titre du projet MorphOS
Cadre Projet système

Équipe Guillaume Gravallon, Anaël Moissiard
Encadrants Yves Denneulin , Gregory Mounie, Patrick Reignier


Présentation

Voici la page de présentation du Projet Système réalisé dans le cadre de notre 2ème année en apprentissage à l'ENSIMAG.

L'objectif de ce projet est la réalisation, à partir de presque rien, d'un noyau de système d'exploitation sur une architecture Intel x86.

Le projet est basé autour des documents suivants, qui ont constitué les ressources principales sur lesquelles nous nous sommes appuyé lors de ces 4 semaines de développement:

Equipe

Organisation

Nous avons pu réaliser les 7 phases contenues dans le projet dans le temps qui était imparti. Nous avons décidé de programmer ensemble tout du long afin d'avoir tous les deux une vision globale et une compréhension de toutes les étapes du projet. De plus, certaines étapes étant assez ardues, réfléchir dessus à deux nous a permis d'avancer plus facilement et de corriger immédiatement les erreurs commises par l'un ou l'autre d'entre nous.

Nous avons utilisé Git en tant que logiciel de gestion de version décentralisé, et notre dépôt distant a été hébergé sur le gitlab fourni par l'Ensimag.

Nous n'avons pas pris le parti de mettre en place en amont du projet un planning prévisionnel, à la fois car il nous était assez difficile d'estimer la durée que prendrait la plupart des phases sans nous être plongé réellement dans les problèmes qu'elles posaient, mais également car les développements à effectuer semblaient assez séquentiels et que nous ne voyions pas forcément l'utilité de poser l'ensemble des étapes sur un planning. Comme l'a rappelé l'équipe enseignante au début du projet, l'objectif au vu du peu de temps imparti est plus de rentrer dans les développements concrets et d'essayer de faire marcher les choses afin de comprendre leur fonctionnement que de passer du temps à concevoir. Nous sommes donc rentré immédiatement dans le vif du sujet. Le fait de ne pas mettre en place de planning prévisionnel ne nous a apparemment pas particulièrement desservi étant donné que nous avons pu achever toutes les phases.

Il est à noter que nous avons beaucoup échangé avec la plupart des autres binômes, à la fois pour confronter nos compréhensions du sujet, pour réfléchir sur les aspects les plus complexes du projet à plus de deux, ou encore pour demander ou apporter de l'aide. Cela a été positif pour tout le monde, a créé une bonne dynamique et nous avons été plusieurs groupes à pouvoir grâce à cela avancer à un rythme suffisants pour pouvoir venir à bout des réalisations attendues. Si vous êtes amenés à réaliser ce projet, n'oubliez pas qu'ici comme dans la plupart des cas le partage profite à tous !

Phases de développement

Phase 1 : prise en main de l'environnement

100 %

Cette phase qui ne prend pas plus de quelques heures permet de prendre en main l'environnement et de réaliser les premiers développements en se basant sur ce qui a été réalisé en cours de Logiciel de Base en 1ère année de la filière apprentissage.

L'objectif est de mettre en place l'affichage de caractères à l'écran. Il est très important de comprendre dès le début qu'on se situe dans un environnement complètement différent de ce dont on a l'habitude lorsque l'on développe ce noyau, et que de nombreuses choses que l'on peut croire acquises ne le sont pas. L'absence de libc signifie par exemple que toutes les fonctionnalités qu'on a l'habitude de connaître (affichage, gestion de mémoire dynamique, manipulation de chaînes de caractères...) sont soit incluses dans le squelette fourni par les enseignants, soit à développer soit même ! C'est le cas par exemple de la fonction printf, et c'est pourquoi on doit lors de cette première phase développer certaines fonctions nécessaire au bon fonctionnement de printf.

On va donc développer les fonctions permettant de gérer certains caractères de bases et de les placer dans la zone mémoire correspondant à l'écran afin que ceux ci s'affichent. Cette phase n'est pas à négliger car l'utilisation d'un printf fonctionnel sera utile dans la suite du projet (même si l'affichage ne remplace pas l'utilisation de GDB lorsqu'il s'agit de débuguer, évidemment).

Phase 2 : Création et lancement de processus de niveau noyau

100 %

Cette phase vise à mettre en place la structure des processus qui vont être lancés par le kernel ou par l'utilisateur lors de toute la suite du projet. Pour ce faire, il est important de se reporter à la spécification qui permet de comprendre quelle sont (pour l'instant) les caractéristiques d'un processus, et notamment les états dans lesquels il peut se situer.

Le changement de contexte entre processus est une étape clé, réalisée par un code assembleur fourni dans les sources du projet de LBD de 1ère année, mais qui nécessite d'avoir créé une bonne structure de donnée pour les processus. On peut dans un premier temps faire un changement de contexte d'un processus A vers un processus B en s'assurant que tout se passe correctement.

Par la suite, on peut mettre en place la gestion des interruptions d'horloge afin de réaliser le changement de contexte de manière automatique à une certaine fréquence. Une nouvelle fois, le code développé en 1ère année peut être une base pour implanter cette gestion de l'interruption 33. Une fois arrivé à ce stade, on peut faire des aller-retours entre 2 processus A et B à une fréquence déterminée.

Lors de cette phase, on commence à développer les primitives liées au processus. Il est très important de suivre à la lettre les spécifications des primitives qui sont à développer tout au long du projet. Certains détails qui peuvent avoir une importance cruciale peuvent résider dans des mots à première vue anodins, c'est pourquoi il faut s'assurer avoir bien compris ce qu'il faut développer avant de le faire, sans quoi on a de grands risques de devoir apporter des corrections par la suite.

Phase 3 : Ordonnancement, création dynamique et terminaison de processus de niveau noyau

100 %

Cette troisième phase concerne la gestion du cycle de vie des processus.

Pour cela nous allons utiliser le fichier queue.h fourni qui permet d'avoir des listes chaînées FIFO avec gestion de priorité. Il est nécessaire de prendre un moment pour bien comprendre le fonctionnement des macros fournies car elles seront utiles pour tout le projet mais leur utilisation n'est pas intuitive et imparfaite (Attention: il est impossible de réaliser un queue_del dans un queue_for_each !).

Nous avons choisi de créer les processus dynamiquement avec des malloc, et de réutiliser les espaces déjà alloués quand les processus sont morts.

Il faut bien lire les specs sur la filiation afin que tout se comporte comme prévu et que les tests passent.

Phase 4 : Gestion des communications et synchronisation de processus de niveau noyau

100 %

Cette phase consiste en la mise en place de files de messages que les processus vont venir lire et écrire, restant en attente quand c'est nécessaire.

Il faut bien faire attention au comportement des fonctions psend et preceive dans les cas où la file est pleine ou vide.

Phase 5 : Séparation des espaces mémoire noyau et utilisateur : gestion de processus utilisateur

100 %

Cette phase est la plus complexe du projet. Elle nécessite peu de code, mais il faut absolument avoir bien compris ce que l'on veut faire et se documenter.

Phase 6 : Gestion du clavier et implémentation d'un pilote de console

100 %

Phase 7 : Implémentation d'un interprète de commandes

99 %

Tests Kernel

100 %

Tests User

100 %

Journal de bord

Semaine 1

05 Juin 2019

Phase 1 :

  • Découverte et prise en main de l'environnement
  • Récupération et modification du code utilisé en cours de Logiciel de Base de 1ère année pour permettre l'affichage de caractères à l'écran

Phase 2 :

  • Création de la structure des processus
  • Début de travail sur le lancement de processus et le changement de contexte entre processus

06 Juin 2019

Phase 2 :

  • Correction de la structure des processus
  • Lancement des processus
  • Changement de contexte entre processus

07 Juin 2019

Phase 2 :

  • Gestion des interruptions de l'horloge afin de procéder au changement de contexte entre processus à une fréquence déterminée

Phase 3 :

  • Mise en place de l'ordonnancement grâce aux macros de gestion de liste chaînée pré-existantes
  • Adaptation du changement de contexte pour la compatibilité avec la liste chaînée

Semaine 2

11 Juin 2019

Phases précédentes :

  • Correction d'erreurs concernant le défilement à l'écran et le changement de processus

Phase 3 :

  • Création dynamique de processus
  • Réflexion sur la filiation entre processus et la terminaison de processus

12 Juin 2019

Phase 3 :

  • Travail sur les primitives de gestion des processus:
    • Terminaison du processus actif
    • Terminaison d'un autre processus
    • Obtention de la priorité d'un processus
    • Obtention du PID du processus appelant
  • Ajout de la gestion dynamique des identifiants de processus, avec possibilité pour un nouveau processus de récupérer l'identifiant d'un processus préalablement détruit
  • Gestion de la fin de processus lorsque la fonction se termine
  • Amélioration de la fonction d'ordonnancement

13 Juin 2019

Phase 3 :

  • Finalisation des primitives de processus:
    • Attente d'un processus fils par son père
    • Correction sur les suppression de processus

Phase 4 :

  • Endormissement des processus
  • Ajout d'un processus idle qui prend la main lorsqu'il n'y a aucun autre processus activable

Tests Kernel :

  • Correction de bugs pour le passage des tests Kernel 1 à 5

14 Juin 2019

Tests Kernel :

  • Correction de bugs pour le passage des tests Kernel 6 à 9

Phase 4 :

  • Réflexions sur les files de messages et début de création des structures et primitives associées

Semaine 3

17 Juin 2019

Phase 4 :

  • Création des primitives liées aux communications par messages entre processus

Tests Kernel :

  • Validation des tests 10 à 12

18 Juin 2019

Phase 4 :

  • Rectification des primitives liées au communications de messages

Tests Kernel :

  • Validation des tests 13 à 16 et 20

19 Juin 2019

Phase 5 :

  • Travail sur l'initialisation de processus utilisateurs
  • Saut du mode kernel au mode utilisateur
  • Début de mise en place des interruptions qui permettront d'effectuer les appels systèmes

20 Juin 2019

Tests Kernel :

  • Validation du test 17.

Note : Les tests 17, 18 et 19 ne sont pas destinés à être exécutés en mode Kernel, leur validation est optionnelle ou impossible.

Phase 5 :

  • Finalisation du saut du mode kernel au mode user lors du lancement du système
  • Rectification de la procédure de déclenchement et de la gestion de l'interruption 49
  • Mise en place de la bibliothèque d'appels système

21 Juin 2019

Phase 5 :

  • Libération de pile user anciennement allouée
  • Ajout d'appels systèmes sécurisés testant les valeurs des pointeurs passés en paramètres des appels

Tests User :

  • Validation des tests 1 à 18 et 20

22 Juin 2019

Phase 6 :

  • Gestion du traitant 33 pour les interruptions claviers

Semaine 4

24 Juin 2019

Phase 6:

  • Mise en place complète de la console avec echo et remplissage d'un buffer en vue d'un futur terminal

Tests User :

  • Validation du test 19 (après rectification pour que la signature de cons_read soit conforme à ce qu'on nous demande d'implanter)

Phase 7:

  • Mise en place d'un processus shell pouvant interpréter certaines commandes au travers d'appels système

25 Juin 2019

L'un des membres de l'équipe n'a pas pu être présent lors de cette journée

Difficultés rencontrées

  • Difficultés pour identifier l'origine de certains bugs, l'utilisation de gdb avec Qemu pour examiner la mémoire n'étant pas forcément triviale pour les non initiés.
  • La phase 5, et notamment le passage en mode user depuis le mode kernel, était assez complexe et nécessitait une compréhension assez fine des mécanismes du processeur. Développer le code nécessaire à cette étape a sans nul doute été la partie la plus difficile du projet.