Projet système PC : 2014 - BRELOT Lucie, CHAUZI Jordi, MALAISE Pierre

De Ensiwiki
Aller à : navigation, rechercher
TacOS
Taco bg.png
Projet Système d'Exploitation 2014

Développeurs Lucie BRELOT
Jordi CHAUZI
Pierre MALAISE

Présentation

Vous pouvez tester notre système grâce au binaire suivant (pour l'utiliser, aidez-vous de la page Projet système : environnement) :

Fichier:TacOS.tar.gz

Objectifs

L'objectif final du projet est de réaliser un système minimaliste en appliquant les cours reçus au cours de la deuxième année à l'ensimage ( Pratique du système PSE 4MMPS, Conception de Système d'Exploitation CSE Cours CSE , Systèmes d'Exploitation et Programmation Concurrente SEPC Systèmes d'exploitation et programmation concurrente .

Le système doit réaliser au minimum les fonctionnalités suivantes :

- Gestion de l'écran et du clavier.

- Interactivité avec l'OS par une console.

- Séparation entre le noyau et l'utilisateur.

- Multi-tâches.

- Synchronisation de processus.

Environnement

Le système a été réalisé en C, et pour certaines parties en assembleur, dans un environnement UNIX et testé en émulation à l'aide de qemu et bochs.

Equipe

Brelot Lucie : filière SLE (Systèmes et Logiciels Embarqués) Lucie BRELOT

Chauzi Jordi : filière SLE (Systèmes et Logiciels Embarqués) Jordi CHAUZI

Malaise Pierre : filière TELECOM Pierre MALAISE

Gestion de projet

Même si le projet était très guidé, nous avons pris du temps pour nous organiser. Nous avons définis que nous travaillerons tous ensemble tous les jours de 9h à 17h à l'Ensimag. Nous avons très peu travaillé le week-end.

Nous avons réalisé un planning prévisionnel à l'aide de l'outil planner, afin de nous donner une idée de notre avancement et de définir plus précisément les tâches à réaliser. Nous avons été la plupart du temps en avance par rapport au planning prévisionnel, en partie parce que le planning prévisionnel a été défini de façon pessimiste.

Afin de nous mettre d'accord sur les tâches réalisés par chacun d'entre nous, nous avons utilisé l'outil [Trello https://trello.com ], qui est un outil de gestion de ToDo List facile d'utilisation et très intéressant au point de vue des fonctionnalités.


Réalisation de l'OS

L'implémentation des différentes fonctionnalités a été réalisée en suivant les différentes phases spécifiées Projet système : roadmap .

Gestion de l'écran

Cette partie a été rapidement réalisée en reprenant le code développé en Pratique du Système 4MMPS Ecran . L'écran est composé de 25 lignes de 80 caractères, l'écriture d'un caractère est réalisée en écrivant directement dans la mémoire vidéo.

Nous avons implémenté la primitive console_putbytes, utilisée par printf afin d'afficher du texte à l'écran.

Création de processus et ordonnancement

Dans cette partie, nous gérons la création de processus "kernel" et de les ordonnancer, c'est-à-dire nous pouvons avoir plusieurs tâches au même moment, qui s'éxecute les unes après les autres dans un quantum (une certaine quantité) de temps.

Dans un deuxième temps, nous avons mis en place des processus crées dynamiquement, c'est-à-dire, dans un processus en lui-même. Nous avons alors dû gérer la filiation entre processus et revoir la terminaison d'un processus.

File de message

Afin de faire communiquer les processus entre eux, nous avons implémenté des files de messages fonctionnant par le principe du producteur consommateur [1].

Une file de message est constituée d'un tampon de messages et d'une liste de processus bloqués sur la file. En effet, si le tampon est plein au moment où processus veut déposer un message, il est bloqué, de même si le processus veut retirer un message d'une file vide.

Séparation noyau/utilisateur

Auparavant, les programmes de tests étaient directement inclus dans le noyau. Dans cette phase, nous allons séparer tous les programmes utilisateurs du noyau afin de pouvoir mettre en place des protections. Chaque processus utilisateur possède son propre espace d'adressage et ne peux accéder à celui des autres.

Mémoire virtuelle

Nous avons mis en place la mémoire virtuelle qui permet de faire croire aux processus que leur espace d'adressage est beaucoup plus grand que celui définit par la mémoire physique. En effet, le système possède une mémoire physique de 256 Mo mais les processus voient un espace de 4 Go.

La mémoire virtuelle permet d'isoler la mémoire de chaque processus. Chaque adresse physique est associée à une adresse linéaire, les associations étant stockées dans une structure spécifique à chaque processus. A la création d'un processus, on crée cette structure et on y mappe la zone mémoire du noyau (avec physique = linéaire, car une fois la pagination activée, il est impossible de retrouver une adresse utilisée préalablement sans l'avoir mappée). Dès que l'on alloue une page physique dans la suite, il faut la mapper dans la table des pages. Des conventions on été prises pour les adresses de début du code (0x40000000), de la pile (0x80000000) et de la zone de mémoire partagée (0xC0000000). Il peut être également difficile de passer des arguments entre processus (leur adresse étant liée au processus actif), il est possible de les copier au passage dans le noyau pour s'affranchir de ce problème.

Passage en mode utilisateur

Le passage en mode utilisateur n'est pas très difficile à mettre en place mais plus compliqué à bien comprendre. Afin de faire changer de mode le processeur, il faut lui faire croire qu'il revient d'une interruption et place dans la pile noyau les éléments dépilés par iret. Cette astuce est à réaliser au lancement du premier processus, par la suite les interruptions vont empiler ce qu'il faut dans la pile.

Appels systèmes

Une fois la séparation noyau/utilisateur réalisée, une application utilisateur ne peut plus utiliser des fonctions définies dans le noyau, comme console_putbytes par exemple. Il faut alors mettre en place des appels systèmes.

Un appel système est un appel réalisé dans l'espace utilisateur qui va seulement placé les paramètres de l'appel et un numéro identifiant l'appel dans des registres. Puis il va effectuer une interruption 49, interruption programmée, qui va être traiter en noyau.

Dans le noyau, le traitant de l'interruption 49 va appeler une fonction C qui va réaliser l'appel de la bonne fonction identifiée par le numéro.

Gestion du clavier

La frappe au clavier est gérée par interruptions. La touche frappée est récupérer et traduite en caractère.

Des primitives systèmes ont été implémentées afin de récupérer les textes tapés au clavier dans un buffer pouvant être transmis à un processus. Projet_système_:_spécification#Le_clavier Ces primitives sont indispensables pour créer l'interpréteur de commandes. En effet, le processus interpréteur est en attente de texte.

Interpréteur de commandes

L'interpréteur de commandes permet de lancer des programmes utilisateurs :

Commande Description

help

Affiche l'ensemble des commandes disponibles dans le Shell

kill

Tue un processus suivant son PID

echo

Active/desactive l'echo

clear

Efface l'écran et fait réapparaitre le prompt

sleep

Endort le shell pour une durée donnée en paramètre

autotest

Lance tous les tests utilisateurs

pinfo

Affiche des informations sur les files de messages

ps

Affiche des informations sur les processus

Extensions

Gestion de la souris

Nous avons implémenté une souris PS/2. Nous nous sommes aidés essentiellement des informations disponibles sur OSdev Contrôleur PS/2, PS/2 Mouse et Mouse Input.

La souris et le clavier sont tous les deux sur le port ps/2, le clavier étant sur le premier port, la souris sur le deuxième. Il faut donc indiquer au contrôleur que l'on veut envoyer les données sur le second port avant d'envoyer effectivement les données.

L'interruption souris est l'IRQ12, cette IRQ intervient sur le PIC 2, slave qui est elle cablée sur l'IRQ2 du PIC 1. Il faut donc démasquer l'IRQ 2 et l'IRQ 12.

Pour la souris, en mode texte, nous affichons le caractère ^. Les boutons gauches et droits permettent de changer soit la couleur du fond, soit la couleur du "curseur".

Real-Time Clock

Le RTC (Real Time Clock) permet d'obtenir une notion de date et heure non liée au lancement du noyau. Il se trouve sur la même puce que le CMOS, petite mémoire statique avec laquelle on va dialoguer pour obtenir l'année, mois, jour, heure, minute et seconde courants. Ceci se fait à travers les ports 0x70 (commande) et 0x71 (donnée). Chacune des informations précédentes se trouve dans un registre du CMOS qu'il faut alors lire pour obtenir une date correcte.


Plusieurs choses sont à noter :

- La mise à jour des registres peut-être relativement longue : à chaque seconde le registre correspondant à la seconde courante est incrémenté, puis si la valeur dans le registre est alors de 60, le registre prend la valeur 0 et le registre des minutes est incrémenté (et ainsi de suite...). Il est ainsi possible d'obtenir des valeurs incorrectes si l'on regarde les registres durant une mise-à-jour. Il existe cependant un flag de mise-à-jour en cours permettant de limiter les erreurs. Il reste encore certains problèmes de cohérence à gérer, assez facilement.

- Le format des valeurs dans les registres n'est pas nécessairement celui attendu (où 42 secondes serait codé par 0x2A) : il existe deux modes, le précédent et un mode BCD (où 42 secondes est codé par 0x42 ...). Cette information est contenue dans un registre d'état et doit être utilisée pour pouvoir convertir si besoin les valeurs obtenues.

- L'heure peut se trouver sous forme 24h ou 12h : le même registre d'état contient cette information. Il faut là encore convertir si besoin la valeur lue.

- L'année est une valeur entre 00 et 99 : il existe un registre siècle sur certains CMOS, il est donc possible si besoin de vérifier si ce registre est utilisable est de lire la valeur pour obtenir une valeur pour l'année sur 4 chiffres.


Nous avons choisi de mettre à jour la valeur de la date à chaque nouvelle seconde de notre interruption horloge. Il serait également possible de gérer les interruptions envoyées par le RTC pour savoir quand regarder les registres.


De nombreuses informations peuvent être trouvées sur :

- OsDev CMOS

- OsDev RTC

Lazy-loading

Le principe du lazy-loading est de n'allouer une ressource que lorsque celle-ci est nécessaire. Ceci permet de retarder et de séparer des opérations lourdes d'allocation et donc de rendre relativement plus rapide le lancement d'une application (on devra cependant perdre du temps durant l'exécution pour effectivement allouer les ressources nécessaires). Dans le cas d'un système d'exploitation, ceci peut être utilisé pour n'allouer les pages physiques que lorsque l'application écrit à ces adresses (pour des données) ou lit à ces adresses (pour le code).


Pour implanter cette fonctionnalité, nous avons modifé notre gestionnaire de défaut de page : lorsqu'une interruption 0x0E (page fault) est lancée, le registre cr2 est aussi chargé avec l'adresse qui a provoqué le défaut de page et un code d'erreur est placé sur la pile. En lisant ce code d'erreur et en vérifiant dans quelle zone se trouve l'adresse dans cr2 (données, code, données partagées, noyau, autre), il est possible de déterminer quand allouer une nouvelle page physique et quand tuer un processus fautif.


Pour les données, les lectures avant la première écriture ne doivent pas générer de défaut de page : on peut pour cela mapper les adresses des données vers une page physique remplie entièrement de 0 et munie d'une permission "Lecture seulement". La première écriture générera un défaut de page qu'il faut alors rattraper.

Pour le code, on peut simplement stocker ses adresses limites et ne pas mapper de pages à ces adresses : un défaut de page à ces adresses correspondra donc à un besoin de charger du code.

Affichage VESA

Nous voulions donner à notre OS la possibilité d'un affichage graphique. Nous avons opté pour le VESA. Pour bien faire nous avons suivi la démarche expliquée dans la doc constructeur et sur OSDEV. Il est nécessaire d'implémenter une fonction qui récupère les informations du pilote VESA. Ensuite, nous avons utilisé une fonction qui récupère les informations propre au mode d'affichage disponible et donné en paramètre. Une fois le bon mode trouvé, il suffit de le lancer. Nous avons enfin pu grâce à des nouvelles fonctions afficher des pixels, puis des fenêtres et enfin du texte.
Vesa.png

Apports et difficultés

Le projet système nous a permis d'appliquer et d'approfondir nos connaissances concernant les systèmes d'exploitation. Il s'agit d'un très bon projet de synthèse des matières CSE, SEPC, PSE et Archi avancée.

De plus, il est vraiment intéressant de programmer à un niveau beaucoup plus bas et de comprendre les bases d'un système. Le monde de l'ordinateur paraît beaucoup moins abstrait et magique. Si notre système d'exploitation fonctionne de tel manière, nous pouvons comprendre pourquoi. Une compréhension plus bas niveau peut permettre d'optimiser plus facilement le développement plus haut niveau.

Par ailleurs, même si le marché des systèmes d'exploitation pour ordinateur est plutôt fermé, il y a encore un peu de places pour les systèmes pour smartphones ou tablettes. De plus, ces systèmes sont plus délicats à concevoir de par les fortes contraintes du matériel.

La phase la plus difficile du projet est la séparation noyau/utilisateur car il faut vraiment avoir compris le principe avant de pouvoir implémenter. Il est également difficile de réunir toutes les documentations et de les recouper pour trouver la bonne information. Nous avons surtout remarqué cela sur la phase d'implémentation des extensions.

Conseils

Comme conseils aux futurs développeurs de système d'exploitation nous ne pouvons que conseiller de bien approfondir la documentation, de ne pas hésiter à faire des petits dessins de piles et à ne pas hésiter à rechercher sur OSDev.

Nous vous conseillons également de beaucoup utiliser la méthode du canard en plastique Wikipedia.

Pour la phase de séparation kernel/user, il est important d'y aller étape par étape et de bien tester le noyau avant de se lancer. Les modifications à apporter ne sont pas très importantes mais peuvent très vite mener à des erreurs.

La page suivante est également très utile :

- Liste des exceptions Intel