< Introduction | TutoOS | Réaliser un secteur de boot qui charge et exécute un noyau >
La programmation d'un noyau est difficile et ce tutoriel s'adresse en premier lieu aux programmeurs ayant déjà une bonne expérience de la programmation et une connaissance suffisante de la théorie des systèmes d'exploitation. L'ensemble des pré-requis nécessaires à la bonne compréhension de ce tutoriel est détaillé dans l'introduction. Si vous débutez en C, que vous ne connaissez pas l'assembleur ou bien que vous n'avez qu'une très faible idée des fonctionnalités d'un noyau, il vous sera donc très difficile de tout saisir. Quoiqu'il en soit, je vous souhaite une excellente lecture et une bonne compilation !
Un secteur de boot est un programme situé sur le premier secteur d'une unité de stockage, et qui est chargé et exécuté au démarrage du PC. Le programme de ce secteur a en principe pour tâche de charger un noyau en mémoire et de l'exécuter. Ce noyau peut être présent au depart sur une disquette, un disque dur, une bande ou tout autre support magnétique. Ce chapitre détaille ce qui se passe quand on boote sur disquette mais les principes expliqués ici restent valables pour tout autre support.
Au démarrage, le PC commence par initialiser et tester le processeur, la mémoire et les périphériques. C'est le Power On Self Test (POST).
Ensuite, le PC charge et exécute un programme particulier résidant en ROM, le BIOS ("Basic Input/Output System" ou "Built In Operating System"). Le BIOS peut être vu comme un système d'exploitation minimal chargé automatiquement par le PC et dont l'un des rôles est de charger un véritable système d'exploitation en essayant de trouver un secteur de boot valide parmi les unités de stockage. Une fois trouvé, ce secteur est chargé en memoire à l'adresse 0000:7C00
puis le BIOS passe la main au programme de boot fraichement chargé.
Le secteur de boot de l'unité de stockage est appelé Master Boot Record (MBR). Il contient des données, dont la table des partitions, et du code exécutable. La table des partitions contient des informations sur les partitions primaires du disque (où elles commencent, leur taille, etc.). Le code exécutable d'un MBR standard cherche sur la table des partitions une partition active, puis, si une telle partition existe, ce code charge le secteur de boot de cette partition (appelé aussi VBR). C'est souvent ce deuxième secteur de boot qui charge le noyau et lui donne la main.
Dans notre cas, nous allons pour commencer créer un secteur de boot très simple qui ne fera qu'afficher un message de bienvenue. Celà n'est pas bien difficile et pour vous épargner un long suspense, voici le programme :
On indique tout d'abord que l'on travaille sur 16 bits pour le codage des instructions et des données, c'est le mode par defaut. Ceci n'est pas encore le début du programme, c'est juste une directive de compilation pour que l'exécutable obtenu soit bien sur 16 bits et pas sur 32 bits.
Cette directive indique l'offset à ajouter à toutes les adresses référencées.
Le programme du secteur de boot est chargé par le BIOS en 0x7C00
, donc toutes les données internes au programme sont situées à partir de cette adresse. Le bloc ci-dessus initialise les registres ds
et es
qui servent à indiquer où débute le segment de données. En mettant la valeur 0x07C0
dans le sélecteur de données, on accède en fait à l'adresse 0x07C0:0000
= 0x07C00
.
L'adressage en mode réel est particulier. La valeur du sélecteur représente les 16 bits "hauts" d'une adresse linéaire sur 20 bits. Il faut donc multiplier par 0x10 la valeur du sélecteur pour obtenir la base. Par exemple, l'adresse A000:1234
en mode réel correspond à l'adresse physique A0000
+ 1234
= A1234
. Celà signifie aussi qu'un segment en mode réel occupe 64k, et pas un octet de plus !
Ensuite on initialise le segment de pile ss
et le pointeur de pile sp
en faisant commencer la pile en 0x8F000
et en la faisant finir en 0x80000
. Note : le choix de ces valeurs est arbitraire, dans le cas présent, la pile aurait pu être placée ailleurs.
À ce stade du programme :
0x07C00
0x07C00
à 0x17C00
0x8F000
et finit en 0x80000
Une fois les principaux registres initialisés, la fonction afficher
est appelée pour mettre à l'écran le message pointé par msgDebut
. Cette fonction fait appel au BIOS pour gèrer l'affichage du message. Plus précisement, elle appelle le service 0x0e
de l'interruption logicielle 0x10
du BIOS qui permet d'afficher un caractère à l'écran en précisant ses attributs (couleur, luminosité...). Il aurait été possible d'écrire ce message à l'écran en se passant de cette facilité offerte par le BIOS mais cela aurait été beaucoup moins simple à réaliser (c'est d'ailleurs l'objet du chapitre suivant... patience !).
Une fois le message affiché, le programme boucle et ne fait plus rien.
En fin de fichier, on définit les variables et les fonctions utilisées dans le programme. La chaîne de caractères affichée au demarrage à pour nom msgDebut
.
Ensuite, la fonction afficher
est définie. Elle prend en argument une chaîne de caractères pointée par les registres DS
et SI
. DS
correspond à l'adresse du segment de données et SI
est un déplacement par rapport au début de ce segment (un offset).
La chaîne de caractère passée en argument doit se terminer par un octet égal à 0 (comme en C).
Cette directive ajoute du bourrage sous forme d'instructions NOP (opcode 0x90, ou 144), puis le mot 0xAA55
à la fin du code compilé afin que le binaire généré fasse 512 octets. Le mot 0xAA55
est crucial : en fin de secteur, il est une signature pour indiquer au BIOS que le secteur en question est un MBR !
Le programme est écrit dans le fichier bootsect.asm
. Pour le compiler avec nasm
et obtenir le binaire (exécutable au boot) bootsect
, il faut utiliser la commande suivante :
$ nasm -f bin -o bootsect bootsect.asm
Ensuite, pour tester notre secteur de boot, nous pourrions l'écrire sur une disquette et rebooter notre machine préférée avec la disquette dedans. Mais cette démarche est très fastidieuse car un PC met toujours du temps à rebooter et les possibilites de débogage sont très limitées à ce stade. Heureusement, il existe de très bons émulateurs de PC, tels que bochs ou qemu. Pour ma part, j'utilise bochs qui, contrairement à qemu, possède un mode debug puissant.
Bochs a la possibilité d'utiliser un fichier en faisant comme si c'était une vraie disquette. Sous Unix, pour générer une image de disquette :
$ cat bootsect /dev/zero | dd of=floppyA bs=512 count=2880
Je ne sais pas trop comment tout cela peut être fait sous Windows (je ne travaille que sous Debian GNU/Linux). En revanche, vous trouverez d'autres développeurs Windows sur l'excellent forum Osdev.org.
Ensuite, la commande pour démarrer sur cette disquette virtuelle avec Bochs devrait ressembler à ça :
$ bochs 'boot:a' 'floppya: 1_44=floppyA, status=inserted'
Il est assez peu probable que Bochs fonctionne du premier coup si vous ne vous êtes pas donné la peine de lire sa documentation pour créer un fichier de configuration qui lui soit intelligible. Je sais, ça n'est pas la partie la plus passionnante, mais elle est hélas indispensable !
Pour ceux qui préfèrent utiliser qemu, il faut taper la commande suivante :
$ qemu -boot a -fda floppyA
Le résultat obtenu avec bochs :
< Introduction | TutoOS | Réaliser un secteur de boot qui charge et exécute un noyau >