1.kernel.S
①有些中断处理,会在栈中留下自己的错误码,就很像Linux的errno,所以当中断处理函数退栈的时候,会造成两种不同的处理方式,这里为了简便,直接将两种一起处理,有错误码的我们执行一条空指令,没有错误码的我们手工压入一个0
②我们在这里采用了宏汇编,来对所有的中断函数进行处理,就是当中断发生的时候,调用宏汇编的函数,在宏汇编的函数中,它压入自己的中断号,然后在IDT中查找自己的中断处理函数,进行处理
[bits 32]
%define ERROR_CODE nop
; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0
; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0
extern put_str ;声明外部函数
extern g_IDT_Table
section .data
intr_str db "interrupt occur!", 0xa, 0
global g_IntrEntryTable
g_IntrEntryTable:
%macro VECTOR 2
section .text
intr%1entry: ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少
%2
;保存上下文环境
PUSH DS
PUSH ES
PUSH FS
PUSH GS
PUSHAD
; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI
mov al,0x20 ; 中断结束命令EOI
out 0xa0,al ; 向从片发送
out 0x20,al ; 向主片发送
PUSH %1 ;压入中断向量号
CALL [g_IDT_Table+ %1*4] ;调用中断处理函数
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 ;跳过错误码
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
2.Interrupt.c
①定义了中断描述符表
②定义了异常发生时,显示在屏幕上的错误信息
③定义了中断处理函数的数组
④声明了中断处理函数的入口,定义在kernel.S中
⑤对PIC进行了设定
⑥加载中段描述符表寄存器
#include "Interrupt.h"
#include "stdint.h"
#include "Global.h"
#include "print.h"
#include "io.h"
#define PIC_M_CTRL 0x20
// 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21 // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0 // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1 // 从片的数据端口是0xa1
#define IDT_DESC_COUNT 0x21 //目前支持的中断总数
typedef struct INT_GATE_DESC_T //中断门描述符的结构体定义
{
uint16_t FunOffsetLowWord;
uint16_t Selector;
uint8_t dCount;
uint8_t Attribute;
uint16_t FunOffsetHighWord;
}INT_GATE_DESC_T;
//用来保存异常的名字
char * IntrName[IDT_DESC_COUNT];
//定义中断处理程序数组
Int_Handler g_IDT_Table[IDT_DESC_COUNT];
//初始化一个中断描述符的结构体
static void MakeIdtDesc(INT_GATE_DESC_T *pGateDesc, const uint8_t attr, const Int_Handler pIntrFun);
//中断门描述符的数组
static INT_GATE_DESC_T g_IDT[IDT_DESC_COUNT];
//此数组定义在kernel.S文件
extern Int_Handler g_IntrEntryTable[IDT_DESC_COUNT];
//通用的中断处理函数 一般在异常出现时的处理
static void GeneralIntrHandler(uint8_t vec_num)
{
//IRQ7 IRQ15 会产生伪中断 无需处理
if(0x27 == vec_num || 0x2f == vec_num)
return ;
put_str("INT VECTOR : 0x");
put_int(vec_num);
put_char('\n');
}
static void InitException(void)
{
int i;
for(i = 0; i < IDT_DESC_COUNT; i++)
{
g_IDT_Table[i] = GeneralIntrHandler;
IntrName[i] = "UNKNOWN";
}
IntrName[0] = "#DE Divide Error";
IntrName[1] = "#DB Debug Exception";
IntrName[2] = "NMI Interrupt";
IntrName[3] = "#BP Breakpoint Exception";
IntrName[4] = "#OF Overflow Exception";
IntrName[5] = "#BR BOUND Range Exceeded Exception";
IntrName[6] = "#UD Invalid Opcode Exception";
IntrName[7] = "#NM Device Not Available Exception";
IntrName[8] = "#DF Double Fault Exception";
IntrName[9] = "Coprocessor Segment Overrun";
IntrName[10] = "#TS Invalid TSS Exception";
IntrName[11] = "#NP Segment Not Present";
IntrName[12] = "#SS Stack Fault Exception";
IntrName[13] = "#GP General Protection Exception";
IntrName[14] = "#PF Page-Fault Exception";
// intr_name[15] 第15项是intel保留项,未使用
IntrName[16] = "#MF x87 FPU Floating-Point Error";
IntrName[17] = "#AC Alignment Check Exception";
IntrName[18] = "#MC Machine-Check Exception";
IntrName[19] = "#XF SIMD Floating-Point Exception";
}
/* 初始化可编程中断控制器8259A */
static void InitPic(void)
{
/* 初始化主片 */
outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
outb (PIC_M_DATA, 0x20);
// ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
outb (PIC_M_DATA, 0x04); // ICW3: IR2接从片.
outb (PIC_M_DATA, 0x01); // ICW4: 8086模式, 正常EOI
/* 初始化从片 */
outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
outb (PIC_S_DATA, 0x28);
// ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2引脚
outb (PIC_S_DATA, 0x01); // ICW4: 8086模式, 正常EOI
/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */
outb (PIC_M_DATA, 0xfe);
outb (PIC_S_DATA, 0xff);
put_str(" INIT PIC DONE\n");
}
static void MakeIdtDesc(INT_GATE_DESC_T *pGateDesc, const uint8_t attr, const Int_Handler pIntrFun)
{
pGateDesc->FunOffsetHighWord = ((uint32_t)pIntrFun & 0xFFFF0000)>>16;
pGateDesc->Attribute = attr;
pGateDesc->dCount = 0;
pGateDesc->Selector = SELECTOR_K_CODE;
pGateDesc->FunOffsetLowWord = (uint32_t)pIntrFun & 0x0000FFFF;
}
//初始化中断描述符表
static void InitIdtDesc(void)
{
int i;
for(i = 0; i < IDT_DESC_COUNT; i++)
{
MakeIdtDesc(&g_IDT[i], IDT_DESC_ATTR_DPL0, g_IntrEntryTable[i]);
}
put_str("IDT DESC INIT DONE\n");
}
//完成有关中断的所有初始化工作
void InitIdt(void)
{
put_str("IDT ININT START\n");
InitIdtDesc();//初始化中断描述符表
InitException();//异常名初始化 并注册通用的中断处理函数
InitPic();//初始化8259A
//IDTR寄存器低16位是IDT界限 高32位是IDT的线性基地址
uint64_t IdtOperand = ((sizeof(g_IDT)-1) | ((uint64_t) ((uint32_t)g_IDT << 16)));
asm volatile("LIDT %0":: "m" (IdtOperand));
put_str("IDT INIT DONE\n");
}