寒假OS学习第五天
Hurlex学习——添加中断描述符表
中断是事件的基础。
在多道程序运行的时候,依靠中断来切换CPU的工作状态
软件引发的中断叫陷阱
当某个中断发生时,典型的处理方式就是CPU会打断当前的任务,保留当前的执行现场后再转移到该中断事先安排好的中断处理函数
中断由中断控制器产生,通过两条中断线和CPU相连
因此,不同的设备的中断有编号
CPU的中断有两类——非屏蔽中断(NMI)和屏蔽中断(INTR)
非屏蔽中断的意思是需要进行无条件立即处理,意味着CPU遇到了不可挽回的错误
我们关注的是屏蔽中断
Intel处理器允许256个中断,中断号范围0-255
CPU在执行完每一条指令后,都会确认刚才是否发送了中断
有,则CPU会读取对应的中断向量,根据得到的中断向量,到IDT中寻找对应的中断描述符,中断描述符中保存着中断函数的段选择子
使用查到的段选择子,到GDT中选取对应的段描述符
段描述符中保存了中断处理函数的段基址和属性
此时,CPU进行特权检测,确认无误后开始保存当前被打断的程序的现场(需要内核),压入当前程序使用的EFLAGS、CS、EIP与错误代码号
然后,CPU跳转到中断函数处
处理结束后,使用iret或者iretd恢复现场
而错误代码需要中断处理函数主动弹出
在这个过程中CPU的工作是:
- 实现中断处理函数。0-19号中断是CPU所有,20-31号中断是保留中断,32-255才是用户自定义中断。但是也有一些约定成俗的中断。
保护模式下的中断
中断的处理运行在ring0层,意味着中断处理程序拥有着系统的全部权限
Intel设置了一个中断描述符表(IDT),和段描述符一样放在贮存里面
有一个中断描述符表寄存器(IDTR)来存储这张表
中断描述符表结构
首先是中断描述符表的结构
可以看到和GDT很相似
设计其数据结构:
#ifndef INCLUDE_IDT_H_
#define INCLUDE_IDT_H_
typedef struct
{
uint16_t func_addr_low;
uint16_t sec_sel; // section selector
uint8_t zero;
uint8_t flags;
uint16_t func_addr_high;
}idt_t;
typedef struct
{
uint16_t limit;
uint32_t base;
}__attribute__((packed))idtr_t;
#endif
中断处理函数
CPU在中断产生时会自动保存EIP、EFLAGS和CS等,但是其他的寄存器需要内核去进行保护
所有中断处理函数中寄存器的保存和恢复过程时一样的,因此将中断处理函数拆分成3个部分:
- 现场保护
- 特有逻辑
- 现场恢复
由处理器自动压入的寄存器有:
- 错误代码
- EIP
- CS
- EFLAGS
- USERESP
- SS
查阅Intel手册得到更多信息。
再次梳理一下流程:
CPU检查到中断->
CPU自动保存一部分现场。若有错误代码,则CPU自动保存错误代码->
CPU调用内核中断处理函数,此时CS改为内核段->
内核中断处理函数保存CPU尚未保存的现场,并加载内核数据段描述符表(毕竟CPU不干这事)->
内核中断函数调用函数处理逻辑->
处理完毕了,内核中断函数恢复现场->
内核中断函数使用iret
退出,CPU自动恢复它的那部分现场
我们的内核处理函数从保护现场开始
由于有些中断还会附带错误码,有些没有,为了方便我们入栈出栈,使用两个函数做区别:
[GLOBAL idt_flush]
idt_flush:
mov eax, [esp+4]
lidt [eax]
ret
.end:
%macro ISR_ERRCODE 1
[GLOBAL isr_%1]
isr_%1:
cli ; 关闭中断,因为现在正在处理中断
push %1 ; 把中断号推进去
jmp isr_common_stub
%endmacro
%macro ISR_NOERRCODE 1
[GLOBAL isr_%1]
isr_%1:
cli ; 关闭中断,因为现在正在处理中断
push 0 ; 0Errorcode
push %1 ; 把中断号推进去
jmp isr_common_stub
%endmacro
ISR_ERRCODE 8 ; 双重故障
ISR_ERRCODE 10 ; 无效TSS
ISR_ERRCODE 11 ; 段不存在
ISR_ERRCODE 12 ; 栈错误
ISR_ERRCODE 13 ; 常规保护
ISR_ERRCODE 14 ; 页故障
ISR_ERRCODE 17 ; 对齐检查
ISR_NOERRCODE 0 ; 除0异常
ISR_NOERRCODE 1 ; 调试异常
ISR_NOERRCODE 2
ISR_NOERRCODE 3 ; 断点异常
ISR_NOERRCODE 4 ; 一簇合
ISR_NOERRCODE 5 ; 对数组的引用超出边界
ISR_NOERRCODE 6 ; 无效的操作码
ISR_NOERRCODE 7 ; 设备不可用
ISR_NOERRCODE 9 ; 协处理器跨段
ISR_NOERRCODE 15 ; 保留
ISR_NOERRCODE 16 ; 浮点处理单元错误
ISR_NOERRCODE 18 ; 机器检查
ISR_NOERRCODE 19 ; 浮点异常
; CPU保留
ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_NOERRCODE 30
ISR_NOERRCODE 31
; 系统调用
ISR_NOERRCODE 255
[EXTERN isr_handler]
isr_common_stub:
pusha ; 把edi esi ebp esp ebx edx ecx eax压进去
mov ax, ds
push eax ; 把ds压进去
push esp ; 这个时候的esp相当于这个结构体的指针
mov ax, 0x10 ; 内核数据段描述表
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
call isr_handler
add esp, 4 ; 清除压进去的参数
pop ebx ; 把ds放在ebx里面
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
mov ss, bx
popa
add esp, 8 ; 清除error code和ISR
iret
.end:
具体函数调用流程为:
ISR_ERRCODE/ISR_NOERRCODE->
ISR_COMMON_STUB->
ISR_HANDLER->
具体逻辑
这里使用了NASM的宏操作
接着,我们完善C语言代码
#include "idt.h"
#include "debug.h"
#include "string.h"
#define MAX_IDT_LEN 256
extern void idt_flush(uint32_t);
static idt_t idt[MAX_IDT_LEN];
static idtr_t idtr;
static interrupt_handler_func_t interrupt_handlers[MAX_IDT_LEN];
static void idt_set(uint32_t num, uint32_t func_addr,
uint8_t sec_sel, uint8_t flags);
void
init_idt()
{
// 初始化idt
memset(idt, 0, sizeof(idt_t) * MAX_IDT_LEN);
memset(interrupt_handlers, 0, sizeof(interrupt_handler_func_t) * MAX_IDT_LEN);
// 初始化idtr
idtr.limit = sizeof(idt_t) * MAX_IDT_LEN - 1;
idtr.base = (uint32_t)idt;
idt_set(0, (uint32_t)isr_0, 0x08, 0x8E);
idt_set(1, (uint32_t)isr_1, 0x08, 0x8E);
idt_set(2, (uint32_t)isr_2, 0x08, 0x8E);
idt_set(3, (uint32_t)isr_3, 0x08, 0x8E);
idt_set(4, (uint32_t)isr_4, 0x08, 0x8E);
idt_set(5, (uint32_t)isr_5, 0x08, 0x8E);
idt_set(6, (uint32_t)isr_6, 0x08, 0x8E);
idt_set(7, (uint32_t)isr_7, 0x08, 0x8E);
idt_set(8, (uint32_t)isr_8, 0x08, 0x8E);
idt_set(9, (uint32_t)isr_9, 0x08, 0x8E);
idt_set(10, (uint32_t)isr_10, 0x08, 0x8E);
idt_set(11, (uint32_t)isr_11, 0x08, 0x8E);
idt_set(12, (uint32_t)isr_12, 0x08, 0x8E);
idt_set(13, (uint32_t)isr_13, 0x08, 0x8E);
idt_set(14, (uint32_t)isr_14, 0x08, 0x8E);
idt_set(15, (uint32_t)isr_15, 0x08, 0x8E);
idt_set(16, (uint32_t)isr_16, 0x08, 0x8E);
idt_set(17, (uint32_t)isr_17, 0x08, 0x8E);
idt_set(18, (uint32_t)isr_18, 0x08, 0x8E);
idt_set(19, (uint32_t)isr_19, 0x08, 0x8E);
idt_set(20, (uint32_t)isr_20, 0x08, 0x8E);
idt_set(21, (uint32_t)isr_21, 0x08, 0x8E);
idt_set(22, (uint32_t)isr_22, 0x08, 0x8E);
idt_set(23, (uint32_t)isr_23, 0x08, 0x8E);
idt_set(24, (uint32_t)isr_24, 0x08, 0x8E);
idt_set(25, (uint32_t)isr_25, 0x08, 0x8E);
idt_set(26, (uint32_t)isr_26, 0x08, 0x8E);
idt_set(27, (uint32_t)isr_27, 0x08, 0x8E);
idt_set(28, (uint32_t)isr_28, 0x08, 0x8E);
idt_set(29, (uint32_t)isr_29, 0x08, 0x8E);
idt_set(30, (uint32_t)isr_30, 0x08, 0x8E);
idt_set(31, (uint32_t)isr_31, 0x08, 0x8E);
idt_set(255, (uint32_t)isr_255, 0x08, 0x8E);
idt_flush((uint32_t)&idtr);
}
void
isr_handler(idt_regs_t *regs)
{
if (!interrupt_handlers[regs->int_no])
{
print_color(rc_black, rc_red, "Unhandled iterrupt: %d\n", regs->int_no);
return;
}
interrupt_handlers[regs->int_no](regs);
}
void isr_register(uint32_t num, interrupt_handler_func_t func)
{
interrupt_handlers[num] = func;
}
static void
idt_set(uint32_t num, uint32_t func_addr,
uint8_t sec_sel, uint8_t flags)
{
idt[num].func_addr_low = func_addr & 0x0FFFF;
idt[num].func_addr_high = (func_addr >> 16) & 0x0FFFF;
idt[num].sec_sel = sec_sel;
idt[num].flags = flags;
idt[num].zero = 0x0;
}
init函数仿照GDT的init函数写成。
进行实验:
#include "debug.h"
#include "gdt.h"
#include "idt.h"
interrupt_handler_func_t deal(idt_regs_t regs)
{
print_color(rc_black, rc_blue, "Segment fault!\n");
while(1);
}
int kern_entry()
{
console_clear();
init_debug();
init_gdt();
init_idt();
print_color(rc_black, rc_white, "Hello, OS kernel!\n");
isr_register(0, deal);
int a = 1 / 0;
return 0;
}
输出结果