一步步写操作系统之第三步:初步建立中断异常处理机制
建立中断异常处理机制,主要就是对8259A的初始化,建立中断描述符表。因为要对十来个已定义的异常进行处理,再加上16个外部中断的处理,代码量就显得很庞大了。因为一时间也想不出更好的处理方案,所以就照搬《自己动手写操作系统》里的那一套方法用着先
具体的初始化流程为:
kernel -> init_prot()
//在 init_prot() 调用init_8259A(),对8259A进行初始化,与以前的代码一样的,只是换成了c形式了。
//调用init_8259A()后 就是重复init_idt_desc()函数对ide表进行初始化话,前十多个是异常处理,后16个是外部中断处理,最后是自定义中断
//值得注意的是为外部中断处理函数建立了一个函数处理表,如此可以在之后灵活的设置修改中断的最终处理函数。
建立中断异常处理机制后(尽管是最简单的),就可以对其进行简单测试。
在kernel.c的main函数里,分别取消以下三个异常测试项的注释符,就可以看到异常信息显示在屏幕上。
//测试异常 //int 3 //测试调试断点异常 //jmp 0x40:0 //测试常规保护错误异常 //ud2 //测试无效操作码异常
对外部中断的测试:
//测试外部中断 //enable_irq(KEYBOARD_IRQ); //开键盘中断 //enable_irq(CLOCK_IRQ); //开时钟中断
注意:因为一下子添加了很多代码,超出了最初在boot.c里设定加载内核4个扇区大小的限制,因此需要将加载的扇区数调大一些。
code:kernel.c(改)
程序代码:
//文件:kernel.c //功能:内核程序,目前功能为测试print.c里的几个输出函数 //运行:run.exe自动会编译boot.c与生成img并调用Bochs运行此程序。 //作者:miao //时间:2010-5-17 #define YCBIT 32 //告诉编译器,以32位格式编译程序 #define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0 #include "global.h" #include "kernel.h" #include "print.h" #include "klib.h" #include "i8259.h" void init(); //内核入口点 asm void main() { lgdt cs:GdtPtr //加载新的GDTR mov eax, SelectorVideo mov gs, ax //视频段选择子(目的) mov eax, SelectorData32 //令32位代码段的变量(printPlace)可以读写 mov ds, ax call init //初始化函数 //测试异常 //int 3 //测试调试断点异常 //jmp 0x40:0 //测试常规保护错误异常 //ud2 //测试无效操作码异常 sti //开中断 jmp kernel_main } void init() { disp_str("================ init start =================\n"); disp_str(" init idt -------------------------------- "); init_prot(); //初始化IDT disp_str("ok\n"); disp_str("================ init end ===================\n"); } int kernel_main() { //测试外部中断 //enable_irq(KEYBOARD_IRQ); //开键盘中断 //enable_irq(CLOCK_IRQ); //开时钟中断 while(1); return 0; } #include "print.c" #include "klib.c" #include "i8259.c"
code:klib.h(新)
程序代码:
//文件:klib.h //功能:klib头文件,内核需要用到的公共函数的声明等 //作者:miao //时间:2010-5-16 //向指定端口写入数据 asm void out_byte(t_port port, t_8 value); //从指定端口读取数据 asm t_8 in_byte(t_port port);
code:klib.c(新)
程序代码:
//文件:klib.c //功能:定义内核需要用到的公共函数 //作者:miao //时间:2010-5-16 //向指定端口写入数据 asm void out_byte(t_port port, t_8 value) { mov edx, [esp + 4] //port mov al, [esp + 4 + 4] //value out dx, al nop //一点延迟 nop ret } //从指定端口读取数据 asm t_8 in_byte(t_port port) { mov edx, [esp + 4] //port xor eax, eax in al, dx nop //一点延迟 nop ret }code:i8259.h(新)
程序代码:
//文件:i8259.h //功能:i8259头文件,放置初始化8259A、以及设置中断需要用到的宏、数据结构定义、全局变量以及函数的声明等 //作者:miao //时间:2010-5-16 //8259A 中断控制端口 #define INT_M_CTL 0x20 //主:中断控制器输入输出端口 #define INT_M_CTLMASK 0x21 //主:通过此端口禁止指定的中断号 #define INT_S_CTL 0xA0 //从:次级中断控制器输入输出端口 #define INT_S_CTLMASK 0xA1 //从:通过此端口禁止指定的中断号 //中断(异常)向量号 从0到255,共256个,前面0~13已定义,20~31Intel保留未使用,32~255用户自定义 #define INT_VECTOR_DIVIDE 0x0 //除法错 #define INT_VECTOR_DEBUG 0x1 //调试异常 #define INT_VECTOR_NMI 0x2 //非屏蔽中断 #define INT_VECTOR_BREAKPOINT 0x3 //调试断点 #define INT_VECTOR_OVERFLOW 0x4 //溢出 #define INT_VECTOR_BOUNDS 0x5 //越界 #define INT_VECTOR_INVAL_OP 0x6 //无效操作码 #define INT_VECTOR_COPROC_NOT 0x7 //设备不可用 #define INT_VECTOR_DOUBLE_FAULT 0x8 //双重错误 #define INT_VECTOR_COPROC_SEG 0x9 //协处理器段越界 #define INT_VECTOR_INVAL_TSS 0xA //无效TSS #define INT_VECTOR_SEG_NOT 0xB //段不存在 #define INT_VECTOR_STACK_FAULT 0xC //堆栈段错误 #define INT_VECTOR_PROTECTION 0xD //常规保护错误 #define INT_VECTOR_PAGE_FAULT 0xE //页错误 // 0xF //Intel保留 #define INT_VECTOR_COPROC_ERR 0x10 //浮点错 // 0x14~0x1F //Intel保留未使用 //0x20~0x2F,作为8259A的16个外部中断用 #define INT_VECTOR_IRQ0 0x20 //主:0x20~0x27 #define INT_VECTOR_IRQ8 0x28 //从:0x28~0x2F // 0x30~0xFF //暂时不是用 //硬件中断向量(打开或禁用某个外部中断时用) #define CLOCK_IRQ 0 #define KEYBOARD_IRQ 1 //386中断门类型值 #define DA_386IGate 0x8E //中断(异常)所具有的权限 #define PRIVILEGE_KRNL 0 #define PRIVILEGE_USER 3 //8259A,主8个,从8个,一共有16个硬件中断 #define NR_IRQ 16 typedef void (*t_pf_irq_handler)(int irq); //定义中断请求处理函数类型 typedef void (*t_pf_int_handler)(); //定义硬件中断处理函数类型 //为16个硬件中断创建一个函数指针表,每个硬件中断发生时调用对应的处理函数 t_pf_irq_handler irq_table[NR_IRQ]; //门描述符 struct GATE { t_16 offset_low;//Offset Low t_16 selector; //Selector t_8 dcount; //该字段只在调用门描述符中有效。 //如果在利用调用门调用子程序时引起特权级的转换和堆栈的改变, //需要将外层堆栈中的参数复制到内层堆栈。 //该双字计数字段就是用于说明这种情况发生时,要复制的双字参数的数量。 t_8 attr; //P(1) DPL(2) DT(1) TYPE(4) t_16 offset_high;//Offset High }; #define IDT_SIZE 64 GATE idt[IDT_SIZE];//中断描述符表,最多可以定义256个,目前暂时申请64个 t_8 idt_ptr[6]; //共6个字节0~15:Limit,16~47:Base 用作sidt以及lidt的参数 //发生异常时,显示对应的异常错误信息 char err_description[][64] = { "#DE Divide Error", "#DB RESERVED", "— NMI Interrupt", "#BP Breakpoint", "#OF Overflow", "#BR BOUND Range Exceeded", "#UD Invalid Opcode (Undefined Opcode)", "#NM Device Not Available (No Math Coprocessor)", "#DF Double Fault", " Coprocessor Segment Overrun (reserved)", "#TS Invalid TSS", "#NP Segment Not Present", "#SS Stack-Segment Fault", "#GP General Protection", "#PF Page Fault", "— (Intel reserved. Do not use.)", "#MF x87 FPU Floating-Point Error (Math Fault)", "#AC Alignment Check", "#MC Machine Check", "#XF SIMD Floating-Point Exception" }; void init_prot(); //初始化IDT函数 asm void disable_irq(int irq); //关闭特定的中断 asm void enable_irq(int irq); //打开特定的中断 void put_irq_handler(int irq, t_pf_irq_handler handler);//向特定的中断注册处理函数
code:i8259.c(新)
程序代码:
//文件:i8259.c //功能:初始化8259A、以及设置中断相关的函数 //作者:miao //时间:2010-5-16 //外部中断默认调用的函数 void spurious_irq(int irq) { disp_str("spurious_irq: "); disp_int(irq); disp_str("\n"); } void init_8259A() { out_byte(INT_M_CTL, 0x11); // 主 8259, ICW1 out_byte(INT_S_CTL, 0x11); // 从 8259, ICW1 out_byte(INT_M_CTLMASK, INT_VECTOR_IRQ0); //主8259,ICW2.设置'主8259'的中断入口地址为 0x20 out_byte(INT_S_CTLMASK, INT_VECTOR_IRQ8); //从8259,ICW2.设置'从8259'的中断入口地址为 0x28 out_byte(INT_M_CTLMASK, 0x4); // 主 8259, ICW3. IR2 对应 '从8259' out_byte(INT_S_CTLMASK, 0x2); // 从 8259, ICW3. 对应 '主8259' 的 IR2 out_byte(INT_M_CTLMASK, 0x1); // 主 8259, ICW4 out_byte(INT_S_CTLMASK, 0x1); // 从 8259, ICW4 //默认一开始屏蔽掉所有外部中断 out_byte(INT_M_CTLMASK, 0xFF); // 主 8259, OCW1 out_byte(INT_S_CTLMASK, 0xFF); // 从 8259, OCW1 int i; for(i=0;i<NR_IRQ;i++) irq_table[i] = spurious_irq; //为16个硬件中断处理函数指针表初始化一个默认的调用函数 } //初始化386中断门 void init_idt_desc(unsigned char vector, t_8 desc_type, t_pf_int_handler handler, unsigned char privilege) { GATE *p_gate = &idt[vector]; t_32 base = (t_32)handler; p_gate->offset_low = base & 0xFFFF; p_gate->selector = SelectorCode32; p_gate->dcount = 0; p_gate->attr = desc_type | (privilege << 5); p_gate->offset_high = (base >> 16) & 0xFFFF; } //异常处理函数,显示错误信息 void exception_handler(int vec_no, int err_code, int eip, int cs, int eflags) { int i; int text_color = 0xA0; disp_pos = 0; //从顶格开始显示错误信息 disp_color_str("Exception! --> ", text_color); disp_color_str(err_description[vec_no], text_color); disp_color_str("\nEFLAGS:", text_color); disp_int(eflags); disp_color_str(" CS:", text_color); disp_int(cs); disp_color_str(" EIP:", text_color); disp_int(eip); if(err_code != 0xFFFFFFFF) { disp_color_str("Error code:", text_color); disp_int(err_code); } } asm void exception() { call exception_handler //显示错误信息 add esp, 4*2 //让栈顶指向 EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGS hlt } //中断和异常 -- 异常,设置错误代码和中断号 asm void divide_error() { push 0xFFFFFFFF //没有错误代码 push 0 //vector_no = 0 jmp exception } asm void single_step_exception() { push 0xFFFFFFFF //没有错误代码 push 1 //vector_no = 1 jmp exception } asm void nmi() { push 0xFFFFFFFF //没有错误代码 push 2 //vector_no = 2 jmp exception } asm void breakpoint_exception() { push 0xFFFFFFFF //没有错误代码 push 3 //vector_no = 3 jmp exception } asm void overflow() { push 0xFFFFFFFF //没有错误代码 push 4 //vector_no = 4 jmp exception } asm void bounds_check() { push 0xFFFFFFFF //没有错误代码 push 5 //vector_no = 5 jmp exception } asm void inval_opcode() { push 0xFFFFFFFF //没有错误代码 push 6 //vector_no = 6 jmp exception } asm void copr_not_available() { push 0xFFFFFFFF //没有错误代码 push 7 //vector_no = 7 jmp exception } asm void double_fault() { push 8 //vector_no = 8 jmp exception } asm void copr_seg_overrun() { push 0xFFFFFFFF //没有错误代码 push 9 //vector_no = 9 jmp exception } asm void inval_tss() { push 10 //vector_no = A jmp exception } asm void segment_not_present() { push 11 //vector_no = B jmp exception } asm void stack_exception() { push 12 //vector_no = C jmp exception } asm void general_protection() { push 13 //vector_no = D jmp exception } asm void page_fault() { push 14 //vector_no = E jmp exception } asm void copr_error() { push 0xFFFFFFFF //没有错误代码 push 16 //vector_no = 10h jmp exception } #define EOI 0x20 //中断和异常 -- 硬件中断 #define hwint_master(irq_No) \ in al, INT_M_CTLMASK /* ┓ */ \ or al, (1 << irq_No) /* ┣ 屏蔽当前中断 */ \ out INT_M_CTLMASK, al /* ┛ */ \ mov al, EOI /* ┓置EOI位 */ \ out INT_M_CTL, al /* ┛ */ \ sti /* CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断*/ \ push irq_No /* ┓ */ \ call dword [&irq_table + 4 * irq_No] /* ┣ 中断处理程序 */ \ pop ecx /* ┛ */ \ cli \ in al, INT_M_CTLMASK /* ┓ */ \ and al, ~(1 << irq_No) /* ┣ 恢复接受当前中断 */ \ out INT_M_CTLMASK, al /* ┛ */ \ iretd \ ret \ #pragma align(16) asm void hwint00() //Interrupt routine for irq 0 (the clock). { hwint_master(0) } #pragma align(16) asm void hwint01() //Interrupt routine for irq 1 (keyboard) { hwint_master(1) } #pragma align(16) asm void hwint02() //Interrupt routine for irq 2 (cascade!) { hwint_master(2) } #pragma align(16) asm void hwint03() //Interrupt routine for irq 3 (second serial) { hwint_master(3) } #pragma align(16) asm void hwint04() //Interrupt routine for irq 4 (first serial) { hwint_master(4) } #pragma align(16) asm void hwint05() //Interrupt routine for irq 5 (XT winchester) { hwint_master(5) } #pragma align(16) asm void hwint06() //Interrupt routine for irq 6 (floppy) { hwint_master(6) } #pragma align(16) asm void hwint07() //Interrupt routine for irq 7 (printer) { hwint_master(7) } #define hwint_slave(irq_No) \ push irq_No \ call spurious_irq \ add esp, 4 \ hlt \ #pragma align(16) asm void hwint08() //Interrupt routine for irq 8 (realtime clock). { hwint_slave(8) } #pragma align(16) asm void hwint09() //Interrupt routine for irq 9 (irq 2 redirected) { hwint_slave(9) } #pragma align(16) asm void hwint10() //Interrupt routine for irq 10 { hwint_slave(10) } #pragma align(16) asm void hwint11() //Interrupt routine for irq 11 { hwint_slave(11) } #pragma align(16) asm void hwint12() //Interrupt routine for irq 12 { hwint_slave(12) } #pragma align(16) asm void hwint13() //Interrupt routine for irq 13 (FPU exception) { hwint_slave(13) } #pragma align(16) asm void hwint14() //Interrupt routine for irq 14 (AT winchester) { hwint_slave(14) } #pragma align(16) asm void hwint15() //Interrupt routine for irq 15 { hwint_slave(15) } //初始化IDT void init_prot() { init_8259A(); // 全部初始化成中断门(没有陷阱门) // 中断向量 中断门类型值 处理函数 权限 //异常 init_idt_desc(INT_VECTOR_DIVIDE, DA_386IGate, divide_error, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_DEBUG, DA_386IGate, single_step_exception,PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_NMI, DA_386IGate, nmi, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_BREAKPOINT, DA_386IGate, breakpoint_exception, PRIVILEGE_USER); init_idt_desc(INT_VECTOR_OVERFLOW, DA_386IGate, overflow, PRIVILEGE_USER); init_idt_desc(INT_VECTOR_BOUNDS, DA_386IGate, bounds_check, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_INVAL_OP, DA_386IGate, inval_opcode, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_COPROC_NOT, DA_386IGate, copr_not_available, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_DOUBLE_FAULT,DA_386IGate, double_fault, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_COPROC_SEG, DA_386IGate, copr_seg_overrun, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_INVAL_TSS, DA_386IGate, inval_tss, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_SEG_NOT, DA_386IGate, segment_not_present, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_STACK_FAULT, DA_386IGate, stack_exception, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_PROTECTION, DA_386IGate, general_protection, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_PAGE_FAULT, DA_386IGate, page_fault, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_COPROC_ERR, DA_386IGate, copr_error, PRIVILEGE_KRNL); //外部中断 init_idt_desc(INT_VECTOR_IRQ0 + 0, DA_386IGate, hwint00, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 1, DA_386IGate, hwint01, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ0 + 2, DA_386IGate, hwint02, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ0 + 3, DA_386IGate, hwint03, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ0 + 4, DA_386IGate, hwint04, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ0 + 5, DA_386IGate, hwint05, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ0 + 6, DA_386IGate, hwint06, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ0 + 7, DA_386IGate, hwint07, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 0, DA_386IGate, hwint08, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 1, DA_386IGate, hwint09, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 2, DA_386IGate, hwint10, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 3, DA_386IGate, hwint11, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 4, DA_386IGate, hwint12, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 5, DA_386IGate, hwint13, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 6, DA_386IGate, hwint14, PRIVILEGE_KRNL); //init_idt_desc(INT_VECTOR_IRQ8 + 7, DA_386IGate, hwint15, PRIVILEGE_KRNL); //自定义中断 //init_idt_desc(INT_VECTOR_SYS_CALL, DA_386IGate, sys_call, PRIVILEGE_USER); *(t_16*)(&idt_ptr[0]) = IDT_SIZE * sizeof(GATE); *(t_32*)(&idt_ptr[2]) = (t_32)&idt + ProtecAddr; asm{lidt idt_ptr} //加载idt } //关闭特定的中断 asm void disable_irq(int irq) { mov ecx, [esp + 4] //irq pushf cli mov ah, 1 rol ah, cl //ah = (1 << (irq % 8)) cmp cl, 8 jae disable_8 //disable irq >= 8 at the slave 8259 disable_0: in al, INT_M_CTLMASK test al, ah jnz dis_already //already disabled? or al, ah out INT_M_CTLMASK, al //set bit at master 8259 popf mov eax, 1 //disabled by this function ret disable_8: in al, INT_S_CTLMASK test al, ah jnz dis_already //already disabled? or al, ah out INT_S_CTLMASK, al //set bit at slave 8259 popf mov eax, 1 //disabled by this function ret dis_already: popf xor eax, eax //already disabled ret } //打开特定的中断 asm void enable_irq(int irq) { mov ecx, [esp + 4] ; irq pushf cli mov ah, ~1 rol ah, cl //ah = ~(1 << (irq % 8)) cmp cl, 8 jae enable_8 //enable irq >= 8 at the slave 8259 enable_0: in al, INT_M_CTLMASK and al, ah out INT_M_CTLMASK, al //clear bit at master 8259 popf ret enable_8: in al, INT_S_CTLMASK and al, ah out INT_S_CTLMASK, al //clear bit at slave 8259 popf ret } //向特定的中断注册处理函数 void put_irq_handler(int irq, t_pf_irq_handler handler) { disable_irq(irq); irq_table[irq] = handler; }
[ 本帖最后由 miaowangjian 于 2010-5-31 16:16 编辑 ]