Introduction
Préambule
Ce document n’est constitué pour le moment que de quelques notes que j’ai pu prendre en essayant de réaliser le début d’un truc qui pourrait vaguement laisser penser que j’essaye de réaliser un noyau de système d’exploitation.
Ces notes me sont purement personelles et je vous invite donc, qui que vous soyez, arrêtez de lire ça ! Vous êtes encore là ? C’est pas sympa …
Présentation
Ce document rassemble quelques notes prises durant ma tentative de développement de ManuX (“Misérable Approximation de Noyau Un*X”). Mon but en développant cette chose est uniquement de mieux comprendre les problèmes liés à l’implantation d’un système d’exploitation. Je n’ai en aucun cas comme objectif d’arriver à un os utilisable à d’autres fins que tester des mécanismes systèmes.
De plus, je ne recherche absolument pas la performance dans le code que j’écris, mais plutôt la lisibilité et la compréhensibilité. Ça peut ne pas sauter aux yeux dans les parties de code encore en développement (c’est à dire partout pour le moment) …
Organisation générale
Comme tout système digne de ce nom, ManuX-32 définit un mode utilisateur et un mode noyau. Il n’utilise pour l’instant pas les différents modes fournis par le processeur, mais j’ai bon espoir pour que cela arrive bientôt.
Structure du code
Le code de ManuX est réparti dans quelques fichiers, eux-mêmes organisés dans quelques répertoires.
Au plus haut niveau, nous allons distinguer deux grandes parties que sont le “code noyau” et le “code utilisateur”.
Le code “utilisateur”
Le code du monde utilisateur est confiné dans le répertoire usr. Les
principaux fichiers de usr sont les suivants
unistd.c
implante les principales fonctions (décrites dans include/unistd.h)
permettant l’accès aux fonctions du noyau. Comme nous le verrons, ce
sont donc surtout des appels systèmes.
stdio.c
implante les principales fonctions (décrites dans include/stdio.h)
permettant de réaliser des entrées/sorties.
init.c
implante le code de la première tâche lancée automatiquement par le
noyau lorsqu’il est initialisé.
La compilation du code utilisateur nécessite naturellement des fichiers
inclus. Ils se trouvent dans le répertoire usr/include. Certains des
fichiers, situés dans le répertoire include/manux sont en fait des
copies depuis l’arborescence du noyau. Ils sont mis à jour au travers de
la cible usrinc du Makefile principal. Il s’agit de
config.h
qui décrit la configuration du noyau, il est donc important de pouvoir
en disposer dans le mode utilisateur afin de savoir ce qui est
disponible ;
types.h
qui définit les types utilisés dans le noyau ;
appelsystemenum.h
qui donne les numéros des appels systèmes ;
string.h
est là pour offir au mode utilisateur des fonctions présentes dans le
noyau (memcpy(), …). Il n’est pas nécessaire que ce soit le même, mais
cela évite de tenir à jour deux fichiers !
Le code du noyau proprement dit
Le gros de ce code est distribué dans un certain nombre de répertoires tels que :
noyau
contient les éléments de base constitutifs d’un noyau ;
lib
contient quant à lui du code un peu plus “générique” ;
i386
héberge le code spécifique à l’architecture matérielle Intel ;
outils
contient quelques outils annexes utilisés pour la construction du noyau
;
boot
est consacré à tout le processus de chargement du noyau, nous le
décrirons dans une section spécifique ;
sf
est le répertoire dans lequel se trouvent les fichiers liés à la
manipulation des fichiers ;
doc
rassemble le peu de documentation que j’arrive à vaguement tenir à jour.
Configuration
La configuration se fait essentiellement au travers du fichier
manux/config.h. Comme nous le verrons dans la section sur les outils,
il est possible de gérer plusieurs versions de ce fichier, de sorte à
pouvoir générer plusieurs images du noyau.
Certaines valeurs sont utilisées ailleurs que dans du C (dans du NASM,
ou directement dans le Makefile) et une cible make est là pour
générer un fichier make.conf qui contient ces variables. Pour qu’elle
fonctionne, il est impératif que ces macros débutent par MANUX\_
D’autres valeurs doivent être déterminées durant la phase de compilation
(par exemple la taille du noyau). Elles sont donc calculées par le
programme outils/taillenoyau et placées dans le fichier taille.conf.
Le fichier include/config.h
Il permet de choisir l’inclusion, ou non, de certines parties du noyau, et parfois de les paramétrer.
Configuration du fichier de boot
MANUX_TAILLE_PAGE
MANUX_NOMBRE_PAGES_SYSTEME
MANUX_ADRESSE_DEBUT_TAS
MANUX_NB_MAX_FICHIERS
MANUX_CONSOLES_VIRTUELLES
MANUX_JOURNAL
Configuration de l’adressage du noyau
MANUX_TAILLE_PAGE
MANUX_NOMBRE_PAGES_SYSTEME
MANUX_ADRESSE_DEBUT_TAS
Configuration de l’affichage
MANUX_CONSOLES_VIRTUELLES
MANUX_JOURNAL
Configuration du système de fichiers
NB_MAX_FICHIERS
Le fichier make.conf
Le fichier taille.conf
Organisation de ce document
Mon but est de décrire les choses de façon progressive, dans l’ordre dans lequel je les ai faites, et dans l’ordre dans lequel il me semble pertinent de les faire de sorte à construire progressivement un noyau qui fonctionne.
La section “Les outils de développement” décrit sommairement les outils nécessaires à la compilation, le débogage et le lancement de ManuX.
La section “Le boot” montre comment faire en sorte que notre code soit chargé en mémoire par le bios de la machine puis exécuté. C’est une partie assez technique, très spécifique au matériel ciblé, et peu intéressante. Je vous autorise à la passer. À la fin de cette partie, le code du noyau lui même commence à être exécuté, c’est donc là que les choses deviennent croustillantes ! Mais c’est également là que les ennuis commencent, …
Afin de pouvoir voir ce que nous faisons, une première activité va
consister à écrire du code pouvant afficher des choses à l’écran, nous
pourrons enfin avoir notre “Hello world !” du noyau. Pour cela, nous
allons créer une “console” et une fonction printk dans la section
“L’affichage”.
La section “Les appels systèmes” sera ensuite consacrée à mettre en place le mécanisme permettant aux applications de communiquer avec le noyau. Pour le moment, ce mécanisme n’est pas nécessaire, un simple appel de fonction permet cela, mais lorsque nous mettrons en place des mécanimes de protection, nous seronsbien contents d’avoir fait les choses proprement !
Dans la section “Un premier init”, nous alons écrire un premier
“code utilisateur”
Nous allons ensuite nous attaquer à un morceau assez conséquent : la gestion de la mémoire qui sera décrite dans la section “La gestion de la mémoire”. Nous ferons dans ManuX les choses aussi simplement que possible.