English Version | Version Française
A hobby operating system written from scratch for x86, following the Building an OS series by nanobyte. Goal: one small step per day, over about 3 months, with my own notes for each step.
nasm: assemblermake: build systemqemu-system-i386: virtual machine to test the OSdosfstools(mkfs.fat) andmtools(mcopy,mdir): build and inspect the FAT12 floppy imagegcc: compiler for the small FAT12 reader tool that runs on my PC- Open Watcom 2 (
wcc,wlink), installed in/opt/watcom: 16-bit C compiler for stage 2 of the bootloader - Developed on Arch Linux
make run # assembles the bootloader and kernel, builds the FAT12 floppy image, starts QEMU
make clean # removes the build/ folder
make # also builds the FAT12 reader tool: ./build/tools/fat build/main_floppy.img test.txt| Day | Topic | Notes |
|---|---|---|
| 1 | Boot sector, "Hello World" with BIOS int 0x10 |
docs/day01-tests.md |
| 2 | Bootloader / kernel split, FAT12 floppy image | docs/day02.md |
| 3 | Reading the disk: LBA to CHS, int 0x13 |
docs/day03.md |
| 4 | The FAT12 file system, reading a file in C | docs/day04.md |
| 5 | The bootloader loads the kernel from FAT12 | docs/day05.md |
| 6 | Two-stage bootloader, stage 2 in C (Open Watcom) | docs/day06.md |
| 7 | printf from scratch: varargs, state machine, 64-bit division |
docs/day07.md |
| 8 | Reading the disk from C: BIOS wrappers, DISK layer | docs/day08.md |
| 9 | The FAT driver in stage 2: open, read, paths | docs/day09.md |
Assembly is machine code written in a human-readable way. An instruction is a mnemonic plus 0 to 2 operands (mov ax, 5). NASM turns it into bytes. Here we target x86.
The BIOS reads the first sector (512 bytes) of the disk and checks that its last 2 bytes equal 0xAA55. It then loads the sector at 0x7C00 and jumps to it. My OS starts there.
A directive guides NASM and is not turned into machine code:
org 0x7C00: "compute addresses starting at 0x7C00". It does not force the BIOS to load there, it only informs NASM.bits 16: the CPU always starts in 16-bit mode, for backward compatibility with the 8086.times 510-($-$$) db 0:$is the current line,$$the start of the section, so$-$$is the code size so far. It pads with zeros up to 510 bytes.dw 0AA55h: the last 2 bytes (the boot signature).
Registers are tiny, very fast memories inside the CPU (ax, si, sp, ds, ss...). A real address is computed as segment × 16 + offset. Several pairs give the same address: 0x0000:0x7C00 and 0x07C0:0x0000 are both 0x7C00. Another rule: a constant cannot be written directly into a segment register, so we go through ax:
mov ax, 0
mov ds, axIt is last-in, first-out (push/pop) and is used by call/ret. It grows downwards in memory, so we set sp = 0x7C00: it grows away from our code and does not overwrite it.
lodsb: loads the byte atds:siintoal, thensi++.or al, al: leavesalunchanged but updates the zero flag.jz .doneleaves the loop when the character is 0.int 0x10withah = 0x0E: asks the BIOS to print the character inal. Also setbh = 0(page).- A new line is
0x0D, 0x0A(carriage return + line feed).
At startup SeaBIOS first tries the hard disk. QEMU has none, so it prints "Boot failed: could not read the boot disk", then falls back to the floppy, which is my image. This is normal and not a bug in my code.
The boot sector is only 512 bytes, too small for a real OS. The bootloader (in the boot sector) loads the kernel, a separate file, from the rest of the disk and hands over control. The code now lives in src/bootloader/boot.asm and src/kernel/main.asm.
Every BIOS and virtual machine supports floppies, images are easy to create, and FAT12 is simple. The kernel becomes a real file (kernel.bin) instead of raw sectors.
dd creates an empty 1.44 MB file (2880 × 512 bytes), mkfs.fat -F 12 formats it, dd ... conv=notrunc writes the bootloader into the first sector, and mcopy copies kernel.bin into the image without mounting it (no sudo).
Writing the bootloader over the first sector erases the headers that describe the disk, so mcopy fails with non DOS media. The bootloader must therefore start with jmp short start, a nop, then the BPB and the EBR fields:
| Field | Value |
|---|---|
| Bytes per sector | 512 |
| Sectors per cluster | 1 |
| Reserved sectors | 1 |
| Number of FATs | 2 |
| Root directory entries | 0E0h (224) |
| Total sectors | 2880 |
| Media descriptor | 0F0h (3.5" floppy) |
| Sectors per FAT | 9 |
| Sectors per track | 18 |
| Heads | 2 |
Multi-byte numbers are stored low byte first: 512 = 0x0200 is stored as 00 02, and the serial number bytes 12 34 56 78 form the value 0x78563412, which mdir shows as 7856-3412.
A disk is divided into cylinders, heads and sectors (CHS). The BIOS wants CHS, but it is simpler for me to number sectors with one value (LBA), so the bootloader converts: sector = (LBA % 18) + 1, head = (LBA / 18) % 2, cylinder = (LBA / 18) / 2 (for 18 sectors per track and 2 heads).
int 0x13 with ah = 02h reads sectors. al = number of sectors, ch/cl = cylinder and sector, dh = head, dl = drive, es:bx = destination in memory. On failure the carry flag (CF) is set.
Floppy disks are unreliable, so the bootloader tries up to 3 times, resetting the disk controller between attempts. If all fail, it prints an error, waits for a key (int 0x16) and reboots with jmp 0FFFFh:0.
make run-monitor opens QEMU with its monitor in the terminal. The command xp /16xb 0x7e00 shows the loaded sector: the start of the FAT table (f0 ff ff ff 0f ...), where the entry for kernel.bin (cluster 2) marks the end of its chain.
cli disables interrupts so that hlt really stops the CPU.
It is the way data is organized on a storage device, like the filing system of a library. FAT12 is very simple, which is why it is used for floppy disks and for getting started.
Reserved sectors (boot sector and header), the FAT table (two copies), the root directory (the list of files) and the data region (the contents of the files).
Compute where the root directory starts, read it, find the entry whose 11-character name matches, take its first cluster, convert the cluster to a sector (start of data region + (cluster - 2) * sectors per cluster), read it, then follow the FAT table to the next cluster until 0xFF8 or more.
For cluster n, the entry starts at byte n * 3 / 2. If n is even I keep the low 12 bits, otherwise I shift right by 4.
tools/fat/fat.c reads a file from the image on my PC: ./build/tools/fat build/main_floppy.img test.txt. On day 5, I will translate the same logic into assembly so the bootloader can load kernel.bin by itself.
| Area | Start (LBA) | Size (sectors) |
|---|---|---|
| Boot sector (reserved) | 0 | 1 |
| FAT 1 | 1 | 9 |
| FAT 2 | 10 | 9 |
| Root directory | 19 | 14 |
| Data region (cluster 2) | 33 | 2847 |
kernel.bin is cluster 2 (sector 33), test.txt is cluster 3 (sector 34).
It now does in assembly what my C program did on day 4: compute where the root directory is and read it, look for the 11-character name KERNEL BIN (repe cmpsb), take its first cluster, read the FAT table, then load the file cluster by cluster, following the 12-bit FAT chain until 0xFF8.
The kernel is loaded at 0x2000:0000 (physical 0x20000), in the large free area of real-mode memory. The bootloader sets ds and es to 0x2000, keeps the boot drive in dl and does a far jump. That is why the kernel is assembled with org 0.
add ax, 31 only works on a 1.44 MB floppy, and add bx, 512 overflows past 64 KB. Only 46 bytes are left in the boot sector, which is why the next step is a second stage.
The boot sector is too small (only 46 free bytes left), so it now does one job: find stage2.bin in the FAT12 root directory and load it. Stage 2 is written in C and is no longer limited to 512 bytes. The code is split into src/bootloader/stage1, src/bootloader/stage2 and src/kernel, each with its own Makefile.
We are still in 16-bit real mode, and GCC cannot produce that code, but Open Watcom can. Important options: -ms (small memory model), -zl (no standard library), -s (no stack checks).
The linker script (FORMAT RAW BIN, OFFSET=0, START=entry) makes the first byte of stage2.bin the entry point, so stage 1 can jump straight into it. In the .map file, the entry address must be 0.
Arguments are pushed right to left, the result comes back in ax, and the caller cleans the stack. Inside a function: [bp+2] is the return address and [bp+4] the first argument. Reading [bp+2] by mistake prints the same letter T over and over (the low byte of the return address).
C cannot call a BIOS interrupt, so x86_Video_WriteCharTeletype is in assembly and putc / puts in C call it.
With cdecl, arguments are pushed right to left, so the first one (fmt) is always at the same place. I take its address (argp = (int*) &fmt) and move forward one word per argument: a char or short takes 1 word (2 bytes in 16-bit mode), a long takes 2, a long long takes 4.
NORMAL prints characters until a %, then LENGTH (h, hh, l, ll), then SPEC (c, s, d, i, u, x, p, o, %). An unknown specifier is ignored.
Divide by the base repeatedly: each remainder is a digit (looked up in "0123456789abcdef"), and the digits come out in reverse order.
In 16-bit real mode the CPU can divide at most 64 bits by 32 bits, with a 32-bit quotient. The compiler asked for a library function I do not have, so I wrote x86_div64_32 in assembly: two 32-bit divisions in a row (long division in base 2³²).
printf("%d %u", -1, -1) prints -1 65535: in 16 bits, -1 is 0xFFFF, read as signed or unsigned.
Everything about a disk (drive number, cylinders, heads, sectors) lives in a DISK structure passed to each function, so the code does not depend on global state.
C cannot call int 13h, so x86_Disk_Reset, x86_Disk_Read and x86_Disk_GetDriveParams are written in assembly. They return a C boolean using mov ax, 1 then sbb ax, 0 (1 if the carry flag is clear).
int 13h with ah = 08h gives the highest cylinder and head numbers, so I add 1 (79 becomes 80 cylinders, 1 becomes 2 heads). Without that, LBA 19 is read from sector 37 and the root directory is garbage.
Dividing or multiplying 32-bit numbers in 16-bit mode makes the compiler call __U4D and __U4M. With the library disabled (-zl), I write them in assembly.
After replacing files with a zip, make clean first: old object files can look up to date and get linked with new ones.
FAT_Open returns a handle (the index of a slot in a table of 10 open files), then FAT_Read, FAT_ReadEntry and FAT_Close. The disk is passed to each function.
The big buffers live in a FAT_Data structure at a fixed address (segment 0x0050, physical 0x00500, up to 64 KB), followed by the FAT table. It is outside stage 2's segment, hence far pointers.
The disk reads whole sectors, but FAT_Read can return any number of bytes. The next sector is loaded when the buffer is used up, following the 12-bit FAT chain until 0xFF8.
FAT_Open("mydir/test.txt") splits the path at each /, finds each element in the current directory (test.txt becomes TEST TXT), and opens it. The root directory is a special case: it is not in the FAT.
With more than 16 files in the root directory, a search that reads a later sector leaves the buffer on that sector, and the next open fails. FAT_Open therefore reloads the root directory's first sector on each call.
- This project follows the "Building an OS" video series by nanobyte. The explanations, journal and tests are my own notes.
- Reference documentation: the OSDev wiki (FAT file system) and Ralf Brown's Interrupt List.
- Released under the MIT license, see LICENSE. If you reuse code that comes from the original tutorial, check the license of the original repository too.
Un système d'exploitation de loisir (hobby OS) écrit à partir de zéro pour l'architecture x86, en suivant la série Building an OS de nanobyte. Objectif : faire un petit pas par jour sur environ 3 mois, avec mes propres notes pour chaque étape.
nasm: l'assembleurmake: le système de buildqemu-system-i386: la machine virtuelle pour tester l'OSdosfstools(mkfs.fat) etmtools(mcopy,mdir) : pour créer et inspecter l'image disquette FAT12gcc: compilateur du petit outil de lecture FAT12 qui tourne sur mon PC- Open Watcom 2 (
wcc,wlink), installé dans/opt/watcom: compilateur C 16 bits pour la stage 2 du bootloader - Développé sous Arch Linux
make run # assemble le bootloader et le kernel, génère l'image disquette FAT12 et lance QEMU
make clean # supprime le dossier de build (build/)
make # compile aussi l'outil de lecture FAT12 : ./build/tools/fat build/main_floppy.img test.txt| Jour | Sujet | Notes |
|---|---|---|
| 1 | Secteur d'amorçage, "Hello World" avec l'interruption BIOS int 0x10 |
docs/day01-tests.md |
| 2 | Séparation bootloader / kernel, image disquette FAT12 | docs/day02.md |
| 3 | Lecture du disque : LBA vers CHS, int 0x13 |
docs/day03.md |
| 4 | Le système de fichiers FAT12, lecture d'un fichier en C | docs/day04.md |
| 5 | Le bootloader charge le kernel depuis FAT12 | docs/day05.md |
| 6 | Bootloader en deux étapes, stage 2 en C (Open Watcom) | docs/day06.md |
| 7 | printf à partir de zéro : arguments variables, machine à états, division 64 bits |
docs/day07.md |
| 8 | Lire le disque depuis le C : enveloppes BIOS, couche DISK | docs/day08.md |
| 9 | Le pilote FAT dans la stage 2 : ouvrir, lire, chemins | docs/day09.md |
L'assembleur est du code machine écrit d'une manière lisible par l'humain. Une instruction est composée d'un mnémonique et de 0 à 2 opérandes (mov ax, 5). NASM transforme cela en octets. Ici, nous ciblons l'architecture x86.
Le BIOS lit le premier secteur (512 octets) du disque et vérifie que ses 2 derniers octets sont égaux à 0xAA55. Il charge ensuite ce secteur à l'adresse mémoire 0x7C00 et saute (jump) dessus. C'est là que mon OS commence.
Une directive guide NASM et n'est pas transformée en code machine :
org 0x7C00: "calcule les adresses en commençant à 0x7C00". Cela ne force pas le BIOS à charger le code à cet endroit, cela informe simplement NASM.bits 16: le processeur démarre toujours en mode 16 bits, pour des raisons de rétrocompatibilité avec le 8086.times 510-($-$$) db 0:$représente la ligne actuelle,$$le début de la section, donc$-$$donne la taille du code écrit jusqu'ici. Cette commande remplit le reste avec des zéros jusqu'à atteindre 510 octets.dw 0AA55h: les 2 derniers octets (la signature d'amorçage ou boot signature).
Les registres sont de toutes petites mémoires très rapides situées directement dans le processeur (ax, si, sp, ds, ss...). Une adresse réelle se calcule ainsi : segment × 16 + offset. Plusieurs paires de valeurs peuvent donner la même adresse physique : 0x0000:0x7C00 et 0x07C0:0x0000 pointent toutes les deux vers 0x7C00. Autre règle : on ne peut pas écrire une constante directement dans un registre de segment, il faut obligatoirement passer par ax :
mov ax, 0
mov ds, axElle fonctionne selon le principe du "dernier entré, premier sorti" (push/pop) et est utilisée par call/ret. Elle grandit vers le bas de la mémoire, c'est pourquoi nous définissons sp = 0x7C00 : elle s'éloigne ainsi de notre code et ne risque pas de l'écraser.
lodsb: charge l'octet situé à l'adresseds:sidansal, puis incrémentesi(si++).or al, al: laissealinchangé mais met à jour le drapeau zéro (zero flag).jz .donepermet de quitter la boucle lorsque le caractère lu est égal à 0.int 0x10avecah = 0x0E: demande au BIOS d'afficher le caractère contenu dansal. On définit égalementbh = 0(la page d'affichage).- Un saut de ligne est représenté par
0x0D, 0x0A(retour chariot + saut de ligne).
Au démarrage, SeaBIOS cherche d'abord à démarrer sur le disque dur. QEMU n'en ayant pas, il affiche "Boot failed: could not read the boot disk", puis bascule sur la disquette, qui contient mon image. C'est un comportement tout à fait normal et ce n'est pas un bug dans mon code.
Le boot sector ne fait que 512 octets, trop peu pour un vrai OS. Le bootloader (dans le boot sector) charge le kernel, un fichier à part, depuis le reste du disque, puis lui laisse la main. Le code est maintenant dans src/bootloader/boot.asm et src/kernel/main.asm.
Tous les BIOS et toutes les machines virtuelles gèrent les disquettes, les images se créent facilement, et FAT12 est simple. Le kernel devient un vrai fichier (kernel.bin) au lieu de secteurs bruts.
dd crée un fichier vide de 1,44 Mo (2880 × 512 octets), mkfs.fat -F 12 le formate, dd ... conv=notrunc écrit le bootloader dans le premier secteur, et mcopy copie kernel.bin dans l'image sans la monter (pas de sudo).
Écrire le bootloader sur le premier secteur efface les en-têtes qui décrivent le disque, donc mcopy échoue avec non DOS media. Le bootloader doit donc commencer par jmp short start, un nop, puis les champs du BPB et de l'EBR :
| Champ | Valeur |
|---|---|
| Octets par secteur | 512 |
| Secteurs par cluster | 1 |
| Secteurs réservés | 1 |
| Nombre de FAT | 2 |
| Entrées du répertoire racine | 0E0h (224) |
| Nombre total de secteurs | 2880 |
| Type de média | 0F0h (disquette 3,5") |
| Secteurs par FAT | 9 |
| Secteurs par piste | 18 |
| Têtes | 2 |
Les nombres sur plusieurs octets sont stockés en commençant par l'octet de poids faible : 512 = 0x0200 s'écrit 00 02, et les octets 12 34 56 78 du numéro de série forment la valeur 0x78563412, que mdir affiche 7856-3412.
Un disque est divisé en cylindres, têtes et secteurs (CHS). Le BIOS veut du CHS, mais il est plus simple pour moi de numéroter les secteurs avec un seul nombre (LBA). Le bootloader convertit donc : secteur = (LBA % 18) + 1, tête = (LBA / 18) % 2, cylindre = (LBA / 18) / 2 (pour 18 secteurs par piste et 2 têtes).
int 0x13 avec ah = 02h lit des secteurs. al = nombre de secteurs, ch/cl = cylindre et secteur, dh = tête, dl = lecteur, es:bx = destination en mémoire. En cas d'échec, le flag carry (CF) est à 1.
Les disquettes sont peu fiables, donc le bootloader réessaie jusqu'à 3 fois en réinitialisant le contrôleur entre deux essais. Si tout échoue, il affiche une erreur, attend une touche (int 0x16) et redémarre avec jmp 0FFFFh:0.
make run-monitor ouvre QEMU avec son moniteur dans le terminal. La commande xp /16xb 0x7e00 montre le secteur chargé : le début de la table FAT (f0 ff ff ff 0f ...), où l'entrée de kernel.bin (cluster 2) marque la fin de sa chaîne.
cli désactive les interruptions pour que hlt arrête vraiment le processeur.
C'est la manière d'organiser les données sur un support, comme le classement d'une bibliothèque. FAT12 est très simple, c'est pourquoi on l'utilise pour les disquettes et pour commencer.
Les secteurs réservés (boot sector et en-tête), la table FAT (deux copies), le répertoire racine (la liste des fichiers) et la zone de données (le contenu des fichiers).
Calculer où commence le répertoire racine, le lire, trouver l'entrée dont le nom de 11 caractères correspond, prendre son premier cluster, le convertir en secteur (début de la zone de données + (cluster - 2) * secteurs par cluster), le lire, puis suivre la table FAT vers le cluster suivant jusqu'à 0xFF8 ou plus.
Pour le cluster n, l'entrée commence à l'octet n * 3 / 2. Si n est pair je garde les 12 bits de poids faible, sinon je décale de 4 vers la droite.
tools/fat/fat.c lit un fichier dans l'image sur mon PC : ./build/tools/fat build/main_floppy.img test.txt. Au jour 5, je traduirai la même logique en assembleur pour que le bootloader charge kernel.bin tout seul.
| Zone | Début (LBA) | Taille (secteurs) |
|---|---|---|
| Secteur de boot (réservé) | 0 | 1 |
| FAT 1 | 1 | 9 |
| FAT 2 | 10 | 9 |
| Répertoire racine | 19 | 14 |
| Zone de données (cluster 2) | 33 | 2847 |
kernel.bin est le cluster 2 (secteur 33), test.txt est le cluster 3 (secteur 34).
Il fait maintenant en assembleur ce que mon programme en C faisait au jour 4 : calculer où est le répertoire racine et le lire, chercher le nom de 11 caractères KERNEL BIN (repe cmpsb), prendre son premier cluster, lire la table FAT, puis charger le fichier cluster par cluster en suivant la chaîne FAT de 12 bits jusqu'à 0xFF8.
Le kernel est chargé à 0x2000:0000 (adresse physique 0x20000), dans la grande zone libre du mode réel. Le bootloader met ds et es à 0x2000, garde le lecteur de démarrage dans dl et fait un saut lointain. C'est pourquoi le kernel est assemblé avec org 0.
add ax, 31 ne marche que sur une disquette de 1,44 Mo, et add bx, 512 déborde au-delà de 64 Ko. Il ne reste que 46 octets dans le boot sector, c'est pourquoi la suite est une seconde étape (stage 2).
Le boot sector est trop petit (il ne reste que 46 octets libres), donc il n'a plus qu'un rôle : trouver stage2.bin dans le répertoire racine FAT12 et le charger. La stage 2 est écrite en C et n'a plus la limite des 512 octets. Le code est séparé en src/bootloader/stage1, src/bootloader/stage2 et src/kernel, chacun avec son propre Makefile.
On est toujours en mode réel 16 bits, et GCC ne sait pas produire ce code, alors qu'Open Watcom le sait. Options importantes : -ms (modèle mémoire small), -zl (pas de bibliothèque standard), -s (pas de vérification de pile).
Le script d'édition de liens (FORMAT RAW BIN, OFFSET=0, START=entry) fait du premier octet de stage2.bin le point d'entrée, donc la stage 1 peut sauter directement dessus. Dans le fichier .map, l'adresse d'entrée doit être 0.
Les arguments sont empilés de droite à gauche, le résultat revient dans ax, et l'appelant nettoie la pile. Dans une fonction : [bp+2] est l'adresse de retour et [bp+4] le premier argument. Lire [bp+2] par erreur affiche toujours la même lettre T (l'octet de poids faible de l'adresse de retour).
Le C ne peut pas appeler une interruption BIOS : x86_Video_WriteCharTeletype est en assembleur, et putc / puts en C l'appellent.
Avec cdecl, les arguments sont empilés de droite à gauche, donc le premier (fmt) est toujours au même endroit. Je prends son adresse (argp = (int*) &fmt) et j'avance d'un mot par argument : un char ou un short occupe 1 mot (2 octets en mode 16 bits), un long 2, un long long 4.
NORMAL affiche les caractères jusqu'à un %, puis LENGTH (h, hh, l, ll), puis SPEC (c, s, d, i, u, x, p, o, %). Un spécificateur inconnu est ignoré.
On divise par la base à répétition : chaque reste est un chiffre (cherché dans "0123456789abcdef"), et les chiffres sortent à l'envers.
En mode réel 16 bits, le processeur divise au maximum 64 bits par 32 bits, avec un quotient de 32 bits. Le compilateur demandait une fonction de bibliothèque que je n'ai pas, j'ai donc écrit x86_div64_32 en assembleur : deux divisions de 32 bits à la suite (la division posée, en base 2³²).
printf("%d %u", -1, -1) affiche -1 65535 : sur 16 bits, -1 vaut 0xFFFF, lu en signé ou en non signé.
Tout ce qui concerne un disque (numéro du lecteur, cylindres, têtes, secteurs) est dans une structure DISK passée à chaque fonction, donc le code ne dépend d'aucun état global.
Le C ne sait pas appeler int 13h : x86_Disk_Reset, x86_Disk_Read et x86_Disk_GetDriveParams sont écrites en assembleur. Elles retournent un booléen C avec mov ax, 1 puis sbb ax, 0 (1 si le flag de retenue est à 0).
int 13h avec ah = 08h donne les plus grands numéros de cylindre et de tête, donc j'ajoute 1 (79 devient 80 cylindres, 1 devient 2 têtes). Sans cela, le LBA 19 est lu au secteur 37 et le répertoire racine est illisible.
Diviser ou multiplier des nombres de 32 bits en mode 16 bits fait appeler __U4D et __U4M par le compilateur. Avec la bibliothèque désactivée (-zl), je les écris en assembleur.
Après avoir remplacé des fichiers avec un zip, faire make clean d'abord : d'anciens fichiers objets peuvent sembler à jour et être liés avec des nouveaux.
FAT_Open rend une poignée (l'indice d'un emplacement dans un tableau de 10 fichiers ouverts), puis FAT_Read, FAT_ReadEntry et FAT_Close. Le disque est passé à chaque fonction.
Les gros tampons sont dans une structure FAT_Data à une adresse fixe (segment 0x0050, physique 0x00500, 64 Ko au maximum), suivie de la table FAT. C'est en dehors du segment de la stage 2, d'où les pointeurs far.
Le disque lit des secteurs entiers, mais FAT_Read peut rendre n'importe quel nombre d'octets. Le secteur suivant est chargé quand le tampon est épuisé, en suivant la chaîne FAT de 12 bits jusqu'à 0xFF8.
FAT_Open("mydir/test.txt") découpe le chemin à chaque /, retrouve chaque élément dans le dossier courant (test.txt devient TEST TXT) et l'ouvre. La racine est un cas particulier : elle n'est pas dans la FAT.
Avec plus de 16 fichiers à la racine, une recherche qui lit un secteur plus loin laisse le tampon sur ce secteur, et l'ouverture suivante échoue. FAT_Open recharge donc le premier secteur de la racine à chaque appel.
- Ce projet suit la série de vidéos « Building an OS » de nanobyte. Les explications, le journal et les tests sont mes propres notes.
- Documentation de référence : le wiki OSDev (système de fichiers FAT) et la liste d'interruptions de Ralf Brown.
- Publié sous licence MIT, voir LICENSE. Si tu réutilises du code issu du tutoriel d'origine, vérifie aussi la licence du dépôt d'origine.








