注册 登录
编程论坛 操作系统内核开发

一步步写操作系统之第三步:初步建立中断异常处理机制

miaowangjian 发布于 2010-05-17 05:32, 1866 次点击
建立中断异常处理机制,主要就是对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 编辑 ]
3 回复
#2
chengstone2010-05-17 10:52
好 不错 不错
#3
miaowangjian2010-05-31 16:27
发现代码里的文件头注释写错了
帖子中的:
code:i8259.c(新)
程序代码:
//文件:klib.c
//功能:初始化8259A、以及设置中断相关的函数
//作者:miao
//时间:2010-5-16
应该为:
code:i8259.c(新)
程序代码:
code:i8259.c(新)
程序代码:
//文件:i8259.c
//功能:初始化8259A、以及设置中断相关的函数
//作者:miao
//时间:2010-5-16

因为这个错误,在
    一步步写操作系统之第四步:创建键盘中断处理模块和初步搭建程序执行系统
中,提示对i8259.c(的修改,写成了对 klib.c的修改。

现已经在帖子里改过来了。

#4
djchx2010-08-18 09:07
顶一下,我是刚开始学,见到各位大哥这么认真的发帖子,无非是更利于我这样的菜鸟能学到东西,谢谢了,
1