BosoKernel : une introduction a la programmation de systemes d'exploitation


1. Introduction

1.1 Presentation

Programmer un noyau est un bon moyen de comprendre comment fonctionne un systeme d'exploitation. Mais cela demande du temps, de la patience et des nerfs solides.
Le but de ce document est de guider tous ceux qui comme moi, un jour, on eu envie de coder un noyau, mais sans savoir par ou commencer.
Pas a pas, je vais essayer d'expliquer le fonctionnement et la programmation d'un noyau pour architecture i386, en m'appuyant sur des exemples.
Je suis moi meme novice en programmation de systemes et je tiens a prevenir que ce document est tres certainement incomplet et qu'il contient peut-etre des erreurs. Vous pouvez me faire parvenir toutes vos remarques a am@bocal.cs.univ-paris8.fr. Le kernel que j'ai commence a developper s'appelle "BosoKernel" (Bocal Operating System Original Kernel).

1.2 Pre-requis

Le noyau presente ici est developpe uniquement en assembleur. On ne peut faire l'impasse sur ce langage proche de la machine qui permet de piloter le materiel. Pour ceux qui ne savent pas programme en assembleur, il existe de nombreux sites qui proposent des tutoriaux. Un tres bon site est "http://webster.cs.ucr.edu/".
L'assembleur pour i386 n'est pas tres dur a apprendre, helas, le meilleur moyen a ma connaissance d'apprendre un langage de programmation est de programmer. Et pour realiser et tester des petits programmes en assembleur, le meilleur outil que je connaisse est "debug" sous DOS.
Connaitre l'architecture du processeur i386 est tres recommande pour comprendre ce document. Les notions importantes seront toutefois expliquees au fur et a mesure.

1.3 Les outils

Il faut imperativement un assembleur. J'utilise "nasm", "http://www.web-sites.co.uk/nasm/", qui a l'avantage d'etre opensource et qui est utilisable sur d'autres architectures que le i386. Ainsi, je l'utilise sur une machine sous Solaris sans aucun probleme.
Pour tester les programmes realises, le mieux est d'utiliser un emulateur de PC. J'utilise "bochs", "http://bochs.com/", qui a pour avantage de s'executer sur un grand nombre de plateformes. Utiliser un emulateur permet de tracer l'execution d'un programme pas a pas et de le deboguer plus facilement. Cet outil permet de faire l'economie d'un second PC qu'il faut sans cesse rebooter et qui rend le debogage vraiment ardu.

2. Le premier programme bootable

2.1 Presentation

Un noyau est present au depart sur un support qui peut etre une disquette, un disque dur, une bande magnetique, etc. Comme mon propos ici est de montrer un exemple de conception de noyau, je vais faire l'economie d'une approche exhaustive et aborder uniquement le cas ou il est sur disquette.
Pour pouvoir fonctionner, le noyau present sur la disquette doit etre charge en memoire ! Cette partie montre ce qui se passe on allume un PC et comment faire un code chargeable et executable au boot. Ca n'est pas encore un noyau, mais c'est un debut :-)

2.2 La theorie : que fait un PC au demarrage ?

Au demarrage, le PC initialise et teste le processeur et les peripheriques. Ensuite, le BIOS charge en memoire le premier secteur de l'unite de boot.
Ce secteur, appele secteur de boot, qui a une taille de 512 octets sur disquette, est un petit programme en assembleur.
Ce programme est charge en memoire a une adresse precise, l'adresse 0x7C00. Une fois charge en memoire, le processeur execute ce programme.
Ce programme de boot a en principe une tache delicate : lancer le noyau. En effet, le BIOS ne peut charger directement le noyau qui fait normalement beaucoup plus que 512 octets. Impossible donc de le charge directement, on fait donc appel a un chargeur que l'on appelle chargeur de premier niveau. Ce premier programme ne va cependant pas toujours charger un noyau mais plutot un 2e chargeur qui va charger lui le noyau.
Par exemple, sous Linux, le "master boot record" est charge au demarrage. Il va charger et lancer le programme "LILO" qui a son tour va nous permettre de charger puis de lancer un noyau.
Pour le moment, nous allons creer un secteur de boot qui ne fait qu'afficher un message.

2.3 La pratique : afficher "Hello world !" au boot

Voici le code d'un programme tres simple qui affiche un message de bienvenue :

	prog01 :
	--------
	[BITS 16]	; indique a nasm que l'on travaille en 16 bits
	[ORG 0x0]
		
	; 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
	
	
	end:
		jmp end
	
	
	;--- Variables ---
	msgDebut	db	"Hello world !",13,10,0
	;-----------------
	
	;---------------------------------------------------------
	; Synopsis:	Affiche une chaine de caracteres se terminant par 0x0
	; Entree:	ds:si -> pointe sur la chaine a afficher
	;---------------------------------------------------------
	afficher:
		push ax
		push bx
	.debut:
		lodsb		; ds:si -> al
		cmp al,0	; fin chaine ?
		jz .fin
		mov ah,0x0E	; appel au service 0x0e, int 0x10 du bios
		mov bx,0x07	; bx -> attribut, al -> caractere ascii
		int 0x10
	        jmp .debut
	
	.fin:
		pop bx
		pop ax
		ret
	
	
	;--- NOP jusqu'a 510 ---
	times 510-($-$$) db 144
	dw 0xAA55

Que fait exactement ce programme ?

2.4 La pratique : compiler et tester le programme

Le programme est ecrit dans un fichier qui s'appelle "bootsect.asm". Pour le compiler avec nasm et obtenir le binaire "bootsect", il faut executer la commande suivante :
	$ nasm -f bin -o bootsect bootsect.asm

Pour lancer le secteur de boot, la methode la plus simple est certainement de copier le binaire sur une disquette avec la commande suivante :

	$ dd if=bootsect of=/dev/fd0
Ensuite il faut rebooter sa machine avec cette disquette inseree. C'est tres penible car un PC fait tout un tas de tests plus ou moins longs au demarrage et les possibilites de debogage sont tres limitees. Heureusement, il existe un emulateur de PC qui tourne sur un tres grand nombre de plateformes : "bochs" (bochs.com).

3. Charger un noyau

3.1 Presentation

La partie precedente montrait comment fonctionne le 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. C'est ce que nous allons voir ici.
Le deuxieme programme est plus complexe. Il s'agit en fait de realiser un programme de boot et un noyau. Le programme de boot occupe le premier secteur de la disquette. Une fois charge, il affiche un message, charge le noyau en memoire a une adresse de notre choix et donne la main a celui-ci. Le premier noyau est tres rudimentaire, il ne fait qu'afficher un message.


%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