Copyright © 2002 Krishnakumar R.
Copyright © 2011 Sébastien Marbrier
Article paru dans le n°077 de la Gazette Linux d'avril 2002.
Cet article est publié selon les termes de la Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
Table des matières
Cet article est un tutoriel pour construire un petit secteur d'amorçage. La première
section expose la théorie sur se qui se passe après le démarrage de l'ordinateur.
Elle expose aussi notre plan. La seconde section explique tout ce dont nous avons
besoin avant d'aller plus loin, et la troisième section traite des programmes. Notre
petit programme d'amorçage n'amorcera pas vraiment Linux
mais affichera quelque chose
à l'écran.
Le microprocesseur contrôle l'ordinateur. Au démarrage, chaque microprocesseur est un simple 8086. Même si vous avez un Pentium flambant neuf, il n'aura que les capacités d'un 8086. À partir d'ici, nous pouvons utiliser un programme et basculer le processeur sur l'infâme mode protégé. Ce n'est qu'à partir de là que nous pouvons utiliser toute la puissance du processeur.
Au départ le BIOS a le contrôle, il n'est qu'un ensemble de programmes en mémoire morte. Le BIOS réalise le POST (Power On Self Test ou test… ). Il contrôle l'intégrité de l'ordinateur (si les périphériques fonctionnent correctement, si le clavier est connecté, etc.). C'est se qui se passe quand l'ordinateur émet des bips. Si tout est en ordre, le BIOS choisi un périphérique de démarrage. Il copie le premier secteur (secteur d'amorçage) du périphérique à l'adresse 0x7C00. Le contrôle est ensuite transféré à cet emplacement. Le périphérique de démarrage peut être une disquette, un disque optique, un disque dur ou bien autre chose. Dans notre cas nous utiliserons un disquette en tant que périphérique de démarrage. Si nous avions écrit du code dans le secteur d'amorçage de la disquette, notre code aurait maintenant été exécuté. Notre rôle est clair : il se limite à l'écriture de programmes sur le secteur d'amorçage de la disquette.
C'est un assembleur. Le code assembleur que nous écrivons est converti en fichier objet par cet outil.
C'est l'éditeur de liens. Le code objet généré par as86 est converti dans le langage de la machine réelle par cet outil. Le langage machine sera dans une forme compréhensible que le 8086 comprend.
Le compilateur C. Pour le moment nous avons besoin d'écrire un programme en C pour transférer notre système d'exploitation sur la disquette.
Une disquette sera utilisée pour stocker notre système d'exploitation. Ce sera également notre périphérique de démarrage.
Vous savez à quoi elle sert.
as86 et ld86 sont présents dans la plupart des distributions standards. Dans le cas contraire, vous pouvez toujours les récupérer depuis le site http://freshmeat.net/projects/bin86/). Les deux sont inclus dans un paquet unique, bin86. Une bonne documentation est disponible à l'adresse http://www.linuxdocs.org/HOWTOs/Assembly-HOWTO/as86.html).
Prenez votre éditeur favori et tapez ces quelques lignes.
entry start start: mov ax,#0xb800 mov es,ax seg es mov [0],#0x41 seg es mov [1],#0x1f loop1: jmp loop1
C'est un langage assembleur qu'as86 comprendra. La première expression indique le point d'entrée où le contrôle doit entrer dans le programme. Nous indiquons que le le contrôle doit commencer par l'étiquette start. La seconde ligne indique la position de l'étiquette start (n'oubliez pas de mettre « : » après le start). L'instruction située juste après start sera la première exécutée dans ce programme.
0xb800 est l'adresse de la mémoire vidéo. Le # représente une valeur immédiate. Après l'exécution de
mov ax,#0xb800
le registre ax contiendra la valeur 0xb800, qui est l'adresse de la mémoire vidéo. Nous déplaçons maintenant cette valeur dans le registre es. es signifie extra segment (registre segment supplémentaire). Souvenez-vous que le 8086 a une architecture segmentée. Il possède des segments tels que des segments de code, de données, des segments supplémentaires, etc. — D'où les registres de segments cs, ds, es. En fait, nous avons fait de la mémoire vidéo notre segment supplémentaire, donc tout ce qui sera écrit dans le segment supplémentaire ira dans la mémoire vidéo.
Pour afficher un caractère à l'écran, vous devez écrire deux octets dans la mémoire
vidéo. Le premier est la valeur A.S.C.I.I. que vous voulez afficher, le second est
l'attribut du caractère. L'attribut est en rapport avec les couleurs utilisées
pour l'avant plan, celle pour l'arrière plan, si le caractère clignote et ainsi
de suite.
seg es est en réalité un préfixe qui indique
quelle est la prochaine instruction à exécuter en pointant sur le segment es. Par conséquent, nous déplaçons dans le premier octet de
la mémoire vidéo, la valeur 0x41, qui est la valeur A.S.C.I.I. du caractère 'A'. nous
devons ensuite placer l'attribut dans le prochain octet. Ici nous plaçons 0x1f, qui
est la valeur représentant un caractère blanc sur un fond bleu. Donc si nous
exécutons ce programme, nous obtenons un caractère blanc sur fond bleu. Enfin, il y a
la boucle. Nous devons arrêter l'exécution après l'affichage du caractère, ou bien
nous utilisons une boucle infinie. Sauvez le fichier dans boot.s
.
Le principe de la mémoire vidéo n'est peut-être pas très clair, alors détaillons. Supposons que l'écran est composé de 80 colonnes et 25 lignes. Donc nous avons besoin de 160 octets pour chaque ligne, un octet pour chaque caractère et un octet pour chaque attribut de caractère. Si nous devons écrire un caractère dans la colonne 3 nous devons sauter les octets 0 et 1 utilisés pour le premier caractère ; les octets 2 et 3 qui sont appartiennent à la seconde colonne ; là seulement écrire la valeur A.S.C.I.I. dans le quatrième octet ainsi que son attribut dans la cinquième case de la mémoire vidéo.
Nous devons écrire un programme en C qui copie notre code sur le premier secteur de la disquette. Le voici :
#include <sys/types.h> /* unistd.h needs this */ #include <unistd.h> /* contains read/write */ #include <fcntl.h> int main() { char boot_buf[512]; int floppy_desc, file_desc; file_desc = open("./boot", O_RDONLY); read(file_desc, boot_buf, 510); close(file_desc); boot_buf[510] = 0x55; boot_buf[511] = 0xaa; floppy_desc = open("/dev/fd0", O_RDWR); lseek(floppy_desc, 0, SEEK_CUR); write(floppy_desc, boot_buf, 512); close(floppy_desc); }
Nous commençons par ouvrir le fichier boot
en lecture
seule, et nous copions son descripteur de fichier dans la variable
file_desc
Nous lisons 510 octets ou bien jusqu'à la fin du
fichier. Ce code est court, alors quand ce dernier cas ce produit, soyons
propres et fermons le fichier.
Les quatre dernières lignes de code ouvrent le lecteur de disquette (qui est le
plus souvent /dev/fd0
). La tête est
positionnée au début du fichier grâce à lseek
et écrit
ensuite les 512 octets du tampon sur la disquette.
Les pages de manuel de read
, write
,
open
et lseek
(dans la section 2 de
man) devraient vous apporter suffisamment d'informations sur
les autres paramètres de ces fonctions et comment les utiliser. Entre les deux
blocs de code, il y a deux lignes qui peuvent être quelque peu mystérieuses.
Les lignes :
boot_buf[510] = 0x55; boot_buf[511] = 0xaa;
Ces informations sont pour le BIOS. Si le BIOS reconnaît un périphérique en
tant que périphérique amorçable, alors le périphérique doit avoir les valeurs
0x55 et 0xaa sur les positions 510 et 511. Nous avons à présent terminé. Le
programme lit le fichier boot
dans un tampon nommé
boot_buf
. il effectue les changements nécessaires dans les
octets 510 et 511 et écrit ensuite boot_buf
sur la
disquette. Si nous exécutons le code, les 512 premiers octets de la disquette
contiendrons le code d'amorçage. Enregistrons le fichiers avec le nom
write.c
.
Pour générer les exécutables à partir de ce fichier, il faut saisir les commandes suivante sur la commande Linux.
as86 boot.s -o boot.o ld86 -d boot.o -o boot cc write.c -o write
D'abord on assemble le fichier boot.s
pour créer un fichier objet boot.o
. Ensuite on effectue l'édition des liens pour obtenir
le fichier final boot
. L'option -d
de ld86 permet de supprimer tous les en-têtes pour
produire un binaire pur. La lecture des pages man d'as86 de de ld86 lèveront tous les
doutes. Nous compilerons ensuite le programme C pour générer un exécutable nommé
write
.
On insère une disquette vierge et on tape.
./write
On redémarre la machine. Dans le BIOS, on sélectionne le lecteur de disquette comme premier périphérique d'amorçage. On place la disquette dans le lecteur et on observe le démarrage de l'ordinateur à partir de la disquette d'amorçage.
Nous verrons ensuite un « A » (avec une couleur blanche sur un arrière-plan bleu). Cela
signifie que le système a démarré depuis la disquette que nous avons créée et ensuite
exécuté le programme du secteur d'amorçage que nous avons écrit. Il est à présent
dans la boucle infinie que nous avons écrite à la fin du secteur d'amorçage. Il faut
à présent redémarrer l'ordinateur et retirer notre disquette de démarrage pour
démarrer Linux
.
À partir de maintenant, nous souhaitons insérer davantage de code dans le programme du secteur d'amorçage afin de faire des choses plus élaborées (telles qu'utiliser les interruptions du BIOS, le basculement sur le mode protégé, etc). Les parties suivantes (parties II, III, etc.) de cet article vous guiderons vers de prochaines améliorations. D'ici là, je vous dit AU REVOIR !
L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.
Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://www.traduc.org/Gazette_Linux.
Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.