本文主要是介绍操作系统真象还原第7章:中断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
这一章挺琐碎的,看了好几遍,好好捋一捋这一章干的事情。
这一章主要就是利用可编程中断控制器8259A来设置中断处理程序,其中32个中断是计算机自己有的(从0开始数),这一章目前只开启了时钟中断,所以这里设置的中断向量号从32开始,然后有个中断描述符表(IDT),类似与全局描述符表(GDT),中断向量号就类似与索引,从IDT中找到中断描述符,中断描述符中包括选择子(毕竟最终要找到中断处理程序,而中断处理程序肯定是在某个内存地址上的),属性,以及偏移地址,其中选择子在GDT中找到段描述符,段描述中存储着虚拟地址,然后经过页表映射到的地址+偏移地址即为中断处理程序的入口地址。
其中中断是分为三种的,故障,陷阱,abort(终止),故障是最轻的那种,相当与失败了可以返回到原程序,陷阱就是可以内嵌中断(就是信号机制),终止就是最坏的那种中断,出现了最大的故障,挽救不会来。关于8259A需要利用端口读写来设置中断机制,包括可屏蔽啥的
关于特权级,主要是对于cpu来说,有0-3级别,级别越小,特权级限越高,访问权限也就越高。对于代码段来说,也就是cs:ip执行的那些段,除了中断之外只允许平级的特权级之间的代码段切换,而对于访问数据段则必须要当前特权级小于数据段的特权级,也就是当前的访问权限大于数据段的访问权限。
代码
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 // 目前总共支持的中断数/*中断门描述符结构体*/
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]; // 用于保存异常的名字
intr_handler idt_table[IDT_DESC_CNT]; // 定义中断处理程序数组.在kernel.S中定义的intrXXentry只是中断处理程序的入口,最终调用的是ide_table中的处理程序
extern intr_handler intr_entry_table[IDT_DESC_CNT]; // 声明引用定义在kernel.S中的中断处理函数入口数组/* 初始化可编程中断控制器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/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */outb (PIC_M_DATA, 0xfe);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;for (i = 0; i < IDT_DESC_CNT; i++) {make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, 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");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");
}
timer.c
#include "timer.h"
#include "io.h"
#include "print.h"#define IRQ0_FREQUENCY 100
#define INPUT_FREQUENCY 1193180
#define COUNTER0_VALUE INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT 0x40
#define COUNTER0_NO 0
#define COUNTER_MODE 2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43/* 把操作的计数器counter_no、读写锁属性rwl、计数器模式counter_mode写入模式控制寄存器并赋予初始值counter_value */
static void frequency_set(uint8_t counter_port, \uint8_t counter_no, \uint8_t rwl, \uint8_t counter_mode, \uint16_t counter_value) {
/* 往控制字寄存器端口0x43中写入控制字 */outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));
/* 先写入counter_value的低8位 */outb(counter_port, (uint8_t)counter_value);
/* 再写入counter_value的高8位 */outb(counter_port, (uint8_t)counter_value >> 8);
}/* 初始化PIT8253 */
void timer_init() {put_str("timer_init start\n");/* 设置8253的定时周期,也就是发中断的周期 */frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);put_str("timer_init done\n");
}
init.c
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h" // 用相对路径演示头文件包含/*负责初始化所有模块 */
void init_all() {put_str("init_all\n");idt_init(); // 初始化中断timer_init(); // 初始化PIT
}
global.h
#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H
#include "stdint.h"#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3#define TI_GDT 0
#define TI_LDT 1#define SELECTOR_K_CODE ((1 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_DATA ((2 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_STACK SELECTOR_K_DATA
#define SELECTOR_K_GS ((3 << 3) + (TI_GDT << 2) + RPL0)//-------------- IDT描述符属性 ------------
#define IDT_DESC_P 1
#define IDT_DESC_DPL0 0
#define IDT_DESC_DPL3 3
#define IDT_DESC_32_TYPE 0xE // 32位的门
#define IDT_DESC_16_TYPE 0x6 // 16位的门,不用,定义它只为和32位门区分
#define IDT_DESC_ATTR_DPL0 ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)
#define IDT_DESC_ATTR_DPL3 ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)#endif
kernel.S
[bits 32]
%define ERROR_CODE nop ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0extern idt_table ;idt_table是C中注册的中断处理程序数组isection .data
global intr_entry_table
intr_entry_table:%macro VECTOR 2
section .text
intr%1entry: ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少%2 ; 中断若有错误码会压在eip后面
; 以下是保存上下文环境push dspush espush fspush gspushad ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI mov al,0x20 ; 中断结束命令EOIout 0xa0,al ; 向从片发送out 0x20,al ; 向主片发送push %1 ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便call [idt_table + %1*4] ; 调用idt_table中的C版本中断处理函数jmp intr_exitsection .datadd intr%1entry ; 存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacrosection .text
global intr_exit
intr_exit:
; 以下是恢复上下文环境add esp, 4 ; 跳过中断号popadpop gspop fspop espop dsadd esp, 4 ; 跳过error_codeiretdVECTOR 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
结果
这篇关于操作系统真象还原第7章:中断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!