GDT 全局描述表
归功于GRUB,你的内核不再是 real mode(实模式),而是处于protected mode(保护模式),该模式允许我们使用微处理器的所有能力,比如 虚拟内存管理,分页,安全的多任务。
GDT 是什么?
GDT ("Global Descriptor Table" 全局描述表) 是一个用来定义不同内存区的数据结构,其包括:基地址,大小,访问特权(比如执行和写),这些区域被称为 "segments(段)"。
我们将使用GDT定义不同的内存段:
为了保留作者的原义,保留了该段原文
- "code": kernel code, used to stored the executable binary code
- "data": kernel data
- "stack": kernel stack, used to stored the call stack during kernel execution
- "ucode": user code, used to stored the executable binary code for user program
- "udata": user program data
- "ustack": user stack, used to stored the call stack during execution in userland
中文解释
- "code": 内核代码,用来存储可执行二进制代码
- "data": 内核数据
- "stack": 内核栈,用来存储内核执行的调用栈
- "ucode": 用户代码,用来存储用户可执行程序的二进制代码
- "udata": 用户程序数据
- "ustack": 用户栈,用来存储用户态执行的调用栈
如何加载GDT?
GRUB初始化一个GDT,但这个GDT不属于我们的内核。 GDT是使用 LGDT(加载全局描述符) 汇编指令 加载的。 LGDT需要一个GDT描述结构的位置。
全局描述符表寄存器GDTR
GDTR寄存器中用于存放全局描述符表GDT的32位的线性基地址和16位的表限长值。基地址指定GDT表中字节0在线性地址空间中的地址,表长度指明GDT表的字节长度值。指令LGDT和SGDT分别用于加载和保存GDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。在保护模式初始化过程中必须给GDTR加载一个新值。
全局描述符表寄存器(GDTR):
C 结构体:
struct gdtr {
u16 limite;
u32 base;
} __attribute__ ((packed));
注意: 指令 __attribute__ ((packed))
用来告诉gcc,该结构体应该在内存中紧凑排布。没有该指令,gcc将包含一些用来优化执行期间访问内存对齐的字节。
现在我们需要定义我们自己的GDT,然后使用LGDT加载它。GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。
GDT table
是如下段描述符结构体组成的数组:
C 结构体:
struct gdtdesc {
u16 lim0_15;
u16 base0_15;
u8 base16_23;
u8 acces;
u8 lim16_19:4;
u8 other:4;
u8 base24_31;
} __attribute__ ((packed));
如何定义我们的GDT table?
我们应该在内存中定义我们的GDT,然后使用 LGDT指令
加载到GDTR寄存器
。
我们将GDT存储在内存如下地址:
#define GDTBASE 0x00000800
x86.cc 中的函数 init_gdt_desc 初始化一个GDT段描述符:
void init_gdt_desc(u32 base, u32 limite, u8 acces, u8 other, struct gdtdesc *desc)
{
desc->lim0_15 = (limite & 0xffff);
desc->base0_15 = (base & 0xffff);
desc->base16_23 = (base & 0xff0000) >> 16;
desc->acces = acces;
desc->lim16_19 = (limite & 0xf0000) >> 16;
desc->other = (other & 0xf);
desc->base24_31 = (base & 0xff000000) >> 24;
return;
}
函数 init_gdt 初始化GDT,下面涉及的其它函数后面将做解释,主要是用来多任务。
void init_gdt(void)
{
default_tss.debug_flag = 0x00;
default_tss.io_map = 0x00;
default_tss.esp0 = 0x1FFF0;
default_tss.ss0 = 0x18;
/* initialize gdt segments */
init_gdt_desc(0x0, 0x0, 0x0, 0x0, &kgdt[0]);
init_gdt_desc(0x0, 0xFFFFF, 0x9B, 0x0D, &kgdt[1]); /* code */
init_gdt_desc(0x0, 0xFFFFF, 0x93, 0x0D, &kgdt[2]); /* data */
init_gdt_desc(0x0, 0x0, 0x97, 0x0D, &kgdt[3]); /* stack */
init_gdt_desc(0x0, 0xFFFFF, 0xFF, 0x0D, &kgdt[4]); /* ucode */
init_gdt_desc(0x0, 0xFFFFF, 0xF3, 0x0D, &kgdt[5]); /* udata */
init_gdt_desc(0x0, 0x0, 0xF7, 0x0D, &kgdt[6]); /* ustack */
init_gdt_desc((u32) & default_tss, 0x67, 0xE9, 0x00, &kgdt[7]); /* descripteur de tss */
/* 初始化全局描述符表寄存器gdtr结构体 */
kgdtr.limite = GDTSIZE * 8;
kgdtr.base = GDTBASE;
/* 将kgdt数组 拷贝到了 起始地址为 GDTBASE 的内存区 */
memcpy((char *) kgdtr.base, (char *) kgdt, kgdtr.limite);
/* 加载到GDTR寄存器 */
asm("lgdtl (kgdtr)");
/* initiliaz the segments */
asm(" movw $0x10, %ax \n \
movw %ax, %ds \n \
movw %ax, %es \n \
movw %ax, %fs \n \
movw %ax, %gs \n \
ljmp $0x08, $next \n \
next: \n");
}
译者注: