先给自己提几个问题。
- 中断是什么?中断就是发生了某些事情,计算机暂停正在执行的程序,去执行处理这件事情
- 为什么需要中断?因为发生了更紧急需要处理的事情,所以需要中断暂停。相对前面实现的操作系统最终会到一个while循环中,现在需要实现中断突破while循环。所以之后的系统就是由中断驱动的。
- 如何实现中断?后面再看看怎么实现。
一、中断分类
中断从触发的地方分为:外部中断和内部中断。
外部中断:cpu外部的中断,一般为硬件产生,也称硬件中断
内部中断:软中断和异常。
- 软中断:软件主动发起的中断。int 3等等
- 异常:指令执行期间cpu内部产生的错误引起的
二、中断描述符表
中断描述符表(Interrupt DesciptorTable,IDT)是保护模式下用于存储中断处理程序入口的表,当cpu接收一个中断时,需要用中断向量在此表中检索对应的描述符。
任务门
任务门和任务状态段是Intel处理器在硬件一级提供的任务切换机制。任务门可以存在于全局描述符GDT、局部描述符表LDT、中断描述符表IDT。大多数操作系统不会使用TSS实现任务切换。
中断门
中断门包含了中断处理程序所在的段的段选择子和段内偏移地址。当通过中断进入后,标志寄存器eflags中的IF位自动置0,也就是进入中断后,自动把中断关闭,避免中断嵌套。中断门只允许存在于IDT中。type为二进制1110。
陷阱门
陷阱门与中断门相似。区别是陷阱门进入中断后,标志寄存器eflags中的IF位不会自动置0。陷阱门只允许存在于IDT中。type值为1111。
调用门
调用门是提供给用户进程进入特权0级的方式,其DPL为3。调用门中记录例程的地址,它不能用int指令调用,只能用call和jmp指令。调用门可以安装在GDTheLDT中。type为1100。
中断描述符表寄存器
中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR),该寄存器分为两个部分:0-15位是表界限,16-47位是IDT的基地址。
使用lidt指令加载IDTR
lidt 48位内存地址
三、实现中断
实现中断包含三个步骤
- 创建中断对应的例程
- 初始化中断描述符表
- 异常名初始化并注册通常的中断处理函数
- lidt 加载中断描述符表
创建中断对应的例程
编译器会将.text段合并在一起,把.data段合并。data段中保存的就是中断例程数组。
[bits 32]
%define ERROR_CODE nop ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0
extern idt_table ;idt_table是c中注册的中断处理程序
section .data
global intr_entry_table
intr_entry_table:
%macro VECTOR 2
section .text
intr%1entry: ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少
%2
;保存上下文环境
push ds
push es
push fs
push gs
pushad ; eax,ecx,edx,ebx,esp,ebp,esi,edi
;如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI
mov al,0x20
out 0xa0,al
out 0x20,al
push %1 ;压入中断向量号
call [idt_table+%1*4] ;调用idt_table中的c版本中断处理函数
jmp intr_exit
section .data
dd intr%1entry ; 存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacro
section .text
global intr_exit
intr_exit:
add esp,4 ;跳过中断号
popad
pop gs
pop fs
pop es
pop ds
add esp,4 ;跳过error_code
iretd
VECTOR 0x00,ZERO
VECTOR 0x01,ZERO
VECTOR 0x02,ZERO
VECTOR 0x03,ZERO
VECTOR 0x04,ZERO
VECTOR 0x05,ZERO
VECTOR 0x06,ZERO
VECTOR 0x07,ZERO
VECTOR 0x08,ERROR_CODE
VECTOR 0x09,ZERO
VECTOR 0x0a,ERROR_CODE
VECTOR 0x0b,ERROR_CODE
VECTOR 0x0c,ZERO
VECTOR 0x0d,ERROR_CODE
VECTOR 0x0e,ERROR_CODE
VECTOR 0x0f,ZERO
VECTOR 0x10,ZERO
VECTOR 0x11,ERROR_CODE
VECTOR 0x12,ZERO
VECTOR 0x13,ZERO
VECTOR 0x14,ZERO
VECTOR 0x15,ZERO
VECTOR 0x16,ZERO
VECTOR 0x17,ZERO
VECTOR 0x18,ERROR_CODE
VECTOR 0x19,ZERO
VECTOR 0x1a,ERROR_CODE
VECTOR 0x1b,ERROR_CODE
VECTOR 0x1c,ZERO
VECTOR 0x1d,ERROR_CODE
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO
VECTOR 0x20,ZERO
初始化中断描述符表
根据前面的中断门描述符结构,创建中断门描述符数组。
/*中断门描述符结构体*/
struct gate_desc{
uint16_t func_offset_low_word;
uint16_t selector;
uint8_t dcount;//门描述符中的第4字节。此项为固定值
uint8_t attribute;
uint16_t func_offset_high_word;
};
/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function){
p_gdesc->func_offset_low_word = (uint32_t)function&0x0000FFFF;
p_gdesc->selector = SELECTOR_K_CODE;
p_gdesc->dcount = 0;
p_gdesc->attribute = attr;
p_gdesc->func_offset_high_word = ((uint32_t)function&0xFFFF0000)>>16;
}
/*初始化中断描述符表*/
static void idt_desc_init(void){
int i;
for(i=0;i<IDT_DESC_CNT;i++){
make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
}
put_str(" idt_desc_init done\n");
}
异常名初始化并注册中断处理函数
/* 通用的中断处理函数,一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr){
if(vec_nr == 0x27 || vec_nr==0x2f){
// 0x2f是从片8259A上的最后一个irq引脚,保留
//IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
return;
}
put_str("int vector: 0x");
put_int(vec_nr);
put_char('\n');
}
//完成一般中断处理函数注册及异常名称注册
static void exception_init(void){
int i;
for(i=0;i<IDT_DESC_CNT;i++){
idt_table[i] = general_intr_handler;
intr_name[i] = "unknown";
}
intr_name[0] = "#DE Divide Error";
intr_name[1] = "#DB Debug Exception";
intr_name[2] = "NMI Interrupt";
intr_name[3] = "#BP Breakpoint Exception";
intr_name[4] = "#OF Overflow Exception";
intr_name[5] = "#BR BOUND Range Exceeded Exception";
intr_name[6] = "#UD Invalid Opcode Exception";
intr_name[7] = "#NM Device Not Available Exception";
intr_name[8] = "#DF Double Fault Exception";
intr_name[9] = "Coprocessor Segment Overrun";
intr_name[10] = "#TS Invalid TSS Exception";
intr_name[11] = "#NP Segment Not Present";
intr_name[13] = "#GP General Protection Exception";
intr_name[14] = "#PF Page-Fault Exception";
// intr_name[15] 第15项是intel保留项,未使用
intr_name[16] = "#MF x87 FPU Floating-Point Error";
intr_name[17] = "#AC Alignment Check Exception";
intr_name[18] = "#MC Machine-Check Exception";
intr_name[19] = "#XF SIMD Floating-Point Exception";
}
lidt 加载中断描述符表
回顾一下lidt加载的结构0-15:为idt的size,16-47为idt的地址
uint64_t idt_operand = ((sizeof(idt)-1)|((uint64_t)((uint32_t)idt<<16)));
asm volatile("lidt %0"::"m"(idt_operand));
总结
涉及硬件比较复杂的部分没有写。