寒假OS学习第五天

寒假OS学习第五天

Hurlex学习——添加中断描述符表

中断是事件的基础。

在多道程序运行的时候,依靠中断来切换CPU的工作状态

软件引发的中断叫陷阱

当某个中断发生时,典型的处理方式就是CPU会打断当前的任务,保留当前的执行现场后再转移到该中断事先安排好的中断处理函数

中断由中断控制器产生,通过两条中断线和CPU相连
因此,不同的设备的中断有编号

CPU的中断有两类——非屏蔽中断(NMI)和屏蔽中断(INTR)
非屏蔽中断的意思是需要进行无条件立即处理,意味着CPU遇到了不可挽回的错误
我们关注的是屏蔽中断

Intel处理器允许256个中断,中断号范围0-255

CPU在执行完每一条指令后,都会确认刚才是否发送了中断
有,则CPU会读取对应的中断向量,根据得到的中断向量,到IDT中寻找对应的中断描述符,中断描述符中保存着中断函数的段选择子
使用查到的段选择子,到GDT中选取对应的段描述符
段描述符中保存了中断处理函数的段基址和属性
此时,CPU进行特权检测,确认无误后开始保存当前被打断的程序的现场(需要内核),压入当前程序使用的EFLAGS、CS、EIP与错误代码号
然后,CPU跳转到中断函数处
处理结束后,使用iret或者iretd恢复现场
而错误代码需要中断处理函数主动弹出

在这个过程中CPU的工作是:

  1. 实现中断处理函数。0-19号中断是CPU所有,20-31号中断是保留中断,32-255才是用户自定义中断。但是也有一些约定成俗的中断。

保护模式下的中断

中断的处理运行在ring0层,意味着中断处理程序拥有着系统的全部权限

Intel设置了一个中断描述符表(IDT),和段描述符一样放在贮存里面
有一个中断描述符表寄存器(IDTR)来存储这张表

中断描述符表结构

首先是中断描述符表的结构

可以看到和GDT很相似

设计其数据结构:

#ifndef INCLUDE_IDT_H_
#define INCLUDE_IDT_H_

typedef struct
{
    
    
	uint16_t func_addr_low;
	uint16_t sec_sel;  // section selector
	uint8_t zero;
	uint8_t flags;
	uint16_t func_addr_high;
}idt_t;

typedef struct
{
    
    
	uint16_t limit;
	uint32_t base;
}__attribute__((packed))idtr_t;

#endif

中断处理函数

CPU在中断产生时会自动保存EIP、EFLAGS和CS等,但是其他的寄存器需要内核去进行保护

所有中断处理函数中寄存器的保存和恢复过程时一样的,因此将中断处理函数拆分成3个部分:

  1. 现场保护
  2. 特有逻辑
  3. 现场恢复

由处理器自动压入的寄存器有:

  1. 错误代码
  2. EIP
  3. CS
  4. EFLAGS
  5. USERESP
  6. SS

查阅Intel手册得到更多信息。

再次梳理一下流程:

CPU检查到中断->
CPU自动保存一部分现场。若有错误代码,则CPU自动保存错误代码->
CPU调用内核中断处理函数,此时CS改为内核段->
内核中断处理函数保存CPU尚未保存的现场,并加载内核数据段描述符表(毕竟CPU不干这事)->
内核中断函数调用函数处理逻辑->
处理完毕了,内核中断函数恢复现场->
内核中断函数使用iret退出,CPU自动恢复它的那部分现场

我们的内核处理函数从保护现场开始

由于有些中断还会附带错误码,有些没有,为了方便我们入栈出栈,使用两个函数做区别:

[GLOBAL idt_flush]

idt_flush:
	mov eax, [esp+4]
	lidt [eax]
	ret
.end:

%macro ISR_ERRCODE 1
[GLOBAL isr_%1]

isr_%1:
	cli  ; 关闭中断,因为现在正在处理中断
	push %1  ; 把中断号推进去
	jmp isr_common_stub
%endmacro

%macro ISR_NOERRCODE 1
[GLOBAL isr_%1]

isr_%1:
	cli  ; 关闭中断,因为现在正在处理中断
	push 0  ; 0Errorcode
	push %1  ; 把中断号推进去
	jmp isr_common_stub
%endmacro

ISR_ERRCODE 8   ; 双重故障
ISR_ERRCODE 10  ; 无效TSS
ISR_ERRCODE 11  ; 段不存在
ISR_ERRCODE 12  ; 栈错误
ISR_ERRCODE 13  ; 常规保护
ISR_ERRCODE 14  ; 页故障
ISR_ERRCODE 17  ; 对齐检查

ISR_NOERRCODE 0  ; 除0异常
ISR_NOERRCODE 1  ; 调试异常
ISR_NOERRCODE 2
ISR_NOERRCODE 3  ; 断点异常
ISR_NOERRCODE 4  ; 一簇合
ISR_NOERRCODE 5  ; 对数组的引用超出边界
ISR_NOERRCODE 6  ; 无效的操作码
ISR_NOERRCODE 7  ; 设备不可用
ISR_NOERRCODE 9  ; 协处理器跨段
ISR_NOERRCODE 15  ; 保留
ISR_NOERRCODE 16  ; 浮点处理单元错误
ISR_NOERRCODE 18  ; 机器检查
ISR_NOERRCODE 19  ; 浮点异常
; CPU保留
ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_NOERRCODE 30
ISR_NOERRCODE 31
; 系统调用
ISR_NOERRCODE 255

[EXTERN isr_handler]

isr_common_stub:
	pusha  ; 把edi esi ebp esp ebx edx ecx eax压进去
	mov ax, ds
	push eax  ; 把ds压进去
	push esp  ; 这个时候的esp相当于这个结构体的指针

	mov ax, 0x10  ; 内核数据段描述表
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax

	call isr_handler
	add esp, 4  ; 清除压进去的参数
	pop ebx  ; 把ds放在ebx里面
	mov ds, bx
	mov es, bx
	mov fs, bx
	mov gs, bx
	mov ss, bx

	popa
	add esp, 8  ; 清除error code和ISR

	iret
.end:

具体函数调用流程为:
ISR_ERRCODE/ISR_NOERRCODE->
ISR_COMMON_STUB->
ISR_HANDLER->
具体逻辑

这里使用了NASM的宏操作

接着,我们完善C语言代码

#include "idt.h"
#include "debug.h"
#include "string.h"

#define MAX_IDT_LEN 256

extern void idt_flush(uint32_t);

static idt_t idt[MAX_IDT_LEN];
static idtr_t idtr;
static interrupt_handler_func_t interrupt_handlers[MAX_IDT_LEN];

static void idt_set(uint32_t num, uint32_t func_addr,
	uint8_t sec_sel, uint8_t flags);

void
init_idt()
{
    
    
	// 初始化idt
	memset(idt, 0, sizeof(idt_t) * MAX_IDT_LEN);
	memset(interrupt_handlers, 0, sizeof(interrupt_handler_func_t) * MAX_IDT_LEN);
	// 初始化idtr
	idtr.limit = sizeof(idt_t) * MAX_IDT_LEN - 1;
	idtr.base = (uint32_t)idt;

	idt_set(0, (uint32_t)isr_0, 0x08, 0x8E);
	idt_set(1, (uint32_t)isr_1, 0x08, 0x8E);
	idt_set(2, (uint32_t)isr_2, 0x08, 0x8E);
	idt_set(3, (uint32_t)isr_3, 0x08, 0x8E);
	idt_set(4, (uint32_t)isr_4, 0x08, 0x8E);
	idt_set(5, (uint32_t)isr_5, 0x08, 0x8E);
	idt_set(6, (uint32_t)isr_6, 0x08, 0x8E);
	idt_set(7, (uint32_t)isr_7, 0x08, 0x8E);
	idt_set(8, (uint32_t)isr_8, 0x08, 0x8E);
	idt_set(9, (uint32_t)isr_9, 0x08, 0x8E);
	idt_set(10, (uint32_t)isr_10, 0x08, 0x8E);
	idt_set(11, (uint32_t)isr_11, 0x08, 0x8E);
	idt_set(12, (uint32_t)isr_12, 0x08, 0x8E);
	idt_set(13, (uint32_t)isr_13, 0x08, 0x8E);
	idt_set(14, (uint32_t)isr_14, 0x08, 0x8E);
	idt_set(15, (uint32_t)isr_15, 0x08, 0x8E);
	idt_set(16, (uint32_t)isr_16, 0x08, 0x8E);
	idt_set(17, (uint32_t)isr_17, 0x08, 0x8E);
	idt_set(18, (uint32_t)isr_18, 0x08, 0x8E);
	idt_set(19, (uint32_t)isr_19, 0x08, 0x8E);
	idt_set(20, (uint32_t)isr_20, 0x08, 0x8E);
	idt_set(21, (uint32_t)isr_21, 0x08, 0x8E);
	idt_set(22, (uint32_t)isr_22, 0x08, 0x8E);
	idt_set(23, (uint32_t)isr_23, 0x08, 0x8E);
	idt_set(24, (uint32_t)isr_24, 0x08, 0x8E);
	idt_set(25, (uint32_t)isr_25, 0x08, 0x8E);
	idt_set(26, (uint32_t)isr_26, 0x08, 0x8E);
	idt_set(27, (uint32_t)isr_27, 0x08, 0x8E);
	idt_set(28, (uint32_t)isr_28, 0x08, 0x8E);
	idt_set(29, (uint32_t)isr_29, 0x08, 0x8E);
	idt_set(30, (uint32_t)isr_30, 0x08, 0x8E);
	idt_set(31, (uint32_t)isr_31, 0x08, 0x8E);
	idt_set(255, (uint32_t)isr_255, 0x08, 0x8E);

	idt_flush((uint32_t)&idtr);
}

void
isr_handler(idt_regs_t *regs)
{
    
    
	if (!interrupt_handlers[regs->int_no])
	{
    
    
		print_color(rc_black, rc_red, "Unhandled iterrupt: %d\n", regs->int_no);
		return;
	}
	interrupt_handlers[regs->int_no](regs);
}

void isr_register(uint32_t num, interrupt_handler_func_t func)
{
    
    
	interrupt_handlers[num] = func;
}

static void
idt_set(uint32_t num, uint32_t func_addr,
	uint8_t sec_sel, uint8_t flags)
{
    
    
	idt[num].func_addr_low = func_addr & 0x0FFFF;
	idt[num].func_addr_high = (func_addr >> 16) & 0x0FFFF;

	idt[num].sec_sel = sec_sel;
	idt[num].flags = flags;
	idt[num].zero = 0x0;
}

init函数仿照GDT的init函数写成。

进行实验:

#include "debug.h"
#include "gdt.h"
#include "idt.h"

interrupt_handler_func_t deal(idt_regs_t regs)
{
    
    
	print_color(rc_black, rc_blue, "Segment fault!\n");
	while(1);
}

int kern_entry()
{
    
    
	console_clear();
	init_debug();
	init_gdt();
	init_idt();
	print_color(rc_black, rc_white, "Hello, OS kernel!\n");
	isr_register(0, deal);
	int a = 1 / 0;
	return 0;
}

输出结果

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45206746/article/details/113333353