欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

12.创建IDT、中断处理程序,初始化8259A,中断测试

程序员文章站 2022-06-20 11:57:01
...

0. 中断那些事儿

中断分类

  1. 外部中断
    1.1 可屏蔽中断(INTR)
    1.2 不可屏蔽中断(NMI)
  2. 内部中断
    2.1 软中断
    2.2 异常

12.创建IDT、中断处理程序,初始化8259A,中断测试
一共0~255,256个中断。
这个0~255就是中断向量号。处理器就是根据中断向量号来定位中断处理程序的。

操作系统是中断驱动的,在实模式下有中断向量表(IVT),中断发生后找到中断处理程序的入口;在实模式下有中断描述符表(IDT),中断发生后根据中断描述符来找到中断处理程序。

中断描述符表 IDT 中不只有中断描述符,还有任务门描述符、陷阱门描述符。

为什么称为“门”?
因为是通往某段程序的大门,中断描述符表中的描述符叫做——门。
12.创建IDT、中断处理程序,初始化8259A,中断测试

中断处理过程

  1. 处理器根据中断向量号来定位到中断描述符。
  2. 进行特权级检查
    2.1若是软中断 int n、int3、into 引发的中断,先检查CPL权限大于等于门描述符DPL,即CPL <= 门描述符DPL。(门槛检查)
    2.2 CPL权限小于目标代码段的DPL,即CPL > 目标代码段的DPL(门框检查)。
    2.3 若是外部中断或者异常,则只检查2.2。
  3. 执行中断处理程序。
  4. 中断返回。返回时也要进行特权级检查。

中断发生时的压栈

  1. 比较CPL和目标代码的DPL,若CPL > DPL,则表示要向高特权级转移。
    1.1 保存旧栈SS_old、ESP_old
    1.2 找到目标级别的栈,加载到SS、ESP.
    1.3 将SS_old、ESP_old压入新栈
  2. 新栈中压入EFLAGS寄存器
  3. 将CS、EIP 保存到当前栈,(中断返回时用)
  4. 将ERROR_CODE记入栈
  5. 若在第 1 步判断未发生特权级转移,则用旧栈来保存EFLAGS、CS、EIP、ERROR_CODE。

下图为中断处理过程。
12.创建IDT、中断处理程序,初始化8259A,中断测试
我们要处理中断,要做的就是:

  1. 建立中断处理函数
  2. 建立中断描述符表 IDT
  3. 打开中断
  4. 等待中断发生

问题来了,中断向量号是怎么给处理器的呢?
我们测试的是用 INTR 引脚给 处理器发送外部可屏蔽中断。

8259A 可编程中断控制器
它是可屏蔽中断的总代理,用它来统一管理可屏蔽中断,
12.创建IDT、中断处理程序,初始化8259A,中断测试
各种可屏蔽中断得经过它才能达到CPU。
12.创建IDT、中断处理程序,初始化8259A,中断测试
8259A 的编程就是两步,初始化、控制。
写 ICW1 ~ ICW4 寄存器和 OCW1 ~ OCW3 寄存器。
具体过程,各个寄存器代表的含义,不多说。

8259A 已经和CPU在硬件上连接好了,就像前面图所示。
我们只需对它进行编程就可以了。

1 总步骤

任务:我们要测试一个时钟中断,就是连接在 8259A 主片上的 IR0 的中断。
步骤:

  1. 写好中断处理程序。(kernel.S)
  2. 建立中断描述符表 IDT。(my_interrupt.c)
  3. 加载 IDT。(my_interrupt.c)
  4. 设置 8259A。 (8259A.S)
  5. 打开中断。(kernel.S)
  6. 观察是否执行中断处理程序的打印字符。

2 写中断处理函数

kernel.S
kernel.S先是实现了33个中断处理程序的入口地址,最后的一个函数是以后在my_interrupt.c中调用的,要加载 idt 到 IDTR寄存器的。

[bits 32]
%define ERROR_CODE nop		 ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0		 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0

extern idt_table		 ;idt_table是C中注册的中断处理程序数组


section .data
idt_ptr  dw  0 
	    dd  0			;为以后的加载 idt 存放48位立即数
global intr_entry_table			;在 C 中引用intr_entry_table数组,intr_entry_table里存的是0x21个中断处理程序的入口地址
intr_entry_table:

%macro VECTOR 2
section .text
intr%1entry:		 ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少

   %2				 ; 中断若有错误码会压在eip后面 
; 以下是保存上下文环境
   push ds
   push es
   push fs
   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
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO 
VECTOR 0x20,ZERO

section .text
global lodidt
lodidt:
	push eax
	push ebx
	mov eax, [esp + 0xc]
	mov ebx, [esp + 0x10]
	
	mov [idt_ptr], eax
	mov [idt_ptr + 2], ebx
	lidt [idt_ptr]
	
	pop ebx
	pop eax
	ret

3. 创建IDT、加载IDT。

my_interrupt.c

#include "my_interrupt.h"
#include "std_int.h"
#include "print.h"
#include "kernel1.h"
#include "8259A.h"

#define IDT_DESC_CNT 0x21	 // 目前总共支持的中断数

/*中断门描述符结构体*/
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;
};


extern void* intr_entry_table[IDT_DESC_CNT];		//intr_entry_table存的是kernel.S中的0x21个中断处理程序的入口地址
static struct gate_desc gate_desc_1[IDT_DESC_CNT];			//中断描述符表 IDT,里面放了33个中断门描述符
char* intr_name[IDT_DESC_CNT];		     // 用于保存异常的名字
void* idt_table[IDT_DESC_CNT];			//保存在 C 中的中断处理程序的地址(kernel.S中只是入口,最终的程序在c中实现)

/* 创建中断门描述符 */
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, void* function) { 
   p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;
   p_gdesc->selector = (1 << 3);
   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(&gate_desc_1[i], ((1 << 7) + (0 << 5) + 0xE), 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引脚,保留
      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。
							    // 以后会由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");		//在 print.S中定义

	idt_desc_init();		//创建 IDT
	exception_init();		//中断处理函数注册
	pic_init();				//设置 8259A
	
//	uint64_t idt_operand = ((sizeof(gate_desc_1) - 1) | ((uint64_t)(uint32_t)(&gate_desc_1) << 16));
//	asm volatile("lidt %0" : : "m" (idt_operand));
	lodidt((uint16_t)(sizeof(gate_desc_1) - 1), (uint32_t)(&gate_desc_1));		//加载IDT,在kernel.S中定义

//	put_int((uint16_t)((uint32_t)intr_entry_table[0]));//调试用
	
	set_if();		//开中断,在8259A.S中定义

	put_str("idt_init done\n");
}

4. 设置8259A,开中断

8259A.S
设置 8259A,开中断

[bits 32]
section .text

global pic_init
pic_init:
	mov dx, 0x20
	mov al, 0x11
	out dx, al
	
	mov dx, 0x21
	mov al, 0x20
	out dx, al
	
	mov al, 0x04
	out dx, al
	
	mov al, 0x01
	out dx, al			;初始化主片
	
	mov dx, 0xa0
	mov al, 0x11
	out dx, al
	
	mov dx, 0xa1
	mov al, 0x28
	out dx, al
	
	mov al, 0x02
	out dx, al
	
	mov al, 0x01
	out dx, al			;初始化从片
	
	mov dx, 0x21
	mov al, 0xfe
	out dx, al
	
	mov dx, 0xa1
	mov al, 0xff
	out dx, al
	
	ret
	
global set_if
set_if:
	sti
	ret
	

5. 主函数

main.c

#include "print.h"
#include "my_interrupt.h"

void main(void) {
	put_str("ww i am kernel !\n");
	idt_init();		//在interrupt.c中
	while(1);
}

6.实验

链接程序时要注意链接顺序。
结果如图,一直产生时钟中断0x20。
12.创建IDT、中断处理程序,初始化8259A,中断测试
info idt
12.创建IDT、中断处理程序,初始化8259A,中断测试
12.创建IDT、中断处理程序,初始化8259A,中断测试
.

相关标签: 从0写操作系统