简介
实验.实现中断处理, 在kernel.s中的中断服务程序中调用在interrupt.c定义的中断函数,并调快时钟
中断的流程
1.外设发出中断信号给8259A芯片
2.8259A芯片处理信号,并向CPU发送中断向量号
3.CPU根据中断向量号,在IDT表中找到对应的中断门描述符
4.从中断门描述符找到kernel.s中的中断服务程序入口地址,跳转到服务程序
5.kernel.s中的中断服务程序中,拿到中断号,执行interrupt.c中的中断处理函数
6.返回
主要代码
引导
省略
内核
main.c
// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始
#include "print.h"
#include "init.h"
int main(void) {
put_str("I am kernel\n");
init_all();
asm volatile("sti"); // 将eflags寄存器的IF位置为1 IF位1,表示不屏蔽可屏蔽外部中断
while (1);
}
init.c
// 文件: init.c
// 时间: 2024-07-22
// 来自: ccj
// 描述: 内核所有初始化操作
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "timer.h"
/// @brief 内核所有初始化
void init_all() {
put_str("init all\n");
idt_init(); // 初始化中断
timer_init(); // 调快时钟
}
interrupt.c
// 文件: interrupt.c
// 时间: 2024-07-22
// 来自: ccj
// 描述: 中断,硬件初始化,初始化中断门描述符表
// 中断描述符表,每个表项叫做一个门描述符(gate descriptor) 门的含义是当中断发生时必须先通过这些门
static struct gate_desc idt[IDT_DESC_CNT];
// 用于保存中断的名字
char* intr_name[IDT_DESC_CNT];
// 声明引用定义在kernel.S中的中断处理函数入口数组
extern intr_handler intr_entry_table[IDT_DESC_CNT];
// 中断处理函数 kernel.S中的中断处理函数入口里调用的这里的函数
intr_handler idt_table[IDT_DESC_CNT];
/// @brief 完成有关中断的所有初始化工作
void idt_init() {
put_str("[int] init start\n");
idt_desc_init(); // 初始化中断描述符表
exception_init(); // 定义每一个中断的中断名和中断处理函数
pic_init(); // 初始化8259A
// 加载中断描述符表 Load Interrupt Descriptor Table
// lidt 2字节中断描述符偏移限制 4字节中断描述表的基地址
// (sizeof(idt) - 1) 为 263
uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
asm volatile("lidt %0" : : "m"(idt_operand));
put_str("[int] init done\n");
}
/// @brief 可编程中断控制器8259A
/// @param
static void pic_init(void) {
...
}
/// @brief 把中断服务程序地址绑定到中断门描述符
/// @param p_gdesc
/// @param attr 属性
/// @param function 中断服务程序
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) {
...
}
/// @brief 把kernel.s的中断服务程序地址写入到中断描述符中
/// @param
static void idt_desc_init(void) {
for (int 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("[int] init int_desc_table done\n");
}
/// @brief 默认中断处理函数
/// @param vec_nr
static void general_intr_handler(uint8_t vec_nr) {
...
}
/// @brief 给每一个中断一个中断名、默认的处理函数
/// @param
static void exception_init(void) {
// 先初始化每一个中断
for (int i = 0; i < IDT_DESC_CNT; i++) {
idt_table[i] = general_intr_handler;
intr_name[i] = "unknown";
}
// 给每一个中断命名
intr_name[0] = "#DE Divide Error";
...
intr_name[19] = "#XF SIMD Floating-Point Exception";
}
kernel.s
; 文件: kernel.s
; 时间: 2024-07-22
; 来自: ccj
; 描述: 产生33个中断服务程序, 构建中断服务程序入口地址表
[bits 32]
; 相当于 #define ZERO (push 0)
%define ERROR_CODE nop
%define ZERO push 0
extern put_str ; 引入外部打印字符串函数
extern idt_table ; 引入在interrupt.c中定义的中断处理函数
section .data
; 重要! 中断服务程序入口地址表 [intr01entry, intr1entry, ..., intr32entry]
global intr_entry_table
intr_entry_table:
;---宏定义 VECTOR 接收2个参数 begin---
%macro VECTOR 2
;---text节 start---
section .text
intr%1entry: ; 中断入口
%2 ; 如果有错误码,错误码入栈
; 保存上下文环境
push ds
push es
push fs
push gs
pushad ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
; 如果是从片上进入的中断, 除了往从片上发送EOI外 ,还要往主片上发送EOI
mov al,0x20 ; 0x20 = EOI
out 0xa0,al ; 向从片发送
out 0x20,al ; 向主片发送
push %1 ; 中断向量号入栈
call [idt_table + %1 * 4] ; 调用interrupt.c的中断处理函数表中向量号的中断处理函数
jmp intr_exit ; 中断执行完成
;---text节 end---
;---data节 begin---
section .data
dd intr%1entry ; 中断入口地址
;---data节 end---
%endmacro
;---宏定义 VECTOR 接收2个参数 end---
section .text
global intr_exit
intr_exit:
add esp, 4 ; 中断号出栈
; 恢复上下文环境
popad
pop gs
pop fs
pop es
pop ds
add esp, 4 ; 错误码出栈
iretd
;---创建中断处理函数 begin---
VECTOR 0x00,ZERO
...
VECTOR 0x20,ZERO
;---创建中断处理函数 end---
timer.c
配置硬件,忽略
编译
省略
运行
start.sh
#/bin/bash
# 文件: start.sh
# 描述: 启动bochs
# 时间: 2024-07-19
# 来自: ccj
set -x
bin/bochs -f bochsrc.disk