6.9捕获0异常

6.9捕获0异常

初始化IDT表

发生异常,系统会先去查IDT表,然后找到对应的异常表项(疑问:系统是怎么分类异常的?)

图1:完整jump到异常处理程序流程图

在对应的异常表项中取得对应的选择子

图2:异常表项的详细结构

图3:中间D的地方要填的数据,我们是32位模式所以是1

我们要初始化这个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

图4:三种门
//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(内中断要返回到程序出错的那条指令继续执行)

中断我们要保护现场

图5:保存寄存器内容

有些是系统自动保存的(我们在王道里面学过,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表项的

图6:找中断处理程序的逻辑结构

将自己定义的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));
}

内联汇编的知识不用深究

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值