Precedent | Sommaire | Suivant

5. Passer d'un noyau en assembleur a un noyau en C

Programmer le noyau en C offre un gain de temps tres important et facilite grandement le debogage. Un seul exemple, la premiere fonction que je programmais pour scroller l'ecran faisait 62 lignes de code en assembleur contre moins de 15 lignes en C plus tard. Programmer en C permet donc de construire son noyau plus rapidement, plus facilement, et de le deboguer avec moins de difficultes. Un autre avantage est que le code ecrit en C est portable sur d'autres architectures.

5.1 Lier du code assembleur avec du code C

Cette page presente des exemples de compilation separee avec nasm melant des fichiers ecrits en assembleur et des fichiers ecrits en C. La difficulte ne vient pas tellement de la compilation elle-meme mais plutot de la relocation par le linkeur.

5.2 Une bibliotheque de fonctions ecrite en C

Le fichier suivant contient deux fonctions qui seront utiles pour afficher des choses a l'ecran.
La fonction scrollup prend en argument un entier n et scrolle l'ecran de n lignes.
La fonction print affiche une chaine de caractere dont l'adresse est passee en argument.
Le code appelle quelques commentaires. Les variables kX et kY stockent en memoire l'emplacement du curseur a l'ecran. La variable kattr contient les attributs video des caracteres affiches.
#define RAMSCREEN 0xB8000	/* debut de la memoire video */
#define SIZESCREEN 0xFA0 	/* 4000, nombres d'octets d'une page texte */
#define SCREENLIM 0xB8FA0

char kX = 0;	/* position en X du curseur */
char kY = 10;	/* position en Y du curseur */
char kattr = 0x07;

/* nline = nombre de lignes a scroller (de 0 a 25) */
void scrollup (unsigned int nline) 
{
	unsigned char* video, *tmp;
	
	for(video=(unsigned char*)RAMSCREEN ; video<(unsigned char*)SCREENLIM ; video++){
			tmp = (unsigned char*) (video+nline*160);

			if(tmp<(unsigned char*)SCREENLIM)
				*video = *tmp;
			else
				*video = 0;
	}

	kY-=nline;
	if(kY<0) 
		kY=0;
}

void print (char* string)
{
	char* ptr;		/* pointeur sur la chaine de caractere */
	unsigned char* video;
	ptr = string;

	while(*ptr!=0){	/* tant que le caractere est different de 0x0 */
		if(*ptr==10){	/* CR-NL */
			kX=0;
			kY++;
		}
		else{
			video = (unsigned char*) (RAMSCREEN+2*kX+160*kY);
			*video = *ptr;
			*(video+1) = kattr;

			kX++;
			if(kX>79){
				kX = 0;
				kY++;
			}
		}
		ptr++;
		if(kY>24)
			scrollup(kY-24);
	}
}

5.3 Un kernel tres simple en assembleur

[BITS 32]

EXTERN scrollup, print
GLOBAL _start

_start:

	mov eax, msg
	push eax
	call print
	pop eax

	mov eax, msg2
	push eax
	call print
	pop eax

	mov eax,2
	push eax
	call scrollup

end:
	jmp end

msg db 'un premier message',10,0
msg2 db 'un deuxieme message',10,0

5.4 Le meme kernel en C

extern void scrollup (unsigned int);
extern void print (char*);

int main();

void _start(void) {
	main();
}

int main(void) {
	print("un message\n");
	print("un autre message\n");
	scrollup(2);
	while(1);
}

5.5 Compiler et tester les programmes

On compile les differents fichiers de la facon habituelle. Dans le cas ou le fichier principal du kernel est ecrit en C, on le compile avec gcc en utilisant l'option "-c" afin d'obtenir un fichier objet. Une fois les deux fichiers objets obtenus, on les lie avec la commande suivante :
$ ld --oformat binary -Ttext 1000 kernel.o aff.o -o kernel

L'option -Ttext indique l'addresse lineaire a partir de laquelle le code commence. Par defaut, ld suppose que le code commence a l'adresse 0. Ce parametre est indispensable a ld car le code du noyau est recopie par notre secteur de boot a l'adresse 0x1000.

L'option -Tdata n'est pas utilisee ici. Elle sert a indiquer l'offset de debut de la section de donnees. Dans le cas present, on utilise pour cette zone les parametres par defaut : la zone de donnees suit la zone de texte (on remarque qu'elle relogee une page memoire plus loin).