IDT
昨天弄完了GDT,今天来弄IDT,IDT就是中断描述符表。和GDT类似。
中断就是一个电信号,它可以打断cpu当前的操作,让cpu执行你指定的中断处理函数,等执行完,cpu会回去继续执行它之前的操作,就像你移动鼠标,就是产生了一个中断,让cpu先帮你移动鼠标。
既然打断cpu让他执行你指定的中断处理函数,那么中断就是有编号的,让cpu知道应该执行哪一个中断处理函数,intel的处理器支持256个中断,也就是有0-255个中断编号
和GDT一样,有中断描述符,大小为8字节,也是放在内存中,由IDTR寄存器保持IDT的位置和大小
include/idt.h(部分)
1 |
|
上面定义了中断描述符的结构体,和IDTR
中断产生的过程
1.cpu在执行完一条语句之后会去看有没有中断产生,如果有则等待对应的时钟信号到来之后去读取中断请求,通过中断向量去IDT中获取该中断的中断描述符。
2.中断描述符有该中断处理函数的段选择子,然后用段选择子去GDT中获得该函数的段基址和属性信息,然后是一些判断
3.判断通过之后cpu会保存当前的现场,就是将一些寄存器的值压入栈中,然后去中断描述符里指定的中断处理函数的地址执行该中断函数
4.执行完成后,cpu将栈中的值拿回来也就是恢复现场,继续之前的任务
由于cpu自动压入栈中的数据是不够的,我们要手动压入一些寄存器的值和一些信息,将这些信息组成一个结构体:
include/idt.h
1 | // 寄存器类型 |
上面定义了一个结构体,里面是要保护的寄存器的值和中断编号什么的信息,然后定义了一个函数指针类型interrupt_handler_t
,这个函数指针类型定义的函数指针指向的是返回值为void,参数为pt_regs *
的函数,也就是我们自己写的中断处理函数的原型。
再下面定义了一系列的中断服务程序,发现这些函数的类型与函数指针定义的类型不一样,因为这些不是中断处理函数,而是要在他们当中调用中断处理函数,它们还要做寄存器的操作。
在执行每一个中断处理函数之前都要进行保护现场,执行完成之后会恢复现场,我们可以把这些相同的操作提取出来,既然涉及到寄存器的修改就上汇编了
idt/idt_s.s
1 | ; 定义两个构造中断处理函数的宏(有的中断有错误代码,有的没有) |
首先上面定义了两个宏用来生成中断服务程序,也就是之前头文件声明的那些void isr1()
,这两个宏指定的参数就一个,然后下面调用这个宏生成中断服务程序,一共调用了33次,生成了33个不同的中断服务程序.(调用宏就是把调用的第一个参数替换宏中%1的位置)
宏最后的jump就是跳转到每一个中断服务程序公共的部分,也就是保护现场,调用中断处理函数,恢复现场这些操作,其中call isr_handler
调用的是c写的函数
idt/idt.c
1 | // 调用中断处理函数 |
这个函数的参数也是pt_regs *regs
,x86下汇编调用c的函数参数传递是通过堆栈进行的,在上面的汇编代码中将需要保护的寄存器的数据和错误代码,中断号等数据已经压入栈中了,这时候的栈指针esp就相当于pt_regs *regs
,所以将esp压入栈中传递给c的函数
在该函数中interrupt_handlers
是一个数组,数组元素是中断处理函数指针,所有这个函数做的事就是判断当前产生的这号中断有没有注册中断处理函数,若有就调用它,没有就输出提示信息,那么相应的注册中断处理函数的函数就是
idt/idt.c
1 | // 注册一个中断处理函数 |
就是将函数指针放入对应的中断号位置
最后是中断描述符表的创建和加载
idt/idt.c
1 |
|
和GDT类似,这里先将保存中断处理函数指针的数组初始化为0,然后初始化IDTR,和GDT不同的是,GDT的数组长度是我们指定的,而IDT的数组长度是256,因为一共有256个中断描述符,再然后将之前定义的33个中断服务程序写入IDT,最后更新IDTR寄存器。
idt/idt_s.s
1 | [GLOBAL idt_flush] |
测试一下,修改init/entry.c
1 |
|