目前为止的OS流程

Init1 - > 中断初始化
cpp
/*完成有关中断的所有初始化工作*/
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");
}
中断描述符表
中断描述符表固定在内存中,通过IDTR寄存器指明内存地址。当CPU接受到中断时,会根据中断向量检索对应的描述符。执行对应的中断处理程序。

为了简化实现,中断描述符表中只有中断门描述符。由低位地址(16位)+选择子(8位)+dount+attribute(8位 10001110)+高位地址(16位)组成。
cpp
/*中断门描述符结构体*/
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) {
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]);
}
make_idt_desc(&idt[lastindex],IDT_DESC_ATTR_DPL3,syscall_handler);
/* 单独处理系统调用,系统调用对应的中断门dpl为3,
* 中断处理程序为单独的syscall_handler */
put_str(" idt_desc_init done\n");
}
中断门描述符的函数是从129个函数指针数组中拿出的function,intr_entry_table每个指针指向了一个中断入口函数,执行流程是:

其中最后一个0x80中断特殊,是系统调用的通用入口,系统调用的attribute不同,DPL为3,是用户态可调用的。保证了用户程序可使用。
系统调用实现
bash
//用户态使用
// 用户程序
int ret = syscall(SYS_WRITE, fd, buf, count);
// ↑ ↑ ↑ ↑
// eax ebx ecx edx
section .text
global syscall_handler
syscall_handler:
push 0
push ds
push es
push fs
push gs
pushad
push 0x80 ; 不管是否需要参数,都一律压入中断向量号,调试时很方便
push edx ;第三个参数
push ecx ;第二个参数
push ebx ;第一个参数
call [syscall_table + eax*4]
add esp,12 ;清理 push edx/ecx/ebx
mov [esp + 8*4],eax ;返回值给放到eax中 ,修改栈中的eax值
jmp intr_exit
中断处理函数
中断处理函数根据硬件和和向量号不同各不相同,所以提供两种注册方式。
exception_init为所有的中断向量提供兜底的基础中断处理函数。
register_handler为不同硬件初始化时自行注册各自的中断处理函数。
cpp
/* 完成一般中断处理函数注册及异常名称注册 */
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";
//intr_name[80] = "SYSCALL INTR";
}
void register_handler(uint8_t vec_no,intr_handler function)
{
//把相关向量号的注册函数指针放进去了
idt_table[vec_no] = function;
}
PIC
初始化中断控制器8259A。
CPU只有两个中断源引脚,可屏蔽中断和不可屏蔽中断。但是实际PC有多个外设中断,所以需要8259A来管理中断。复用CPU的中断引脚。
提供了以下作用:
- 中断复用
- 优先级管理
- 中断屏蔽
- 中断向量生成
- 级联控制
写入中断向量表地址
cpp
uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
asm volatile("lidt %0" : : "m" (idt_operand));
其余功能
开关中断+获得中断状态。
cpp
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);
enum intr_status intr_set_status(enum intr_status status);
enum intr_status intr_get_status(void);