操作系统真象还原实验记录之实验⼗⼀:实现中断处理(⼆)
操作系统真象还原实验记录之实验⼗⼀:实现中断处理(⼆)
书p335 7.6.2 改进中断处理程序,并调快时钟
1.实验代码第⼀次修改
对应
书p335 7.6.2 改进中断处理程序
这次是上⼀次实验的修改
在cpu获得中断向量号后,会调⽤中断处理程序,上次实验中断处理程序kernel.s是汇编写的,这次对kernel.s进⾏修改,在中断处理程序⾥调⽤了c语⾔写的处理程序来执⾏中断处理
1.1 interrupt.c
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.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_CNT 0x21      // ⽬前总共⽀持的中断数33
#define EFLAGS_IF  0x00000200      // eflags寄存器中的if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))
/*中断门描述符结构体*/
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);
static struct gate_desc idt[IDT_DESC_CNT];  // idt是中断描述符表,本质上就是个中断门描述符数组
char* intr_name[IDT_DESC_CNT];      // ⽤于保存异常的名字
/
********    定义中断处理程序数组    ********
* 在kernel.S中定义的intrXXentry只是中断处理程序的⼊⼝,
* 最终调⽤的是ide_table中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];
/********************************************/
extern intr_handler intr_entry_table[IDT_DESC_CNT];    // 声明引⽤定义在kernel.S中的中断处理函数⼊⼝数组
/* 初始化可编程中断控制器8259A */
/* 初始化可编程中断控制器8259A */
static void pic_init(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
/* IRQ2⽤于级联从⽚,必须打开,否则⽆法响应从⽚上的中断
主⽚上打开的中断有IRQ0的时钟,IRQ1的键盘和级联从⽚的IRQ2,其它全部关闭 */
outb (PIC_M_DATA, 0xfe);  只打开时钟中断,其余均屏蔽
/* 打开从⽚上的IRQ14,此引脚接收硬盘控制器的中断 */
outb (PIC_S_DATA, 0xff);
put_str("  pic_init done\n");
}
/* 创建中断门描述符 */
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, lastindex = IDT_DESC_CNT - 1;
for (i = 0; i < IDT_DESC_CNT; i++) {
make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
}
/* 单独处理系统调⽤,系统调⽤对应的中断门dpl为3,
* 中断处理程序为单独的syscall_handler */
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引脚,保留
return;  //IRQ7和IRQ15会产⽣伪中断(spurious interrupt),⽆须处理。
}
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数组中的函数是在进⼊中断后根据中断向量号调⽤的,
* 见kernel/kernel.S的call [idt_table + %1*4] */
idt_table[i] = general_intr_handler;      // 默认为general_intr_handler。
idt_table[i] = general_intr_handler;      // 默认为general_intr_handler。          // 以后会由register_handler来注册具体处理函数。
intr_name[i] = "unknown";        // 先统⼀赋值为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[12] = "#SS Stack Fault Exception";
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";
}
/*完成有关中断的所有初始化⼯作*/
void idt_init() {
put_str("idt_init start\n");
idt_desc_init();    // 初始化中断描述符表
exception_init();    // 异常名初始化并注册通常的中断处理函数
pic_init();    // 初始化8259A
/* 加载idt */
uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));  asm volatile("lidt %0" : : "m" (idt_operand));
put_str("idt_init done\n");
}
增加了如下
char* intr_name[IDT_DESC_CNT];      // ⽤于保存异常的名字
intr_handler idt_table[IDT_DESC_CNT];
static void general_intr_handler(uint8_t vec_nr)
static void exception_init(void)
我们写的33个中断处理程序,前20个的中断向量号是异常,由cpu产⽣
第21个到第32个中断向量号需保留,所以名字赋值成unknown。
所以两块8259A芯⽚产⽣的15个中断向量号从0x20到0x2f
其中IRQ7为并⼝1,IRQ15是保留,伪中断经常⽤这两个号,这两个接⼝⽆法通过IMR屏蔽寄存器来屏蔽,所以需要软件单独处理伪中断是⼀种硬件中断,⽐如中断线路上的电⽓信号异常等。
本次实验只打开了IRQ0,所以由8259A芯⽚产⽣的中断向量号只可能是0x20,0x27、0x2f。
CPU还可能产⽣0~19号中断向量号(第15号除外)
上述本次实验可能产⽣中断向量号,都会进⼊中断处理程序,在中断处理程序调⽤general_intr_handler,打印⾃⼰的中断向量号,0x27、0x2f的伪中断除外。
1.2 kernel.s
[bits 32]
%define ERROR_CODE nop  ; 若在相关的异常中cpu已经⾃动压⼊了错误码,为保持栈中格式统⼀,
这⾥不做操作.
%define ZERO push 0  ; 若在相关的异常中cpu没有压⼊错误码,为了统⼀栈中格式,就⼿⼯压⼊⼀个0
extern put_str;
extern idt_table;
section .data
global intr_entry_table
intr_entry_table:
%macro VECTOR 2
section .text
intr%1entry:  ; 每个中断处理程序都要压⼊中断向量号,所以⼀个中断类型⼀个中断处理程序,⾃⼰知道⾃⼰的中断向量号是多少
%2    ; 中断若有错误码会压在eip后⾯
; 以下是保存上下⽂环境
push ds
push es
push fsunknown怎么处理
push gs
pushad    ; PUSHAD指令压⼊32位寄存器,其⼊栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
; 如果是从⽚上进⼊的中断,除了往从⽚上发送EOI外,还要往主⽚上发送EOI
mov al,0x20                  ; 中断结束命令EOI
out 0xa0,al                  ; 向从⽚发送
out 0x20,al                  ; 向主⽚发送
push %1    ; 不管idt_table中的⽬标程序是否需要参数,都⼀律压⼊中断向量号,调试时很⽅便
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