Construire un système d'exploitation de zéro : ce que ça m'a appris sur les ordinateurs
Il n'y a pas de meilleure façon de comprendre comment fonctionne le logiciel que de construire ce sur quoi il tourne. En partant d'un bootloader vide, j'ai implémenté la gestion des interruptions, un driver clavier et la pagination mémoire — entièrement from scratch, sans framework.
Ce guide de développement noyau parcourt KFS-1 et KFS-2, ancré dans les fondamentaux d'OSDev. Nous passons du démarrage sur bare-metal aux interruptions, aux pilotes et à un shell de débogage, montrant comment chaque concept bas niveau devient du code fonctionnel.
Si c'est votre premier noyau, vous êtes au bon endroit. L'objectif est de rendre le chemin de démarrage, la disposition mémoire et le modèle d'interruption concrets, et non mystérieux.
Ce Que Vous Construisez (KFS-1 et KFS-2)
- Un noyau compatible Multiboot qui démarre via GRUB.
- Un script de liaison personnalisé et une chaîne d'outils freestanding.
- Configuration GDT et IDT avec câblage complet ISR/IRQ.
- Terminal texte VGA avec défilement et écrans multiples.
- Pilote clavier avec décodage de scancode.
- KFS-2 : shell interactif avec complétion par tabulation, introspection GDT et inspection de la pile.
Fondamentaux
Le développement noyau commence par la compréhension du vrai chemin de démarrage et des structures CPU minimales qui doivent être initialisées avant que tout fonctionne. Voici les concepts fondamentaux :
Démarrage et Outillage
- Compilation Croisée : Votre compilateur hôte cible votre OS, pas votre noyau. Construisez un compilateur croisé dédié afin que les binaires soient produits pour le CPU/ABI cible sans suppositions sur l'OS hôte.
- Bootstrapping : Mise sous tension → init BIOS/UEFI → POST → sélection du périphérique de démarrage → chargeur d'amorçage → chargement du noyau → services système. Votre noyau se trouve à la fin de cette chaîne.
- Chargeur d'Amorçage et GRUB : Le chargeur d'amorçage charge votre noyau en mémoire et y saute. GRUB est le choix habituel car il est conforme à Multiboot et prend en charge de nombreux types de systèmes de fichiers.
- Multiboot + En-tête : Un nombre magique, des drapeaux et une somme de contrôle placés dans les 8 premiers Kio permettent à GRUB de reconnaître votre noyau et de lui transférer le contrôle.
- Script de Liaison : Définit la disposition mémoire du noyau : où vivent
.text,.data,.bss, et la pile, et comment ils s'alignent.
Modèle CPU et Interruptions
- GDT : La Table de Descripteurs Globaux définit les privilèges et limites des segments. Même un modèle mémoire plat nécessite une GDT valide avec un descripteur nul et des segments noyau.
- IDT : La Table de Descripteurs d'Interruptions associe les vecteurs d'interruption aux gestionnaires (ISR) afin que le CPU sache comment réagir aux exceptions et événements matériels.
- ISR : Les Routines de Service d'Interruption sauvegardent l'état du CPU et transfèrent le contrôle aux gestionnaires C.
- IRQ + PIC : Les périphériques matériels signalent les interruptions via le PIC. Le PIC doit être remappé, et un signal de Fin d'Interruption doit être envoyé pour terminer le traitement.
- Contrôleur Clavier : Les scancodes sont lus depuis le port
0x60, et l'état est vérifié via le port0x64. L'IRQ1 signale une nouvelle saisie clavier.
Partie 1 : Outillage et Environnement
Compilation Croisée et Build Freestanding
Le code noyau ne peut pas être compilé avec le gcc de votre système. Un compilateur hébergé intègre des bibliothèques système
et suppose l'existence d'un OS. À la place, vous construisez une chaîne d'outils freestanding (par exemple i686-elf-gcc)
qui cible le matériel brut sans dépendances envers un OS.
Les drapeaux principaux utilisés dans KFS sont :
-ffreestanding -nostdlib -nodefaultlibs -fno-builtin -fno-stack-protector
Ces drapeaux garantissent que le compilateur génère du code autonome et ne suppose pas la présence de libc ni d'un environnement d'exécution.
Image de Démarrage et Émulateur
Nous construisons une ISO amorçable avec GRUB et la lançons dans QEMU. Cela permet un retour rapide sans toucher au matériel réel.
grub-mkrescue -o build/kernel.iso isodir
qemu-system-i386 -cdrom build/kernel.iso
Partie 2 : Bootstrapping et Multiboot
Les recommandations "Bare Bones" d'OSDev font ressortir un point essentiel : votre noyau n'est qu'un programme, mais il doit respecter le protocole de démarrage. Pour Multiboot, cela signifie un en-tête dans les 8 premiers Kio pour que GRUB puisse le reconnaître.
En-tête Multiboot (KFS-1)
section .multiboot
align 4
dd 0x1BADB002
dd 0x00
dd - (0x1BADB002 + 0x00)
Sans cet en-tête, GRUB ignore complètement votre noyau.
Script de Liaison et Disposition Mémoire
KFS place le noyau à 2 Mo et aligne chaque section sur des frontières de 4 Ko. Cela suit les bonnes pratiques d'OSDev et rend la transition vers la pagination simple par la suite.
ENTRY(_start)
SECTIONS {
. = 2M;
.text BLOCK(4K) : ALIGN(4K) { *(.multiboot) *(.text) }
.rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) }
.data BLOCK(4K) : ALIGN(4K) { *(.data) }
.bss BLOCK(4K) : ALIGN(4K) { *(COMMON) *(.bss) }
}
Point d'Entrée du Noyau et Pile
L'entrée assembleur initialise un pointeur de pile, appelle kernel_main, et s'arrête s'il retourne jamais.
_start:
mov $stack_top, %esp
call kernel_main
cli
1: hlt
jmp 1b
Récapitulatif : une fois que GRUB charge le noyau et que vous contrôlez la pile, vous pouvez initialiser les structures CPU en toute sécurité.
Partie 3 : Première Sortie - Terminal VGA
Les fondamentaux d'OSDev recommandent de démarrer en mode texte VGA. KFS écrit directement à 0xB8000
et construit un terminal bufferisé avec défilement et écrans multiples.
- Chaque cellule de caractère fait 2 octets : ASCII + attribut de couleur.
- Mises à jour du curseur via les ports
0x3D4et0x3D5. - Les écrans bufferisés permettent la commutation Alt+flèche et le défilement vers l'arrière.
Partie 4 : Segmentation et la GDT
Même avec un modèle mémoire plat, le CPU requiert une GDT valide. KFS-1 configure 3 entrées (nulle, code noyau, données noyau). KFS-2 s'étend à 7 entrées, ajoutant la pile noyau et les segments utilisateur.
gdt_set_gate(0, 0, 0, 0, 0);
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // code noyau
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // données noyau
gdt_set_gate(3, 0, 0xFFFFFFFF, 0x96, 0xCF); // pile noyau
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xFA, 0xCF); // code utilisateur
gdt_set_gate(5, 0, 0xFFFFFFFF, 0xF2, 0xCF); // données utilisateur
gdt_set_gate(6, 0, 0xFFFFFFFF, 0xF6, 0xCF); // pile utilisateur
Le stub assembleur utilise lgdt et recharge les registres de segments pour finaliser le basculement.
Partie 5 : Interruptions - IDT, ISR et IRQ
IDT et Exceptions
L'IDT définit 256 portes d'interruption. Le CPU utilise les vecteurs 0-31 pour les exceptions (division par zéro, faute de page, etc.). KFS installe des stubs assembleur et route vers un gestionnaire C avec un cadre de registres sauvegardés.
- Chaque ISR sauvegarde les registres avec
pusha. - Les registres de segments sont restaurés après le gestionnaire.
iretretourne au code interrompu.
Remappage du PIC et IRQ
Les interruptions matérielles du PIC entrent en conflit avec les vecteurs d'exceptions du CPU à moins d'être remappées. KFS remapping les IRQ vers 32-47, puis configure les portes IDT pour chaque ligne IRQ.
outb(0x20, 0x11);
outb(0xA0, 0x11);
outb(0x21, 0x20);
outb(0xA1, 0x28);
outb(0x21, 0x04);
outb(0xA1, 0x02);
outb(0x21, 0x01);
outb(0xA1, 0x01);
outb(0x21, 0x00);
outb(0xA1, 0x00);
Chaque gestionnaire IRQ doit envoyer un EOI (0x20) au PIC avant de retourner.
Partie 6 : Pilote Clavier et Flux d'Entrée
Le clavier lève l'IRQ1. KFS lit les scancodes depuis le port 0x60 et les convertit en ASCII via
une table de correspondance. L'état Shift et Alt est suivi, et les scancodes étendus sont gérés pour les flèches.
- L'utilisateur appuie sur une touche.
- Le contrôleur clavier déclenche l'IRQ1.
- Le CPU saute à l'entrée IDT 33 (IRQ1 remappée).
- L'ISR assembleur appelle
keyboard_handler(). - Le scancode est traduit et affiché sur le terminal.
Dans KFS-1, les touches fléchées font défiler et Alt+flèches changent d'écran. Dans KFS-2, Échap bascule le shell de débogage.
Partie 7 : Shell de Débogage KFS-2
KFS-2 introduit un shell minimal pour inspecter l'état du noyau. Il analyse les entrées, prend en charge la complétion par tabulation et distribue les commandes via une table de fonctions.
- help : Lister les commandes disponibles.
- gdt : Afficher les entrées GDT et les drapeaux d'accès.
- stack : Inspecter le pointeur de pile et son contenu.
- time : Lire l'heure RTC via les ports CMOS 0x70/0x71.
- reboot et halt : Contrôle du système.
Cela rend le noyau interactif sans nécessiter un espace utilisateur complet.
Conclusion
KFS-1 et KFS-2 transforment les fondamentaux d'OSDev en un noyau fonctionnel : vous démarrez avec Multiboot, mettez en place une chaîne d'outils freestanding, câblez la GDT et l'IDT, gérez les exceptions et les interruptions matérielles, et construisez un terminal réactif avec une vraie saisie clavier. Le shell de débogage dans KFS-2 transforme ensuite votre noyau en système interactif, rendant l'état bas niveau visible et testable.
La leçon essentielle est simple : un système d'exploitation est un programme qui possède la machine. Une fois que vous contrôlez le chemin de démarrage, la disposition mémoire, l'état du CPU et les interruptions, chaque fonctionnalité de haut niveau devient un choix d'ingénierie, non un mystère.
Écrit par

Tech Lead et Ingénieur Full Stack pilotant une équipe de 5 ingénieurs chez Fygurs (Paris, Remote) sur un SaaS cloud-native Azure. Diplômé de 1337 Coding School (42 Network / UM6P). Écrit sur l'architecture, l'infrastructure cloud et le leadership technique.