print · rss · source

< Réaliser un secteur de boot qui passe en mode protégé | TutoOS | Un noyau en C qui recharge la GDT >


Écrire un noyau en C

Sources

Le package contenant toutes les sources est téléchargeable ici : bootsect_kernelC.tgz.
Pour naviguer dans l'arborescence : BootSector_kernelC


Pourquoi passer de l'assembleur au C ?

La programmation en C offre par rapport à l'assembleur des avantages incontournables :

  • concision de l'écriture
  • facilité du débogage
  • portabilité sur d'autres architectures

Il est possible d'utiliser à peu prèt n'importe quel langage compilé pour écrire le code d'un noyau et chacun choisira le langage avec lequel il se sent le plus à l'aise (Pascal, C++...), la seule contrainte étant que ce langage doit permettre d'insérer des routines en assembleur et de manipuler directement les adresses en mémoire.

Un noyau en assembleur qui fait appel à des fonctions en C

Des routines pour afficher quelque chose à l'écran en C

Le fichier screen.c contient des fonctions permettant d'afficher des caractères à l'écran :

#include "types.h"

#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 courante du curseur a l'ecran */
char kY = 17;
char kattr = 0x0E;              /* attributs video des caracteres a afficher */


/*
 * 'scrollup' scrolle l'ecran (la console mappee en ram) vers le haut
 * de n lignes (de 0 a 25).
 */

void scrollup(unsigned int n)
{
        unsigned char *video, *tmp;

        for (video = (unsigned char *) RAMSCREEN;
             video < (unsigned char *) SCREENLIM; video += 2) {
                tmp = (unsigned char *) (video + n * 160);

                if (tmp < (unsigned char *) SCREENLIM) {
                        *video = *tmp;
                        *(video + 1) = *(tmp + 1);
                } else {
                        *video = 0;
                        *(video + 1) = 0x07;
                }
        }

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

void putcar(uchar c)
{
        unsigned char *video;
        int i;

        if (c == 10) {          /* CR-NL */
                kX = 0;
                kY++;
        } else if (c == 9) {    /* TAB */
                kX = kX + 8 - (kX % 8);
        } else if (c == 13) {   /* CR */
                kX = 0;
        } else {                /* autres caracteres */
                video = (unsigned char *) (RAMSCREEN + 2 * kX + 160 * kY);
                *video = c;
                *(video + 1) = kattr;

                kX++;
                if (kX > 79) {
                        kX = 0;
                        kY++;
                }
        }

        if (kY > 24)
                scrollup(kY - 24);
}

/*
 * 'print' affiche a l'ecran, a la position courante du curseur, une chaine
 * de caracteres terminee par \0.
 */

void print(char *string)
{
        while (*string != 0) {  /* tant que le caractere est different de 0x0 */
                putcar(*string);
                string++;
        }
}
  • Les variables kX et kY stockent en mémoire l'emplacement du curseur a l'écran. La variable kattr contient les attributs vidéo des caractères affichés.
  • La fonction scrollup() prend en argument un entier n et scrolle l'écran de n lignes.
  • La fonction putcar() affiche un caractère à l'écran
  • La fonction print() affiche une chaîne de caractères

Notez que certains types ont été définis dans le fichier types.h :

#ifndef _I386_TYPE_
#define _I386_TYPE_

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char uchar;

#endif

Un nouveau noyau en assembleur

Le code du noyau ci-dessous fait appel aux fonctions d'affichage définies ci-dessus. Cet exemple est assez intéressant car il montre comment un programme en assembleur fait appel à une fonction écrite en C (ce sujet est développé dans l'annexe sur la compilation séparée en assembleur sous Unix) :

[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

Par rapport aux précédents noyau, on note surtout :

  • L'absence de directive ORG, qui sert au calcul des adresses en fonction de l'endroit où le code est relogé. Ce calcul est maintenant réalisé par le linker ld.
  • La présence du point d'entrée _start, indispensable à ld.

Compiler le noyau

$ gcc -c screen.c
$ nasm -f elf -o kernel.o kernel.asm
$ ld --oformat binary -Ttext 1000 kernel.o screen.o -o kernel

L'option -Ttext indique l'addresse linéaire à partir de laquelle le code commence. Par défaut, ld suppose que le code commence à l'adresse 0x0. Ici, ce paramètre est indispensable car le code du noyau est recopié par le secteur de boot en 0x1000. La même fonctionnalité était implémentée par la directive [ORG 0x1000] dans les noyaux précédent en assembleur.

L'option -Tdata, qui sert à indiquer l'offset de début de la section de données, n'est pas utilisée. Par défaut, le linker considère que la zone de données suit la zone de texte (on remarque qu'elle est relogée une page mémoire plus loin).

Un noyau écrit entièrement en C

Le code

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

extern kY;
extern kattr;

void _start(void)
{
        kY = 18;
        kattr = 0x5E;
        print("un message\n");

        kattr = 0x4E;
        print("un autre message\n");

        scrollup(2);

        while (1);
}

Le code du noyau en C reprend de façon fidèle le code exprimé plus haut en assembleur.

Compiler le noyau

$ gcc -c screen.c
$ gcc -c kernel.c
$ ld --oformat binary -Ttext 1000 kernel.o screen.o -o kernel

< Réaliser un secteur de boot qui passe en mode protégé | TutoOS | Un noyau en C qui recharge la GDT >

print · rss · source
Page last modified on October 05, 2010, at 08:05 PM