Projet système PC : 2018 - BONNAVENTURE William, BOUVIER Pierre, PODLEWSKI Alexandre, SCHAMBER Loic, SUONG Anthony, TURLURE Maxime

De Ensiwiki
Aller à : navigation, rechercher
PotatOS
Screenshot PotatOS.png
Projet Système d'Exploitation 2018

Développeurs BONNAVENTURE William
BOUVIER Pierre
PODLEWSKI Alexandre
SCHAMBER Loic
SUONG Anthony
TURLURE Maxime

Présentation

Le système d'exploitation PotatOS inclus les mécanismes de base d'un noyau de système d'exploitation tels que le lancement de processus, la gestion de la filiation entre processus, la mémoire partagée, etc. Des extensions ont été ajoutées à ce noyau tel qu'un mode VGA permettant un affichage en résolution 640x480 pixels 1 octet par couleur RGB.

                    #########
              #########   ######### 
           ###                     ###
         ###                         ###
       ###                             ##
       ##                               ##
      #                                  ##
     ##                                  ## 
    ##         ####                ####  ##
   ##         ####m#              #m#### ##
   ##          ####     ########   ####   ##
  ##                    ##   ##           ##
  ##                      ###              ##
 ##                                        ##
 ##                                         ##
##                                          ##
##                                           #
##                                           ##
##                                            ##
##                                            ##
##                                            # 
 ##                                          ## 
 ##                                         ###
  ##                                       ###
   ###                                   ###
     #####                            ####
         ##############################
                   #####

Architecture Mémoire

Organisation de la mémoire Kernel

----------------------- 0x0
EMPTY
----------------------- 0x1000 (4ko)
IDT
----------------------- 0x10000 (64ko)
GDT
----------------------- 0x20000 (128ko)
TSS
----------------------- 0xA0000 (640ko)
VIDEO BANK
----------------------- 0x100000 (1Mo)
KERNEL
----------------------- mem_heap
KERNEL HEAP
----------------------- 0x4000000 (64Mo)
FREE MEMORY
----------------------- 0xFF1E000 (255Mo)
VGA Linear Frame Buffer
----------------------- 0x10000000 (256Mo)

Organisation de la mémoire User

-------------- 0x0
SAME AS KERNEL
-------------- 0x10000000 (256Mo)
FREE MEMORY
-------------- 0x30000000 (~800Mo)
SHARED MEMORY
-------------- 0x3C000000 (~1Go)
FREE MEMORY
-------------- 0x40000000 (1Go)
PROGRAM CODE
-------------- end_program
FREE MEMORY
-------------- process->stack
PROGRAM STACK
-------------- 0xFFFFFFFF (4Go - 1)

Avancement et difficultés

Phase 1 :
100 %
Phase 2 :
100 %
Phase 3 :
100 %
Phase 4 :
100 %
Phase 5 :
100 %
Phase 6 :
100 %
Phase 7 :
100 %

Extensions

Pilote BGA (Bochs Graphics Adaptater)

Par défaut l'affichage se lance en mode textuel 80x25 blocs et fonctionne sur tous les systèmes.

VBE (VESA Bios Extension) banked mode

Si une carte graphique compatible est détectée (du type BGA), alors le kernel active le mode VGA 640x480 pixels en 24bits par pixel. Par défaut cette activation se fait en banked mode. Le banked mode consiste à utiliser une zone mémoire restreinte pour récupérer ou mettre des valeurs aux pixels. Ainsi il faut parfois changer de banque (ie changer de zone de l'image) afin d'accéder à un certain pixel, ce qui a un coup.

Représentation très abstraite du banked mode :

----------------
|    |    |    |
|    |    |    |
----------------
|    |    |    |
|    |    |    |
----------------

Le schéma représente une image 2D découpée en blocs. On peut associer ces blocs à des banques de pixels. Si on passe d'une zone à l'autre, on doit changer de banque. Ceci est obligatoire à cause de la zone mémoire restreinte dédiée à la banque courante.

VBE LFB (Linear Frame Buffer) mode

De manière automatique le driver BGA va chercher parmi les périphériques PCI (grâce au pilote PCI) si la carte graphique y apparaît. Si la carte est trouvée, alors le driver BGA demande à la carte graphique via le port PCI l'adresse de mappage physique du Linear Frame Buffer. Ce buffer linéaire permet alors d'avoir de plus grandes performances en écrivant directement dans une zone mémoire dédiée à l'affichage VGA (926 pages de 4ko). On peut reprendre l'exemple du banked mode, mais où ici nous aurions uniquement une seule banque en mémoire et de taille 640x480x24 bits.

Souris

La souris en mode PS/2 est supportée au sein du noyau. Un affichage temps-réel en mode graphique est possible via le mode VGA.

Le système de fichier PTFS

PTFS est un système de fichier fonctionnant sur des blocs de 1 Kio et est capable de supporter jusqu'à 64 Mio de stockage. PTFS possède une liste des blocs libres et utilise l'algorithme du buddy afin de la gérer.

De plus, PTFS fonctionne à l'aide des inodes. Afin de stocker leur localisation, une table des inodes est utilisée.

Voici le schéma du disque dur:

================================================================================
|  super  |  inode  | ... |  inode  |  data   |  data   |         ...          |
|  block  | table 1 | ... | table N | block 0 | block 1 |         ...          |
================================================================================

Le super bloc (le bloc 0)

Ce bloc est réservé et à utilisation spéciale. Il ne peut pas être utilisé pour stocker les données de l'utilisateur. Le super bloc contient dans l'ordre:

  • Les paramètres du système de fichier (sur 2 octets)
  • La table des listes des blocs libres (sur 32 octets)
  • La première table des inodes (sur 988 octets)

Le super bloc est toujours considéré comme occupé et ne peut donc pas être utilisé comme un bloc standard. Notre système de fichier utilise le chaînage des blocs pour stocker les grands fichiers. Le numéro 0 signifie alors que la liste est terminée.

Les paramètres

=================================================
|      N : u16     | I : u8 |  Magic Number: u8 |
=================================================

À la création du disque, l'utilisateur peut définir:

  • Le nombre de bloc(s) utilisés pour créer le système de fichier: 0 < N < 65536
  • Le nombre de bloc(s) réservés pour la table des inodes 0 <= I < N
  • Un Magic Number qui vaut toujours 0x5A et qui permet de vérifier l'existence du système de fichiers sur le disque.

Cela forme un entier de 32 bits.

La table des listes des blocs libres

Nous utilisons l'algorithme du compagnon pour gérer les blocs libres.

Pour chaque taille de bloc possible, une liste chaînée est disponible pour stocker les blocs libres de cette taille. Pour faciliter l'implémentation de l'algorithme du compagnon, cette liste doit être triée.

Les blocs qui sont en tête de liste sont stockées dans la table des listes:

================================================================================
| Taille | Taille | Taille |          ...           | Taille | Taille | Taille |
|  2^0   |  2^1   |  2^2   |          ...           |  2^13  |  2^14  |  2^15  |
================================================================================
NB: Comme le bloc 0 n'est jamais libre, il ne peut pas y avoir de bloc ayant pour taille 2^16.

Les cellules de chaque liste sont stockées dans les blocs libres et possèdent cette structure:

======================================
| next_block : u16 |    size : u16   |
======================================
NB: Si next_block vaut 0, cela veut dire que la liste est terminée.

Lors de la création du système de fichier, les N - I blocs disponibles pour les données sont marqués libérés.

La première table des inodes

La table des inodes fait la correspondance entre le numéro de l'inode et le numéro du bloc. Pour des raisons d'économies d'espace, le début de la table des inodes est contenu dans le super bloc. Le principe des inodes et leur fonctionnement sera décrit dans la section suivante.

Les inodes

Principe de fonctionnement des inodes

Un inode stocke les métadonnées des fichiers. Cela permet d'implémenter plus facilement des fonctionnalités avancées telles que les liens symboliques.

Chaque inode est écrit au début d'un bloc (l'inverse n'est pas vrai). Une fois l'inode terminé, ce dernier contient le contenu du fichier.

Ici, nous avons simplifiés la structure des inodes de POSIX, car notre système d'exploitation ne gère pas toutes les fonctionalités d'un UNIX:

typedef struct inode {
    uint16_t next;
    uint16_t inode_number;
    uint32_t size;
    uint32_t ctime;
    uint8_t flag;
    uint8_t reference_counter;
} inode;

Une petite description des champs:

   - inode_number stocke le numéro d'inode et facilite grandement l'implémentation du système de fichier;
   - next est non-nul si le bloc ne peut contenir tout le fichier (voir La gestion des grands fichiers);
   - size contient la longueur en octet du fichier;
   - ctime est la date de création de l'inode;
   - flag renseigne sur le type de fichier:
       - 1 si c'est un fichier régulier
       - 2 si c'est un dossier
       - 4 si c'est un lien symbolique
       - 8 si le fichier est verrouillé en lecture / écriture
   - reference_counter est un compteur de référence et est utilisé pour les hard links

La table des inodes

La table des inodes stocke la correspondance entre un inode et son bloc associé: Table[n°inode] == n°bloc:

================================================================================
| Inode | ... | Inode | Inode | ... | Inode | ...  |    Inode        |   ...   |
|   0   | ... |  493  |  494  | ... |  1005 | ...  | 512.(I-1) + 494 |   ...   |
================================================================================
Bloc 0 ---------------| Bloc 1 (optionnel) -| ...  | bloc I (optionnel) -------|

Il faut donc 2 octets à stocker par inode. Le super bloc contient au plus 494 inodes. Ce qui nous fait au plus 494 fichiers.

Pour outrepasser cette limitation, l'utilisateur peut décider de réserver I blocs supplémentaires pour stocker la table des inodes. Chaque nouvelle table rajoute 512 inodes supplémentaires à la suite des précédents inodes.

L'inode 0

Une chose importante à noter est que l'inode 0 est la racine du système de fichier. L'inode 0 doit être un dossier contenant "/". Toute implémentation acceptant un autre type de fichier pour l'inode 0 sera considérée comme invalide. Contrairement aux autres inodes, le champ ctime de l'inode 0 représente le temps de la dernière création de fichier au sein du système de fichier.

La gestion des grands fichiers

Si un fichier fait plus de 1010 octets (un inode fait 14 octets), alors on ne peut pas le stocker sur un seul bloc. Les blocs sont chaînés par le champ next, si ce dernier est non-nul. Les futurs blocs n'auront pas la structure d'un inode mais celle-ci:

================================================================================
|  next_block : u16  |                    content of file           
================================================================================

Pour être conforme, une implémentation du système du fichier ne doit allouer que le nombre de bloc nécessaire au stockage du fichier. Par conséquent, la fin du fichier doit être présente sur le dernier bloc alloué.

La gestion des dossiers

Un dossier est un fichier spécial, où son contenu est structuré. Dans notre système de fichier, chaque fichier est un couple (numéro de l'inode, nom de fichier). Nous avons choisi d'aligner ces couples sur un espace de 8 octets, la longueur maximale des noms de fichiers est donc de 6 caractères.

Bien évidemment, un dossier peut être un grand fichier.

La gestion des liens symboliques

Le contenu du fichier est simplement le chemin (relatif ou absolu) de l'autre fichier

Les chemins

Description d'un nom de fichier valide

Un nom de fichier valide est composé de 1 à 6 caractères, tous les caractères ASCII sont autorisés à l'exception de "/" et de "\0". À l'instar d'un système POSIX, les ".." et "." sont réservés. Un nom de fichier sera appelé par la suite "composant".

Description d'un chemin valide

Un chemin PTFS est un chemin POSIX, avec une limitation sur les composants comme décrit en section précédente.

Chargement dynamique avec page fault

  • Par défaut seule la première page du programme et la première page de la pile est chargée en mémoire lors de la création d'un processus.

Plusieurs types de fautes de page sont volontairement engendrées afin de charger dynamiquement un programme en mémoire :

  • L'accès à une zone du programme censée être valide engendre le chargement de la page associée.
  • L'accès à une zone de la pile censée être dans les limites définie engendre une allocation de page pour cette portion de pile.
  • Dans les autres cas, le page fault est considéré comme une véritable faute, et le processus est tué.

Carte son

Les cartes sons compatibles Sound Blaster 16 sont gérés avec un pilote très minimaliste.

L'initialisation de la carte son étant faite au démarrage de kernel, l'utilisateur peut communiquer avec la carte son principalement via les appels systèmes read_ADC pour l'entrée son, et write_DAC pour la sortie son.

Le son est géré sur un seul canal (donc en mono), sur 8 bits en non relatif (le son est donc absolument sur 0-255). Aucune gestion des timing ni interruption son n'est implémenté, il faudra donc gérer au niveau de l'application les données envoyés au/récupérés du buffer son via write_DAC/read_ADC, ainsi que le timing des appels.

Pour faciliter la démonstration du son, un appel système play_note a été ajouté en plus de la gestion primitive du son. Elle permet, au lieu d'envoyer des données son brutes à la carte, de sélectionner la note qui sera joué. Cette fonction permet de jouer des notes justes sur trois octaves (tenter de jouer une note de plus de trois octaves plus haut que la gamme de base entraînera des grosses pertes de précision sur la note jouée). Les notes sont représentées par des entiers, 0 étant DO-1, 11 étant SI-1, 12 étant DO-2 etc...

Pilote IDE

Le pilote IDE est nommé en raison de son interaction avec le contrôleur IDE de la machine.

Lors du lancement du kernel, le pilote IDE récupère via le pilote PCI, n'importe quel périphérique compatible avec les commandes IDE (ie: pciClass = 0x1, pciSubClass = 0x1). Il peut ainsi lister au démarrage les différents périphériques détectés.

En plus de récupérer une liste des périphériques connectés au contrôleur IDE, le pilote IDE permet de lire et écrire les secteurs d'un disque dur ATA.

IDE, RAM et PTFS

Par défaut le kernel alloue un espace mémoire de 32Mo qui est formaté avec le système de fichier PTFS et donc se comporte comme un disque dur.

Toutefois, le pilote IDE va essayer de récupérer un disque dur ATA de taille <= 64Mo. Si un tel disque est connecté, alors vous pourrez le monter à la place du disque dur en RAM. Pour des raisons de sécurité, un disque ATA de taille > 64Mo ne sera ni monté, ni formaté, afin d'éviter de détruire des données importantes sur des machines physiques (si jamais vous lancez le kernel sur une machine physique).

mount ATA
format ATA 32768 16

Une fois qu'un disque est formaté, il n'est pas nécessaire de le reformater, car le système de fichier va détecter si le disque présente des identifiants de PTFS (notamment un magic number qui permet cette détection).

Ainsi, vu que la mémoire est déjà formatée au démarrage, vous pouvez simplement basculer d'un disque à l'autre. Un exemple de création de fichier sur un disque puis de basculement de disque :

mount MEM
touch /a
ls
mount ATA
touch /b
ls
mount MEM
ls

Les points de montage ne sont pas supportés au sein de PTFS par le kernel. Donc, lorsque vous faites la commande mount, c'est toute l'arborescence (y compris la racine) qui est remplacée.

De plus, pour améliorer les performances, une page mémoire de 4ko est réservée comme buffer intermédiaire entre les fonctions d'écriture/lecture et le disque. Comme un bloc de PTFS fait 1024o, et qu'un secteur de disque dur fait 512o, nous pouvons avoir 4 blocs de PTFS au sein de la même page. Ainsi ces buffers sont remplacés à tour de rôle en suivant la technique simple d'un Round Robin (1, 2, 3, 0, 1, 2, 3, 0, ...). Lorsqu'un bloc doit être évincé du buffer, il est automatiquement écrit sur 2 secteurs consécutifs sur le disque.

Pilote PCI

Chaque périphérique PCI est principalement identifié par : un numéro de bus, un numéro de périphérique, un identifiant du vendeur (AMD, Intel, ...), un identifiant du produit, un numéro de classe du produit et un numéro de sous-classe de produit.

Lors du lancement du kernel, les pilotes BGA et IDE peuvent utiliser le pilote PCI afin d'avoir des informations sur les périphériques qui les intéressent.

L'utilisation courante de ce pilote se fait via la commande lspci qui engendre un rafraîchissement et un affichage de la liste des périphériques connectés en PCI.