Comprendre Stage 2
Le tutoriel que vous vous apprêtez à lire a été écrit par un bénévole, qui a mis à disposition gratuitement son savoir. Malgré le passage à la validation de ce tutoriel, nous ne pouvons garantir la véricité des informations contenues dedans. Merci de garder ceci à l'esprit pendant votre lecture
Bonjour,
Vous avez suivi le tutoriel qui vous explique comment créer un secteur de boot, mais vous désirez aller plus loin, suivez le guide !
Dans ce tutoriel, vous apprendrez comment marche le Stage2 de Logram. Vous verrez qu'il a une structure assez simple, mais beaucoup plus complexe que Stage1. Bonne chance
Ce que devra faire Stage2
Stage2 ne devra pas se contenter de charger quelque chose, il devra d'abord initialiser la mémoire. Ce n'est pas le plus difficile, il faut juste être attentif.
Là où il y aura des problèmes, c'est pour charger en mémoire Logram\sys64\kernel.ext. Ce fichier sera sur une partition FSL, et il faudra donc savoir lire le FSL. Heureusement, FSL est un système de fichier simple : il ne m'a fallut que 45 minutes pour coder la fonction de lecture, fonction même qui servira dans le noyau de Logram.
Dans la suite de ce tutoriel, ce sera Stage2 de Logram qui sera décrit. Si votre OS est différent (et j'en suis sûr), vous ne saurez que difficilement suivre le tutoriel. Si vous voulez un OS qui utilise un format reconnu par Linux (ext2/3, reiserfs, mais aussi NTFS et FAT), je vous conseille d'utiliser GRUB, qui permet de charger le noyau directement pour vous
Etape 1 : Initialisation de la mémoire
Logram sera en mode Long. Pour utiliser le mode Long, il nous faut absolument une structure de pagination. Créer cette structure de pagination à la main serait une tâche colossale, et en plus elle fait 2Mo, et charger 2Mo sur le disque prendrait trop de temps.
Qu'est-ce qu'une structure de pagination ?
Une structure de pagination est une structure qui permet de traduire une adresse en une autre. L'adresse traduite s'appelle Adresse virtuelle, l'adresse résultante s'appelle Adresse physique. Une structure de pagination est pratique dans le sens qu'une application peut demander de charger par exemple un fichier MaVideo.avi de 130Mo. Cette application va ensuite le lire, sans problème, du début à la fin, alors que les données de ce fichier peuvent en fait se trouver sur le disque, dans la mémoire, ou les deux en même temps. La pagination simplifie donc énormément les applications.
A quoi ressemble une structure de pagination ?
Une structure de pagination est un bloc de mémoire comprenant une multitude d'enregistrements de 8 octets. Cette structure est une structure à plusieurs niveaux :
- Une PLM4E qui pointe vers 512 PDPE
- Des PDPE qui pointent vers 512 PDE
- Des PDE qui pointent vers 512 PTE
- Des PTE qui pointent vers 512 pages en mémoire
Si nous devions utiliser toute cette structure, c'est à dire 512 PLM4E, 512 PDPE et 512 PDE, nous aurions besoin d'une structure de pagination de 128Mo ! Heureusement, nous n'allons utiliser qu'une seule PLM4E et une seule PDPE. La structure fait donc 2Mo, ce qui est justement la taille d'une page. La gestion de la mémoire n'en sera que simplifiée.
Comment construire cette structure ?
Pour construire cette structure, nous ne devons que créer une PLM4E, une PDPE et 512 PDE (boucle for() en C). Nous devrons alors simplement mapper quelques pages. Voici le code qui se charge de créer la structure :
Code : cmemset((void *)(0x100000-0x10000), 0, 2*1024*1024); // On efface préalablement la mémoire
crtPLM4EPDPE(7, 100, 0, monPLM4E); // On crée l'unique PLM4E qui pointe vers la PDPE
for (i=0; i<511; i++) {
crtPLM4EPDPE(7, 101+i, 0,(int64 *)((0x100000-0x10000)+(i*8))); // On crée la PDPE qui contient 512 PDE
}
crtPDE(391 , 2, 0, (int64 *)(0x101008-0x10000)); // On mappe les premières pages de la mémoire en
crtPDE(391 , 4, 0, (int64 *)(0x101010-0x10000)); // Sautant la première, pour éviter les pointeurs NULL
crtPDE(391 , 6, 0, (int64 *)(0x101018-0x10000)); //
crtPDE(391 , 8, 0, (int64 *)(0x101020-0x10000)); //
Voilà, la mémoire est initialisée. Il ne faut plus qu'y charger kernel.ext
Etape 2: Charger Logram\sys64\Kernel.ext
Voici le plus dur. Le chargement en lui-même est simple, mais il faut d'abord une bonne explications du format FSL. Ensuite, il faudra trouver comment lire des secteurs sur le disque dur sans passer par le BIOS.
Qu'est-ce que le FSL ?
Le FSL est le système de fichiers utilisé par Logram. Il est simple, rapide et puissant. Ce qui fait que le FSL est simple, c'est qu'il n'utilise pas de MFT, ni de FAT. Tout le disque est géré grâce à des blocs. Ces blocs font une certaine quantité de secteurs. Chaque bloc commence par un secteur qui contient des informations sur ce bloc, comme par exemple l'adresse du bloc suivant.
Lecture d'un fichier
Vous l'aurez compris, lire un fichier est simple, il suffit de commencer par le premier bloc et de continuer avec les blocs suivant, de proche en proche, d'adresse en adresse, jusqu'à ce que le fichier soit lu jusqu'au bout.
Mine de rien, nous avons déjà notre première fonction : fsl_read. La voici :
Code : cvoid fsl_read(int baseblock, void *buf){
/*
Pour lire un fichier:
- Pour chaque bloc du fichier (commencer à baseblock)
- Lire l'adresse du bloc suivant
- Pour chaque secteur
- le lire et le placer dans le buffer
- si ce secteur était le dernier quitter
- Fin boucle
- Fin boucle
*/
volatile int numblk, suivblk, i;
volatile FSL_BLOCK *blk;
volatile void *mbuf;
numblk = baseblock;
while (1) {
//lire l'en-tête du bloc (adresse d'un secteur dans un bloc = 2048+(numblk*SC)+numsect.
readsector((void *) &bufsect, 2048+(numblk*disk.blocsize)+0);
blk = (FSL_BLOCK *) &bufsect
suivblk = blk->follow;
//pour chaque secteur
for (i=1; i<32; i++) { //for (i=1; i<disk.blocsize; i++) {
//printd("boucle for");
//lire le secteur et le placer dans le buffer
readsector((void *) mbuf, 2048+(numblk*((int) disk.blocsize))+i);
}
//passer au bloc suivant, s'il existe
numblk = suivblk;
//Si il n'y a pas de boc suivant, quitter
if (numblk==0) return;
}
}
Maintenant, il ne nous reste plus qu'à trouver le premier bloc de ce fichier. C'est simple, c'est noté dans d'autres fichiers, les répertoires.
Lire le contenu d'un répertoire
Un répertoire est une suite de bloc, comme un fichier, à la différence qu'à la place de données, un répertoire contient une suite de secteurs, qui décrivent les fichiers contenus dedans. Pour trouver le premier bloc d'un fichier, il nous suffit de lire son répertoire parent, et de trouver le bon fichier dedans. Voici donc la fonction dérivée de fsl_read qui fait cela (attention, elle est plus longue) :
Code : cint fsl_open(int baseblock, const unsigned short *filename){
/*
Pour ouvrir un fichier:
- Pour chaque bloc du dossier parent
- Lire l'id de bloc suivant
- Pour chaque secteur
- Lire le nom du fichier
- Le comparer à filename
- si c'est bon, retourner le bloc contenant le fichier
- Fin boucle
- Fin boucle
*/
volatile int numblk, suivblk, k;
volatile FSL_BLOCK *blk;
FSL_FILE *file;
unsigned char foundname[228];
numblk = baseblock;
//printd("1");
while (1) {
//lire l'en-tête du bloc (adresse d'un secteur dans un bloc = 2048+(numblk*SC)+numsect.
readsector((void *) &bufsect, 2048+(numblk*disk.blocsize)+0);
blk = (FSL_BLOCK *) &bufsect
suivblk = blk->follow;
//pour chaque secteur
//printd("2");
for (k=1; k<disk.blocsize; k++) {
//printd("3");
//lire le nom de fichier
readsector((void *) &bufsect,2048+ (numblk*disk.blocsize)+k);
file = (FSL_FILE *) &bufsect
if (strcmp(file->filename, filename)) {
print("File found");
//on a trouvé le bon fichier !
return file->startblock; //remarquez que bufsect contient encore l'enregistrement, ça pourrait servir.
}
else if (file->filename[0] != 0);
{
wchartochar(foundname,file->filename);
print(foundname);
}
}
//passer au bloc suivant, s'il existe
numblk = suivblk;
if (numblk == 0) {
//pas de bloc suivant, on est à la fin du répertoire
print("File cannot be found :");
print((char *) filename);
asm("hlt");
for(;;); //boucle infinie.
}
}
}
Maintenant que nous savons lire des répertoire et des fichiers, comment l'utiliser ?
Ouvrir un chemin d'accès
Un chemin d'accès comme Logram\sys64\kernel.ext peut être décomposé en différentes parties :
- Logram est le nom du premier dossier à ouvrir dans le dossier principal ( / dans Linux). Dans Logram, il n'est pas représenté.
- sys64 est un sous dossier dans Logram. Une fois Logram ouvert, il va donc falloir lire l'enregistrement de sys64.
- Kernel.ext est le fichier à charger
Nous allons donc ouvrir
Logram dans le répertoire principal, puis ouvrir
sys64 dans
Logram, et enfin
kernel.ext dans
sys64. C'est réalisé avec ce tout petit code qui utilise nos fonctions précédentes :
Code : cblk = fsl_open(0, L"Logram"); // des L car ça doit être en unicode.
blk = fsl_open(blk, L"sys64");
blk = fsl_open(blk, L"kernel.ext");
fsl_read(blk, (void *)0x800000-0x10000);
Voilà, notre fichier est ouvert.
Remarque : la lecture sur un disque IDE
Vous avez pu remarquer que nous avons utilisé la fonction readsector, pour lire un secteur sur le disque principal de l'ordinateur. Cette fonction a été récupérée de KOS, un autre OS libre, et adaptée à Logram. Pas besoin de commentaires, elle est simple, courte, et tout se trouve dans les spécifications ATAPI/7. (voir wikipédia).
Code : cvoid readsector(void* buf, int block)
{
volatile unsigned char cyl_lo, cyl_hi, sect, head, status;
volatile int devselect, i, timeout;
volatile int16* buffer;
devselect = ATA_D_MASTER;
/* Ask the controller to NOT send interrupts to acknowledge
commands */
outb(0x1F0 + ATA_DEVICE_CONTROL, ATA_A_nIEN | ATA_A_4BIT);
udelay(1);
sect = (block & 0xff);
cyl_lo = (block >> 8) & 0xff;
cyl_hi = (block >> 16) & 0xff;
head = ((block >> 24) & 0x7) | 0x40;
/* Select device */
outb(0x1F0 + ATA_DRIVE, ATA_D_IBM | devselect);
udelay(100);
/* Write to registers */
outb(0x1F0 + ATA_DEVICE_CONTROL, ATA_A_4BIT);
outb(0x1F0 + ATA_ERROR, 1);
outb(0x1F0 + ATA_PRECOMP, 0);
outb(0x1F0 + ATA_SECTOR_COUNT, 1);
outb(0x1F0 + ATA_SECTOR_NUMBER, sect);
outb(0x1F0 + ATA_CYL_LSB, cyl_lo);
outb(0x1F0 + ATA_CYL_MSB, cyl_hi);
outb(0x1F0 + ATA_DRIVE, (ATA_D_IBM | devselect | head));
/* Send the command, either read or write */
outb(0x1F0 + ATA_CMD, ATA_C_READ);
/* Wait for command completion (wait while busy bit is set) */
for(timeout = 0; timeout < 30000; timeout++)
{
status = inb(0x1F0 + ATA_STATUS);
if(!(status & ATA_S_BSY))
break;
udelay(1);
}
/* copy data from the controller internal buffer to the buffer
provided by the user */
buffer = (int16 *) buf;
for (i = 0 ; i < 256 ; i++) buffer [i] = inw (0x1F0 + ATA_DATA);
}