Écrivez votre micro OS (1ère partie)

Gazette Linux n°077 — Avril 2002

Sébastien Marbrier

Adaptation française  

Frédéric Marchal

Correction du DocBook 

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

1. Le fond
1.1 Le costume
1.2 Notre rôle
1.3 Le Plan
2. Ce dont vous avez besoin
3. 1, 2, 3, Partez !
3.1 Le secteur d'amorçage
3.2 Écriture du secteur d'amorçage de la disquette
3.3 Allons jusqu'au bout
À propos de l'auteur

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.

1. Le fond

1.1 Le costume

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.

1.2 Notre rôle

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.

1.3 Le Plan

Tout d'abord écrire un petit programme en assembleur 8086 (n'ayez pas peur ; je vous apprendrai comment l'écrire), et le copier sur le secteur d'amorçage de la disquette. Nous écrirons un programme en C pour le copier. Amorcez l'ordinateur avec cette disquette et admirez.

2. Ce dont vous avez besoin

as86

C'est un assembleur. Le code assembleur que nous écrivons est converti en fichier objet par cet outil.

ld86

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.

gcc

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 vierge

Une disquette sera utilisée pour stocker notre système d'exploitation. Ce sera également notre périphérique de démarrage.

Une machine linux

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).

3. 1, 2, 3, Partez !

3.1 Le secteur d'amorçage

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.

3.2 Écriture du secteur d'amorçage de la disquette

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.

3.3 Allons jusqu'au bout

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 !

À propos de l'auteur

Krishnakumar R.

Krishnakumar est un étudiant en dernière année à l'Université publique Thissur Kerala, en Inde. Son voyage au pays des Systèmes d'exploitation a commencé avec la progammation de modules linux. Il a construit un système d'exploitation de routage nommé GROS (Les détails sont disponibles sur sa page personnelle : http://www.askus.way.to/). Ses autres centres d'intérêts incluent le réseau, les pilotes de périphériques, le portage et les systèmes embarqués.

Adaptation française de la Gazette Linux

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.