5.9 GDT与IDT的初始化(harib02i)
5.9 GDT与IDT的初始化(harib02i)
CPU用8字节来表示一个段的1.段的大小2.段的起始地址3.段的管理属性(禁止写入,禁止执行,系统专用)信息,但8字节(64位)远大于段寄存器(16位),故用段寄存器来存储段号,由于16位中的低三位不能用,故只有0~8191的区域,即8192个段,8192*8=68836字节(64kb),64kb的数据CPU无法储存,故存储在内存中,这64kb的数据即为GDT(global(segment) descriptor table)全局段号记录表,而为了使用GDT方便,便将GDT的首地址和有效设定个数存入GDTR(global(segment) descriptor table register)特殊寄存器中
- GDT:global(segment) descriptor table 全局段号记录表
-
段寄存器:16位
-
IDT:interrupt descriptor table 中断记录表 IDT记录了0~255的中断号码与调用函数的对应关系,其设定方法与GDT相似(也许是因为使用同样方法可以简化CPU电路)
注意:设定IDT(interrupt descriptor table)最好先于设定GDT(global(segment) descriptor table),否则会比较麻烦
/*SEGMENT_DESCRIPTOR与GATE_DESCRIPTOR一样,均是以CPU的资料为基础,写成结构体的形式*/
struct SEGMENT_DESCRIPTOR {//8字节=2*2字节+2*1字节+2*1字节
short limit_low, base_low;//short类型占两字节
char base_mid, access_right;
char limit_high, base_high;
};
struct GATE_DESCRIPTOR {//8字节=2*2字节+2*1字节+1*2字节
short offset_low, selector;//short类型占两字节
char dw_count, access_right;
short offset_high;
};
void init_gdtidt(void)
{
/*表明要使用内存0x00270000~0x0027ffff的65535B设为GDT,原因是这一块内存未被使用*/
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
/*表明要使用0x0026f800~0x00270000的2048B设为IDT,这也应证了“有256个中断号码,用8B表示每个中断对应的信息”*/
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800;
int i;
/* global(segment) descriptor table 全局短号记录表*/
/* GDT初始化 */
for (i = 0; i < 8192; i++) {/*此处要注意C语言进行指针的加法运算时,内部隐含着乘法运算*/
set_segmdesc(gdt + i, 0, 0, 0);
}
/*设定,段号为1的段,上限值为0xffffffff(即大小正好是4GB),地址是0,它表示的是CPU所能管理的
全部内存本身。段的属性设为0x4092,它的含义我们留待明天再说*/
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
/*段号为2的段,它的大小是512KB,地址是0x280000。这正好是为bootpack.hrb而准备的。这个段可以执行bootpack.hrb。因为bootpack.hrb是以ORG 0为前提翻译成的机器语言。*/
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
load_gdtr(0xffff, 0x00270000);/*同下方的load_idtr(0x7ff, 0x0026f800);,C语言不能给GDTR赋值*/
/* interrupt descriptor table 中断记录表*/
/* IDT初始化 */
for (i = 0; i < 256; i++) {
set_gatedesc(idt + i, 0, 0, 0);
}
load_idtr(0x7ff, 0x0026f800);
return;
}
//上限(limit,指段的字节数-1),基址(base),访问权限
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
if (limit > 0xfffff) {
ar |= 0x8000; /* G_bit = 1 */
limit /= 0x1000;
}
sd->limit_low = limit & 0xffff;
sd->base_low = base & 0xffff;
sd->base_mid = (base >> 16) & 0xff;
sd->access_right = ar & 0xff;
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
sd->base_high = (base >> 24) & 0xff;
return;
}
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
gd->offset_low = offset & 0xffff;
gd->selector = selector;
gd->dw_count = (ar >> 8) & 0xff;
gd->access_right = ar & 0xff;
gd->offset_high = (offset >> 16) & 0xffff;
return;
}
此处将6.4中的内容提前到此处
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6] ;将从地址[ESP+6]起的6字节数据赋值给GDTR寄存器
RET
_load_gdtr函数的功能是将段上限(limit)和地址值赋值给名为GDTR(global(segment) descriptor table register)的48 位寄存器。注意:GDTR不能用正常的MOV指令进行赋值,只能使用LGDT指令
关于GDTR寄存器,其低16位(即内存的最初2个字节)是段上限(即GDT 的有效字节数 - 1),剩下的高32位, 代表GDT的开始地址。
经过上图的变换,即可完成写入GDT的任务。
注意:从ESP到ESP+3的四字节空间应该是被函数_load_gdtr的什么属性占据了,查明资料后补充
struct SEGMENT_DESCRIPTOR {//8字节=2*2字节+2*1字节+2*1字节
short limit_low, base_low;//short类型占两字节
char base_mid, access_right;
char limit_high, base_high;
};
结构体中base分为low(2字节),mid(1字节),high(1字节) 3段,合起来刚好是32位。分三段是为了与80286时代的程序兼容,这样80286用的操作系统,可以不用修改就在386以后的CPU上运行了。
//上限(limit,指段的字节数-1),基址(base),访问权限
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
if (limit > 0xfffff) {
ar |= 0x8000; /* G_bit = 1 */
limit /= 0x1000;
}
sd->limit_low = limit & 0xffff;
sd->base_low = base & 0xffff;
sd->base_mid = (base >> 16) & 0xff;
sd->access_right = ar & 0xff;
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
sd->base_high = (base >> 24) & 0xff;
return;
}
也正因为是三段,所以以上set_segmdesc函数中需要使用移位运算符和AND运算符来填入数据
由于段上限最大是4GB,即一个32位(4字节)数值,所以段大小占4字节,基址(base 起始地址)再占4字节,则段的管理属性没地方保存。若想保存段的管理属性,则段的大小只能使用20位,则可以使用的段上限缩小为1MB。故因特尔的解决办法是给段增加一个属性——Gbit,该标志位为1时,将limit的单位解释为页(4KB),1M*4KB=4GB,则不会产生浪费。因此20位的段上限写到limit_low(2字节)和limit_high(1字节)的低四位中,limit_high(1字节)的高四位写入段属性(还有一部分在access_right)。
access_right或ar的高4位(即limit_high(1字节)的高四位)被称为“扩展访问权”,因为它在80286时代不存在,386后才可以用。这四位由“GD00”构成,G为上文中的Gbit标志位,D为段的模式(1是指32位模式,0是指16位模式),这里的16位模式主要只用于运行 80286的程序,不能用于调用BIOS。所以,除了运行80286程序以外, 通常都使用D=1的模式。
access_right或ar的低4位在80286时代就已经有了,下面简单介绍以下
00000000(0x00):未使用的记录表(descriptor table)。
10010010(0x92):系统专用,可读写的段。不可执行。
10011010(0x9a):系统专用,可执行的段。可读不可写。
11110010(0xf2):应用程序用,可读写的段。不可执行。
11111010(0xfa):应用程序用,可执行的段。可读不可写。
32位模式下,CPU有系统模式(也称为“ring0”)和应用模式(也称为“ring3”)
举例:在应用模式下不允许执行LGDT指令(从指定的地址读取6个字节(48位),然后赋值给GDTR),一旦可以执行LGDT便可以修改GDTR,从而对GDT进行修改
struct SEGMENT_DESCRIPTOR {//8字节=2*2字节+2*1字节+2*1字节
short limit_low, base_low;//short类型占两字节
char base_mid, access_right;
char limit_high, base_high;
};