6.9捕获0异常
初始化IDT表
发生异常,系统会先去查IDT表,然后找到对应的异常表项(疑问:系统是怎么分类异常的?)
在对应的异常表项中取得对应的选择子
我们要初始化这个IDT表,给其填入表项内容,然后发生除0异常时候,可以自动跳入那个表项
//init.c
void kernel_init (boot_info_t * boot_info) {
// 初始化CPU,再重新加载
cpu_init(); //初始化GDT表
irq_init(); //初始化IDT表
}
//irq.c->irq_init()
void irq_init(void) {
for (uint32_t i = 0; i < IDT_TABLE_NR; i++) {
gate_desc_set(idt_table + i, KERNEL_SELECTOR_CS, (uint32_t) exception_handler_unknown,
GATE_P_PRESENT | GATE_DPL0 | GATE_TYPE_IDT);
}
lidt((uint32_t)idt_table, sizeof(idt_table));
}
gate_desc_set的四个实参解释
idt_table + i:第i个表项的指针
KERNEL_SELECTOR_CS:选择子是代码段
(uint32_t) exception_handler_unknown:异常处理程序的入口地址,相当于偏移量offset,因为KERNEL_SELECTOR_CS是从0地址开始(基地址从0开始)
GATE_P_PRESENT | GATE_DPL0 | GATE_TYPE_IDT:对参数attr进行设置,让其是表示中断门
//设置参数attr的宏
#define GATE_TYPE_IDT (0xE << 8) // 中断32位门描述符
#define GATE_P_PRESENT (1 << 15) // 是否存在
#define GATE_DPL0 (0 << 13) // 特权级0,最高特权级
#define GATE_DPL3 (3 << 13) // 特权级3,最低权限
//cpu.c->gate_desc_set()
void gate_desc_set(gate_desc_t * desc, uint16_t selector, uint32_t offset, uint16_t attr) {
desc->offset15_0 = offset & 0xffff; //低16位的偏移量
desc->selector = selector;
desc->attr = attr;
desc->offset31_16 = (offset >> 16) & 0xffff; //高16位的地址
}
解释一下这四个参数,参考图1
gate_desc_t * desc:结构体数组具体的那一项
uint16_t selector:选择子,我们GDT采用的是平坦模式,所以这里填的是代码段的选择子KERNEL_SELECTOR_CS
uint32_t offset:偏移量,从GDT表中拿到基地址后,通过这个offset来定位到具体的异常处理程序
uint16_t attr:参数,因为cpu有三种门,区别这三种门的关键在这个attr
//gate_desc_t结构体数组,中断描述符结构体
//cpu.h
typedef struct _gate_desc_t {
uint16_t offset15_0;
uint16_t selector;
uint16_t attr;
uint16_t offset31_16;
}gate_desc_t;
//irq.c
static gate_desc_t idt_table[IDT_TABLE_NR]; // 中断描述表
IDT_TABLE_NR是定义的宏为128
idt_table一共256个表项,每个表项64位,也就是8字节,其中0起始的那个表项系统自己占用了,所以我们从1开始用
//irq.c
void exception_handler_unknown (void); //中断处理程序的入口地址
//start.S
.text
.extern do_handler_unknown
.global exception_handler_unknown
exception_handler_unknown:
// 保存所有寄存器
pusha // 保存通用寄存器
// 保存段寄存器
push %ds
push %es
push %fs
push %gs
call do_handler_unknown
// 恢复保存的寄存器
pop %gs
pop %fs
pop %es
pop %ds
popa
iret //中断返回指令,所以不能用c语言去写
为什么不直接写个c函数?
因为c函数汇编以后只能汇编出ret,但是我们这里的是中断返回,要用iret(内中断要返回到程序出错的那条指令继续执行)
中断我们要保护现场
有些是系统自动保存的(我们在王道里面学过,PC,PSW(这里面的EFLAGS)之类的系统自动保存,难道CS表示PC里面的内容吗?)
//start.S
.text
.extern do_handler_unknown
.global exception_handler_unknown
exception_handler_unknown:
// 保存所有寄存器
pusha // 保存通用寄存器
// 保存段寄存器
push %ds
push %es
push %fs
push %gs
call do_handler_unknown
// 恢复保存的寄存器
pop %gs
pop %fs
pop %es
pop %ds
popa
iret //中断返回指令,所以不能用c语言去写
pusha保存的是通用寄存器中的内容
那下面push一连串的是保存段寄存器中的内容
保存完以后,在执行完do_handler_unknown这个中断处理程序以后,要记得还原现场pop那一串
lidt指令
加载ldt表项的
将自己定义的idt_table加载进IDTR
//cpu_instr.h
static inline void lidt(uint32_t start, uint32_t size) {
struct {
uint16_t limit;
uint16_t start15_0;
uint16_t start31_16;
} idt;
idt.start31_16 = start >> 16;
idt.start15_0 = start & 0xFFFF;
idt.limit = size - 1;
__asm__ __volatile__("lidt %0"::"m"(idt));
}
内联汇编的知识不用深究