Precedent | Sommaire | Suivant

3. Charger un noyau

3.1 Presentation

La partie precedente montrait comment fonctionne un programme de boot charge a partir d'une disquette. Seulement, le programme vu precedemment ne faisait qu'afficher un message.
La finalite d'un programme de boot est normalement de charger un noyau (ou au pire un autre secteur de boot). C'est ce que nous allons voir ici.
Cette partie est plus complexe car il s'agit de realiser deux programmes : un secteur de boot et un noyau. Le programme de boot, qui occupe le premier secteur de la disquette, affiche un message, charge le noyau en memoire a une adresse de notre choix et donne la main a celui-ci. Le noyau est tres rudimentaire, il ne fait qu'afficher un message.

3.2 Un programme de boot plus complet

Le programme suivant est le programme du secteur de boot. Il differe assez peu de celui vu dans la partie precedente :


%define BASE	0x100  
%define KSIZE	1	; nombre de secteurs de 512 octets a charger

[BITS 16]
[ORG 0x0]

jmp start
%include "UTIL.INC"
start:
	mov [bootdrv],dl	; recuparation de l'unite de boot

; initialisation des segments en 0x07C0
	mov ax,0x07C0
	mov ds,ax
	mov es,ax
	mov ax,0x8000	; stack en 0xFFFF
	mov ss,ax
	mov sp, 0xf000

; affiche un msg
	mov si,msgDebut
	call afficher

; charger le noyau
	xor ax,ax
	int 0x13

	push es
	mov ax,BASE
	mov es,ax
	mov bx,0
	
	mov ah,2
	mov al,KSIZE
	mov ch,0
	mov cl,2
	mov dh,0
	mov dl,[bootdrv]
	int 0x13
	pop es

; saut vers le kernel
	jmp dword BASE:0


msgDebut	db	"Chargement du kernel",13,10,0

bootdrv: db 0

;; NOP jusqu'a 510
times 510-($-$$) db 144
dw 0xAA55

Le programme commence par un saut a l'adresse 'start'. Une directive 'include' ajoute au code du noyau le contenu du fichier 'UTIL.INC'. Ce fichier contient le code de la fonction 'afficher' vu precedement :

    jmp start
    %include "UTIL.INC"
    start:
Ensuite, le programme stocke dans une variable un nombre servant a identifier le peripherique de boot (ici le lecteur de disquettes). Cette variable sera reutilisee plus tard pour indiquer a partir de quel peripherique doit etre charge le noyau.
    mov [bootdrv],dl	; recuparation de l'unite de boot

Le noyau se situe au debut du second secteur de la disquette. Pour le charger en memoire, on utilise l'interruption 0x13 du BIOS. Cette fonction est expliquee en detail sur le site "The Art of Assembly" . Elle permet de copier un ou plusieurs secteurs d'une disquette en memoire. Dans le cas present, on recopie le deuxieme secteur de la disquette, qui contient le noyau, a l'adresse 0x1000 en RAM. La variable KSIZE defini le nombre de secteurs a charger pour que tout le noyau soit bien recopie en memoire. Le premier noyau, qui est decrit dans la partie suivante, est tres court. Sur ma machine, le binaire correspondant fait 69 octets. On peut donc se contenter de mettre la valeur de KSIZE a 1 pour copier un seul secteur.

; charger le noyau
	xor ax,ax
	int 0x13

	push es
	mov ax,BASE
	mov es,ax
	mov bx,0
	
	mov ah,2
	mov al,KSIZE
	mov ch,0
	mov cl,2
	mov dh,0
	mov dl,[bootdrv]
	int 0x13
	pop es
Ensuite, une instruction de saut execute le code du noyau. La macro 'BASE' est definie de facon a ce que le pointeur d'instructions pointe sur l'adresse 0x1000 :
    ; saut vers le kernel
    jmp dword BASE:0

3.3 Un premier noyau tres simple

Voici le programme du noyau :

[BITS 16]
[ORG 0x0]

jmp start

%include "UTIL.INC"

start:
; initialisation des segments en 0x100
	mov ax,0x100
	mov ds,ax
	mov es,ax
	mov ax,0x8000	; stack en 0xFFFF
	mov ss,ax
	mov sp, 0xf000

; affiche un msg
	mov si,msg00
	call afficher

end:
	jmp end


msg00: db 'Kernel is speaking !',10,0

Ce programme initialise les registres de code et de donnees afin qu'ils pointent sur la bonne zone memoire (0x1000). Ensuite, un message est affiche pour attester de la reussite des operations. A la ligne suivante, le noyau ne fait vraiment pas grand chose : il boucle indefiniment.

3.4 Compiler et tester

Le code se decompose en un fichier pour le secteur de boot et un fichier pour le noyau. Le fichier UTIL.INC est juste une sorte de librairie qui comprend la fonction 'afficher' :
    $ ls 
    UTIL.INC bootsect.asm kernel.asm

On compile les differents programmes :

$ nasm -f bin -o bootsect bootsect.asm
$ nasm -f bin -o kernel kernel.asm
$ ls -l
total 14
-rw-r--r--   1 am       users        492 Jan 17 17:20 UTIL.INC
-rw-r--r--   1 am       users        512 Jan 19 18:16 bootsect
-rw-r--r--   1 am       users        715 Jan 17 17:22 bootsect.asm
-rw-r--r--   1 am       users        297 Jan 17 17:50 kernel.asm
-rw-r--r--   1 am       users         69 Jan 19 18:16 kernel

On remarque que le binaire du secteur de boot fait bien 512 octets. Le binaire obtenu pour le noyau fait seulement 69 octets.
La disquette que nous allons faire aura le noyau debutant au deuxieme secteur. On realise une image de la disquette avec la commande suivante :

$ cat bootsect kernel /dev/zero | dd of=floppy bs=512 count=2880
$ ls -l floppy
-rw-r--r--   1 am       users    1474117 Jan 19 18:27 floppy

Une fois la disquette realisee, on peut la tester avec bochs et on obtient l'ecran suivant :