include/gdt.h
est un fichier d'entete qui definit les structures necessaires
a la manipulation de la GDT.
On commence par definir l'adresse ou doit resider la GDT ainsi
que sa taille :
#define GDTBASE 0x800 /* addr. physique ou doit resider la gdt */
#define GDTSIZE 0xFF /* nombre max. de descripteurs dans la table */
Puis on definit une structure qui correspond aux descripteurs de segment et
une autre qui correspond aux donnees a charger dans le registre GDTR. La directive
__attribute__ ((packed)) indique a gcc qu'une structure doit
occuper le moins de place possible en memoire. Si on omet cette directive, il
y a des chances que le compilateur insere des octets entre les champs
de la structure afin de les aligner pour optimiser la vitesse d'acces. Cette
optimisation du compilateur est ici enormement genante car
on s'attend a ce que chaque structure decrite ci-dessous corresponde
a la meme chose en memoire :
/* descripteur de segment */
typedef struct {
u16 lim0_15;
u16 base0_15;
u8 base16_23;
u8 acces;
u8 lim16_19 : 4;
u8 other : 4;
u8 base24_31;
} gdtdesc __attribute__ ((packed));
/* registre GDTR */
struct gdtr {
u16 limite ;
u32 base ;
} __attribute__ ((packed));
struct gdtr kgdtr;
On definit ensuite la table de GDT. On met les champs a zero pour
que soit initialise le descripteur nul :
/* table de GDT */
gdtdesc kgdt[GDTSIZE] = {0, 0, 0, 0, 0, 0, 0};
La variable kgdtptr correspond au nombre de descripteur initialises
dans la GDT. Cette variable commence a 1 car le descripteur nul est initialise :
/* pointeur sur un descripteur libre dans la GDT */
unsigned int kgdtptr = 1;
kern/gdt.c
contient les fonctions qui permettent d'initialiser et de manipuler
des descripteurs de segment et la GDT.
La fonction 'init_gdt' fait un travail vraiment important. Au boot,
le programme du MBR commute le PC en mode protege afin de pouvoir
charger et executer un noyau 32 bits. Le probleme est que la GDT initialisee
par le secteur de boot ne correspond pas forcement a celle que l'on souhaite
pour le noyau. Par exemple, si on boote Bosokernel a l'aide de LILO ou d'un
autre boot loader, on ne sait pas a l'avance ou sera la GDT ni comment elle
sera constituee. Un noyau doit donc creer sa propre GDT, et c'est ici la
fonction 'init_gdt' qui fait cela.
La fonction 'init_gdt' commence par initialiser les descripteurs de segment
pour le code, les donnees et la pile :
/*
* Cette fonction initialise la GDT apres que le kernel soit charge
* en memoire. Une GDT est deja operationnelle, mais c'est celle qui
* a ete initialisee par le secteur de boot et qui ne correspond
* pas forcement a celle que l'on souhaite pour bosokernel.
*/
void init_gdt(void) {
gdtdesc code, data, stack;
/* initialisation des descripteurs de segment */
init_code_desc(0x0, 0xFFFFF, &code);
init_data_desc(0x0, 0xFFFFF, &data);
init_gdt_desc(0, 0x10, 0x97, 0x0D, &stack);
add_gdt_desc(code);
add_gdt_desc(data);
add_gdt_desc(stack);
Les descripteurs sont
places dans le tableau 'kgdt' situe quelque part en memoire et une fois que
le tableau est correctement rempli, on le recopie a l'endroit en memoire ou
il doit resider (definit par GDTBASE). On initialise la structure 'kgdtr'
puis on effectue le changement de GDT en chargeant cette structure dans
le registre GDTR. On met a jour les selecteurs de segments de donnees (ds, es, fs
, gs et ss) puis on fait un "long jump" pour mettre a jour le selecteur
du segment de code (cs) :
/* initialisation de la structure pour GDTR */
kgdtr.limite = GDTSIZE*8;
kgdtr.base = GDTBASE;
/* recopie de la GDT a son adresse */
memcopy(kgdt, kgdtr.base, kgdtr.limite);
/* chargement du registre GDTR */
asm("lgdtl (kgdtr)");
/* initialisation des segments */
asm(" movw $0x10,%ax \n \
movw %ax, %ds \n \
movw %ax, %es \n \
movw %ax, %fs \n \
movw %ax, %gs \n \
movw $0x18,%ax \n \
movw %ax, %ss \n \
movl $0x1FFFF,%esp \n \
nop \n \
nop \n \
ljmp $0x08,$next \n \
next: \n");
}