操作系统真象还原 --- 7.中断

先给自己提几个问题。

  1. 中断是什么?中断就是发生了某些事情,计算机暂停正在执行的程序,去执行处理这件事情
  2. 为什么需要中断?因为发生了更紧急需要处理的事情,所以需要中断暂停。相对前面实现的操作系统最终会到一个while循环中,现在需要实现中断突破while循环。所以之后的系统就是由中断驱动的。
  3. 如何实现中断?后面再看看怎么实现。

一、中断分类

中断从触发的地方分为:外部中断和内部中断。
外部中断: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位内存地址

三、实现中断

实现中断包含三个步骤

  1. 创建中断对应的例程
  2. 初始化中断描述符表
  3. 异常名初始化并注册通常的中断处理函数
  4. 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));

总结

涉及硬件比较复杂的部分没有写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值