Catégorie:Projets de spécialité
Bienvenue sur la page de présentation de notre système d'exploitation " Sys'thematic " qui a été développé dans le cadre du Projet Système, que nous avons choisi comme Projet de spécialité 2A.
Cette page a pour but de présenter succinctement nos réalisations ainsi que les difficultés que nous avons rencontrées (et surmontées !) pendant le projet.
Avant toute chose, voici le binaire de notre noyau qui est bootable sur machine nue (IA-32), Qemu et Bochs.
Instructions pour créer une clé bootable pour machine nue :1.
Elise Klingelschmidt (filière ISI)
François Lê (filière ISI)
Le projet système a pour objectif l'implémentation d'un système d'exploitation bootable sur machine nue d'architecture Intel i386.
Il fait suite aux cours de Pratique du Système 2 suivis pendant le premier semestre de deuxième année.
La page d'accueil du projet est ici : 3, et les spécifications techniques sont ici : 4.
Nous avons réalisé l'ensemble des sept phases décrites dans la page roadmap 5. Etant un groupe de deux, les extensions étaient optionelles mais nous avons pû en réaliser deux : amélioration du shell et système de fichiers dans la mémoire physique : voir les extensions.
Nous avons largement testé notre implémentation à l'aide de nos tests et des tests fournis par les enseignants. Nous avons fait le choix de tester exclusivement sur Qemu et Bochs jusqu'à la fin de la phase 7. A la fin de l'implémentation des sept phases obligatoires, nous avons relancé tous les tests sur machine nue. Cela a été réalisé grâce à notre commande shell test_all et la phase d'expérimentation sur machine nue s'est déroulée sans problème. En ce qui concerne la validation des extensions, nous testons d'abord sur Qemu puis sur machine réelle. La encore, il n'y avait pas de différence entre le comportement sur Qemu et le comportement sur machine nue : tout fonctionne correctement ! :-)
Nous n'avons pas eu de difficulté particulière liée à la gestion d'équipe. Nous avons fait le choix de travailler ensemble dans des salles de l'école la plupart du temps, principalement dans le but d'avoir une vision globale du projet, mais aussi pour pouvoir réfléchir ensemble sur les questions délicates ou sur les structures de données à mettre en place (entre autres : processus, buffer du clavier, système de fichier, etc).
Pour le partage de fichiers, nous avons utilisé le gestionnaire de version Git.
Nous avons également utilisé Trello 6, un gestionnaire de projet en ligne. Cet outil nous a été très utile pour noter toutes les erreurs, améliorations, idées et choses à faire à un seul endroit. Cet outil nous a aussi permis de nous répartir le travail le soir et le week-end et de visualiser notre avancement au sein même d'une phase.
thumb|upright=3|center|Trello de Sys'thematic
Nous avons réalisé un planning prévisionnel au début du projet grâce aux informations des années précédentes. Ce planning s'est avéré très proche de la réalité.
Voici le planning que nous avons mis en place :
thumb|upright=3|left|Planning et difficulté des phases
Phase 1 : Difficulté 1, 1/2 journée
Phase 2 : Difficulté 1, 1/2 journée
Phase 3 : Difficulté 2, 2 jours
Phase 4 : Difficulté 3, 3 jours
''' Tests ''' : 1 jour
Phase 5 : Difficulté 5, 7 jours
''' Tests ''' : 1/2 journée
Phase 6 : Difficulté 3, 1 jours
Phase 7 : Difficulté 3, 2 jours
Le projet est découpé en sept phases qui correspondent chacune à un aspect du système. Ces phases, de difficulté et de longueur inégales, sont dépendantes des phases précédentes et il est donc conseillé de respecter l'ordre dans lequel les effectuer. Ces phases sont décrites de façon détaillée dans le document de l'ensiwiki suivant : 7.
On affiche des caractères à l'écran en écrivant directement dans une partie de la mémoire couplée avec la carte graphique, ce qui permet d'afficher des traces d'exécution du noyau dès le début du projet.
Difficultés rencontrées : Cette phase ne nous a pas posé de difficultés, nous avons réutilisé le travail fait en Pratique du Système.
Dans cette phase, on met en place la notion de processus : c'est la structure de données qui permet au noyau de pouvoir reprendre l'exécution d'un programme qui a été interrompue.
C'est également dans cette phase que sont mises en place les interruptions horloge qui vont déclencher le changement de contexte entre les processus.
Difficultés rencontrées : Cette phase ne nous a pas posé de difficultés, le travail demandé ressemble beaucoup à celui réalisé en Pratique du Système.
Conseil : La structure du processus sera amenée à évoluer, ne pas chercher à tout gérer dans cette phase.
Dans cette phase, on s'intéresse au cycle de vie des processus :
1) Ordonnancement
Les processus doivent maintenant être ordonnancés suivant leur priorité. Nous avons utilisé pour cela les primitives de files de priorité fournies dans les sources de départ.
L'ordonnancement des processus peut avoir lieu dans trois cas :
* soit une interruption horloge a eu lieu : le nouvel élu est choisi parmi les processus les plus prioritaires selon un ordre FIFO.
* soit un processus auparavant inactif (endormi, en attente) redevient actif : s'il est strictement plus prioritaire que le processus actif courant, on fait appel à la fonction d'ordonnancement qui va élire ce processus comme nouveau processus actif.
* soit le processus actif se bloque ou s'endort : on fait appel à la fonction d'ordonnancement qui élira le prochain processus actif parmi les plus prioritaires.
2) Création dynamique
A partir de cette phase, les processus peuvent être créés de façon dynamique, grâce à un appel à start : leurs structures (contexte d'exécution, pile, ...) doivent donc être allouées au moment de la création et libérées au moment de la terminaison.
3) Terminaison
Il y a trois façon pour un processus de se terminer :
Lorsque le processus qui se termine est actif, on ne peut pas libérer immédiatement ses structures (contexte d'exécution, pile, ...) car on a besoin de pouvoir l'ordonnancer une dernière fois afin de pouvoir passer la main à un autre processus. Pour cela, on introduit un état MOURANT : les processus mourants ne peuvent plus être actifs et seront libérés au prochain appel à la fonction d'ordonnancement.
4) Filiation
Les processus sont reliés entre eux par des liens filiaux. Chaque processus connait son père ainsi que la liste de ses fils.
Un processus père peut attendre la terminaison de son fils grâce à un appel à la primitive waitpid. Il est donc nécessaire de pouvoir récupérer la valeur de terminaison du fils.
Pour cela, lorsqu'un fils se termine, il devient zombie et on sauvegarde sa valeur de retour dans sa structure de processus.
Lorsque le père va faire un waitpid de ce fils zombie, il pourra lire sa valeur de terminaison avant de le libérer.
On ajoute un champ à la structure du processus précisant le pid bloquant, c'est à dire le pid du fils que le père attend. Cela permet à un fils qui se termine de réveiller son père en attente de sa terminaison.
Exemple en image :
Difficultés rencontrées : Cette phase n'est pas particulièrement complexe, mais nécessite de bien comprendre le comportement attendu des primitives à implémenter.
Conseils : On peut commencer par implémenter et tester les trois premiers points avant de se lancer dans la filiation.
Il est également conseillé de vérifier que votre implémentation de la filiation est cohérente avec celle décrite dans les spécifications : pas d'appels système sur un processus zombie, etc.
L'endormissement des processus ne comporte pas de grandes difficultés : les processus sont stockés dans une file de priorité ordonnée en fonction de leur heure de réveil.
La plus grande partie de cette phase concerne l'implémentation des files de messages. Une file de message est comme une boite aux lettres dans laquelle un processus peut déposer ou récupérer un message.
L'intérêt des files de messages est la possibilité de synchronisation entre processus : on peut notamment exploiter le fait qu'un processus lisant dans une boite vide se retrouve bloqué jusqu'à ce qu'un autre processus dépose un message et de même si un processus veut déposer un message dans une boîte pleine.
Les files de messages constituent également un premier moyen de communication entre processus.
Difficultés rencontrées : La synchronisation des échanges dans une file mérite qu'on y prête attention :
En effet, si un processus est bloqué sur une file vide en attente d'un message, le prochain message reçu doit lui être transmis directement avant d'être débloqué.
De même, si un processus est bloqué sur une file pleine en attente d'un emplacement où écrire, c'est le premier processus qui libère un emplacement dans la file qui va y écrire le message à sa place.
On utilise la structure des processus pour stocker les informations nécessaires à cette synchronisation.
Conseil : A la fin de cette phase, il est primordial de vérifier que tous les tests fournis pouvant être exécutés à ce stade ont bien le comportement attendu.
Cette phase est la plus longue (en temps, pas en quantité de code) et la plus difficile du projet. Elle nécessite une bonne compréhension de ce qui est attendu.
Il s'agit en effet de mettre en place deux notions fondamentales d'un système d'exploitation : la séparation mémoire et la mémoire virtuelle.
Nous avons suivi l'ordre suivant pour implémenter cette phase :
* Mise en place de la pagination :
Création d'un allocateur mémoire sous la forme d'une liste chaînée de pages vides.
Chaque page fait 4Ko et l'allocateur mémoire travaille sur tout l'espace de mémoire physique libre (entre 64MB et 256MB). 8
Difficultés rencontrées :
* La table de hashage semblait bien initialisée à la création mais ne l'était plus lors du premier passage en mode utilisateur (on ne retrouvait pas le bon code utilisateur). Il s'agissait d'une erreur d'allocation.
Conseils :
* La table des pages et toutes les pages mappées dedans doivent être alignées sur 4Ko (utilisez votre allocateur !)
* Ne pas oublier de tenir compte du sens de la pile : le sommet de pile est l'adresse la plus haute de la pile. Nous vous conseillons de dessiner votre pile (plusieurs fois) !
Cette phase a pour but l'écriture d'un pilote de clavier. Cela consiste en fait à récupérer et à traiter les interruptions matérielles envoyées par le clavier au processeur qui délègue le traitement au système d'exploitation. Nous avons choisi d'utiliser un unique buffer de taille assez grande.
Difficultés rencontrées : Il n'y a pas particulièrement de difficulté dans cette partie, la notion d'interruption étant particulièrement bien acquise à ce stade. La gestion du buffer devient plus délicate si on décide de gérer les flèches gauches/droites (dans le but de faciliter l'édition de commande pour le shell).
Conseil : Faire une implémentation du pilote aussi simple que possible pour pouvoir ensuite l'améliorer de façon cohérente avec l'implémentation et les besoins du shell.
Un interpréteur de commande ne fait pas vraiment partie du noyau. Il est plus à voir comme un processus utilisateur permettant d'interagir avec le noyau. Néanmoins cette partie est justifiée par le fait qu'un interpréteur de commande permet d'utiliser et de mettre en évidence le comportement du système de façon interactive : le noyau ne se contente plus d'exécuter du code pré-défini mais il fait ce que l'utilisateur lui demande.
Voici une capture d'écran après avoir lancé la commande help (commande générale d'information sur les commandes gérées par le shell) :
thumb|upright=4|center|Commandes disponibles sur l'OS Sys'thematic
Voici une capture d'écran présentant le résultat d'une commande ps après quelques actions sur notre shell. thumb|upright=4|center|Commande ps
Difficultés rencontrées : La principale difficulté était la gestion de l'echo (pour ne pas réécrire sur le prompt) et de l'effacement (pour ne pas pouvoir effacer le prompt du shell par exemple).
Conseil : Dans un premier temps, se focaliser sur un shell de base sans extension. Il peut être intéressant de doter le shell d'un historique de commandes pour rendre plus agréable l'édition de commande.
Difficultés :
La gestion des flèches directionnelles nous a pris un peu de temps et de très nombreux tests avant d'être opérationnelle.
Limites :
Nous avons choisi d'implémenter notre propre système de fichier dans la RAM. Cela évite de devoir se plonger une nouvelle fois dans une documentation complexe pour reprendre un système de fichier existant et cela évite également d'avoir à gérer les interactions avec un vrai disque. En contrepartie, notre système de fichier n'est pas persistant (il est réinitialisé à chaque fois que l'on éteint ou que l'on redémarre le système). Il permet en revanche de simuler la gestion de fichiers comme sur un système Linux, à l'exception de la gestion des répertoires et des droits des fichiers. Nous avons fait ce choix en raison du temps limité qu'il nous restait pour implémenter le système de fichiers.
Nous avons choisi de limiter le nombre maximal de fichiers à 1022 et la somme des tailles de tous les fichiers ne peut être supérieure à 64 Mo.
Ces contraintes étant respectées, la taille d'un fichier peut être quelconque.
Voici une liste des commandes shell relatives à la gestion de fichier :
thumb|upright=4|center|Commandes disponibles sur l'OS Sys'thematic
Puis un exemple de manipulation de fichier :
thumb|upright=4|center|Manipulation d'un fichier
Nous avons créé un petit éditeur de fichier qui permet d'ajouter plusieurs lignes à la fin d'un fichier (il crée également le fichier s'il n'existe pas), en voici un exemple d'utilisation :