使用Rust开发操作系统(异常处理)

释放双眼,带上耳机,听听看~!

异常处理

  • 注册异常处理函数

  • 定义异常处理函数

    • 注册异常处理函数
  • 双重异常Double Faults

  • 堆栈切换

    • 为双重处理添加一个堆栈
    • 注册双重异常处理函数
  • 下一步要做什么?

在上一篇文章中我们完成了对GDT,IDT,TSS以及PIC8259A的初始化以及加载工作,现在我们需要为操作系添加一些异常处理功能,并且使用8259A编写一个简易的键盘驱动

注册异常处理函数

在实现IDT的过程中我们提到了x86-interrut调用约定(详情请查看使用Rust开发操作系统(中断描述符表–IDT)关于调用约定章节)注册处理函数的诀窍是 首先编写一个 extern x86-interrupt中断处理函数,函数的参数是根据你要处理异常的类别去传递的,主要分为2种,带错误码的和不带错误码的,然后将编写的函数注册到对应的异常中去
例如:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1// 注:库及结构已经更改过,请以github代码为准(https://github.com/VenmoTools/OperatingSystem)
2use system::ia_32e::descriptor::{InterruptDescriptorTable, InterruptStackFrame};
3use system::bits::PageFaultErrorCode;
4
5// 不带错误码
6extern "x86-interrupt" fn example_interrupt(_stackframe: &mut InterruptStackFrame) {
7}
8// 带错误码(错误码需要解析)
9extern "x86-interrupt" fn exmple_with_err_code(stackframe: &mut InterruptStackFrame, err: u64) {
10
11}
12// 页异常需要单独的错误码
13extern "x86-interrupt" fn page_fault(stackframe: &mut InterruptStackFrame, code: PageFaultErrorCode) {
14}
15
16

在使用之前一定在kernel/src/main.rs或者kernel/src/lib.rs文件的头部添加一下内容


1
2
3
1#![feature(abi_x86_interrupt)]
2
3

注册异常处理函数例子


1
2
3
4
5
6
7
8
9
1extern "x86-interrupt" fn page_fault(stackframe: &mut InterruptStackFrame, code: PageFaultErrorCode) {
2   ......
3}
4....
5let mut idt = InterruptDescriptorTable::new();
6idt.page_fault.set_handler_fn(page_fault);
7....
8
9

需要注意的是,中断和异常都需要用到IDT,注册中断处理函数和异常处理函数的方式有很大区别在下文中我们详细阐述注册中断处理函数

定义异常处理函数

好了,掌握异常处理函数的注册技巧后我们开始注册20个异常处理函数(木有办法。。就是这么多),我们暂时只打印异常信息不做具体处理(为了完整性,列出了所有异常)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
1// in kernel/src/descriptor/idt.rs
2// 调试中断
3extern "x86-interrupt" fn breakpoint(stackframe: &mut InterruptStackFrame) {
4    println!("EXCEPTION:BREAKPOINT\n{:#?}", stackframe);
5}
6// 缺页错误
7extern "x86-interrupt" fn page_fault(stackframe: &mut InterruptStackFrame, code: PageFaultErrorCode) {
8    println!("EXCEPTION:PageFault");
9    println!("Error Code:{:?}", code);
10    println!("{:#?}", stackframe);
11    loop_hlt();
12}
13// 除零错误
14extern "x86-interrupt" fn divide_by_zero(stackframe: &mut InterruptStackFrame) {
15    println!("EXCEPTION:divide_by_zero\n{:#?}", stackframe);
16}
17// NMI不可屏蔽中断
18extern "x86-interrupt" fn non_maskable_interrupt(stackframe: &mut InterruptStackFrame) {
19    println!("EXCEPTION:non_maskable_interrupt\n{:#?}", stackframe);
20}
21// 调试
22extern "x86-interrupt" fn debug(stackframe: &mut InterruptStackFrame) {
23    println!("EXCEPTION:debug\n{:#?}", stackframe);
24}
25/// 溢出异常
26extern "x86-interrupt" fn overflow(stackframe: &mut InterruptStackFrame) {
27    println!("EXCEPTION:overflow\n{:#?}", stackframe);
28}
29/// 超出范围
30extern "x86-interrupt" fn bound_range_exceeded(stackframe: &mut InterruptStackFrame) {
31    println!("EXCEPTION:bound_range_exceeded\n{:#?}", stackframe);
32}
33/// 无效的操作码
34extern "x86-interrupt" fn invalid_opcode(stackframe: &mut InterruptStackFrame) {
35    println!("EXCEPTION:invalid_opcode\n{:#?}", stackframe);
36}
37/// 设备不可用
38extern "x86-interrupt" fn device_not_available(stackframe: &mut InterruptStackFrame) {
39    println!("EXCEPTION:device_not_available\n{:#?}", stackframe);
40}
41/// x87浮点异常
42extern "x86-interrupt" fn x87_floating_point(stackframe: &mut InterruptStackFrame) {
43    println!("EXCEPTION:x87_floating_point\n{:#?}", stackframe);
44}
45/// 机器检查
46extern "x86-interrupt" fn machine_check(stackframe: &mut InterruptStackFrame) {
47    println!("EXCEPTION:machine_check\n{:#?}", stackframe);
48}
49/// SIMD浮点异常
50extern "x86-interrupt" fn simd_floating_point(stackframe: &mut InterruptStackFrame) {
51    println!("EXCEPTION:simd_floating_point\n{:#?}", stackframe);
52}
53/// 虚拟化异常
54extern "x86-interrupt" fn virtualization(stackframe: &mut InterruptStackFrame) {
55    println!("EXCEPTION:virtualization\n{:#?}", stackframe);
56}
57/// 无效TSS
58extern "x86-interrupt" fn invalid_tss(stackframe: &mut InterruptStackFrame, _err: u64) {
59    println!("EXCEPTION:invalid_tss\n{:#?}\nCode:{:#?}\n", stackframe, _err);
60}
61/// 段不存在
62extern "x86-interrupt" fn segment_not_present(stackframe: &mut InterruptStackFrame, _err: u64) {
63    println!("EXCEPTION:segment_not_present\n{:#?}\nCode:{:#?}\n", stackframe, _err);
64}
65/// 堆栈段错误
66extern "x86-interrupt" fn stack_segment_fault(stackframe: &mut InterruptStackFrame, _err: u64) {
67    println!("EXCEPTION:stack_segment_fault\n{:#?}\nCode:{:#?}\n", stackframe, _err);
68}
69/// 常规保护异常
70extern "x86-interrupt" fn general_protection_fault(stackframe: &mut InterruptStackFrame, _err: u64) {
71    println!("EXCEPTION:general_protection_fault\n{:#?}\nCode:{:#?}\n", stackframe, _err);
72}
73/// 对齐检查异常
74extern "x86-interrupt" fn alignment_check(stackframe: &mut InterruptStackFrame, _err: u64) {
75    println!("EXCEPTION:alignment_check\n{:#?}\nCode:{:#?}\n", stackframe, _err);
76}
77/// 安全检查异常
78extern "x86-interrupt" fn security_exception(stackframe: &mut InterruptStackFrame, _err: u64) {
79    println!("EXCEPTION:security_exception\n{:#?}\nCode:{:#?}\n", stackframe, _err);
80}
81
82

注册异常处理函数

好了,我们完成了19个异常处理函数,还有一个双重异常处理函数我们在下文编写

然后我们开始注册


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1// in kernel/src/descriptor/idt.rs
2use system::ia_32e::descriptor::{InterruptDescriptorTable, InterruptStackFrame};
3use system::bits::PageFaultErrorCode;
4
5lazy_static! {
6    static ref IDT: InterruptDescriptorTable = {
7        let mut idt = InterruptDescriptorTable::new();
8        idt.breakpoint.set_handler_fn(breakpoint);
9        idt.page_fault.set_handler_fn(page_fault);
10        idt.divide_by_zero.set_handler_fn(divide_by_zero);
11        idt.invalid_tss.set_handler_fn(invalid_tss);
12        idt.security_exception.set_handler_fn(security_exception);
13        idt.segment_not_present.set_handler_fn(segment_not_present);
14        idt.alignment_check.set_handler_fn(alignment_check);
15        idt.bound_range_exceeded.set_handler_fn(bound_range_exceeded);
16        idt.device_not_available.set_handler_fn(device_not_available);
17        idt.general_protection_fault.set_handler_fn(general_protection_fault);
18        idt.invalid_opcode.set_handler_fn(invalid_opcode);
19        idt.machine_check.set_handler_fn(machine_check);
20        idt.non_maskable_interrupt.set_handler_fn(non_maskable_interrupt);
21        idt.virtualization.set_handler_fn(virtualization);
22        idt.x87_floating_point.set_handler_fn(x87_floating_point);
23        idt.stack_segment_fault.set_handler_fn(stack_segment_fault);
24        idt.simd_floating_point.set_handler_fn(simd_floating_point);
25        idt.overflow.set_handler_fn(overflow);
26        idt.debug.set_handler_fn(debug);
27        idt
28    };
29}
30
31

好了,这样子我们就注册成功了,当发生异常的时候我们就可以显示异常的信息了

双重异常Double Faults

当系统正在处理一个异常时,如果又检测到一个异常,处理器试图向系统通知一个双重故障

双重故障的行为类似于普通异常,但是提供双重故障处理程序确实很重要,因为如果未处理双重故障,则会发生致命的三次故障。无法捕获三重故障,大多数硬件都会对复位系统。

我们为其编写一个双重异常处理函数


1
2
3
4
5
1extern "x86-interrupt" fn double_fault(stackframe: &mut InterruptStackFrame, _err: u64) {
2    println!("EXCEPTION:DOUBLE FAULT\n{:#?}", stackframe);
3}
4
5

我们编写的这个双重异常处理函数似乎跟其他的异常处理函数没有什么区别,但是我们能捕获一个基本的双重异常,防止硬件对系统进行复位,但是双重异常处理没有这么简单,在开头我们只说了双重错误是一种特殊的异常,它发生在CPU无法调用异常处理程序时,那么发生异常时转到对应的异常处理函数时如何恢复?当处理一个异常时又发生一个异常又该怎么处理?

在AMD64手册具有确切的定义(在第8.2.9节中)

在处理先前(第一个)异常处理程序期间发生第二个异常时,可能会发生双重错误异常”。这个 “可能”很重要:只有异常的非常特殊的组合才会导致双重错误。这些组合是


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1+--------------------------+------------------------+
2| First Exception          |Second Exception        |
3+--------------------------+------------------------+
4|Divide-by-zero,           |Invalid TSS,            |
5|Invalid TSS,              |Segment Not Present,    |
6|Segment Not Present,      |Stack-Segment Fault,    |
7|Stack-Segment Fault,      |General Protection Fault|
8|General Protection Fault  |                        |
9+--------------------------+------------------------+
10|                          |Page Fault,             |
11|                          |Invalid TSS,            |
12|Page Fault                |Segment Not Present,    |
13|                          |Stack-Segment Fault,    |
14|                          |General Protection Fault|
15+--------------------------+------------------------+
16
17

例如:

当正处理一个Divide-by-zero异常时,有可能又产生一个Page Fault异常。在这种情况下,通知给系统的是一个Page Fault异常而不是Double Fault异常。但是,如果正处理一个Invalid TSS时,又产生了一个General Protection Fault|;或者如果正处理一个Page Fault时,又产生了Page Fault,那么就引起双重故障。

实际上,即使是没有注册对应处理程序的情况也遵循此方案,使用没有注册的异常处理函数会导致Segment Not Present的异常。假如没有定义一个Segment Not Present的处理程序,因此发生Segment Not Present的异常。根据表,这样会导致双重异常

堆栈切换

在发生中断或者系统调用时会发生堆栈切换,在切换的过程中会涉及到特权级的改变,幸运的是在IA-32e架构下提供了自动切换栈帧的机制(中断堆栈表的堆栈切换机制 IST)

注:只64非兼容模式下,64位兼容模式依旧按照32位堆栈切换方式

在IA-32e模式中提供了7个栈指针,共IDT使用,IST位就是用于为中断/异常处理程序提供IST栈表索引,当确定目标以后会强制将SS段寄存器赋值为NULL选择子,并将中断栈地址加载到RSP寄存器中,最后将原SS,RSP,RFLAGS,CS和RIP寄存器压入新栈中

为双重处理添加一个堆栈

知道如何切换堆栈后我们为双重异常注册一个栈,tss使用的诀窍是需要向对应的ISP索引处添加一个堆栈方便进行上线文切换,记住,栈的生长方向是从高地址到低地址,因此我们需要把高地址注册到TSS中


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1// in kernel/descriptor/gdt.rs
2
3// IST索引值为0,其他IST索引也可以
4pub const DOUBLE_FAULT_LIST_INDEX: u16 = 0;
5
6lazy_static! {
7    // 现在的双重故障堆栈没有保护页面以防止堆栈溢出(还没有做内存管理),因此不能在双重故障处理程序中执行任何占用大量堆栈的操作
8    static ref TSS: TaskStateSegment = {
9        let mut tss = TaskStateSegment::new();
10        let stack_len =
11        {
12            // 这里STACK_SIZE需要显示写出是usize的,因为我们的VirtAddr只完成了对usize的加法操作
13            // 按照4kb对齐刚好是一个页面
14            const STACK_SIZE:usize = 4096;
15            // 指定使用的栈大小位4096
16            static mut STACK:[u8;STACK_SIZE] = [0;STACK_SIZE];
17            // 获取STACK的虚拟地址
18            let stack_start = VirtAddr::from_pointer(unsafe{&STACK});
19            // 栈的生长方向是高地址向低地址我们添加栈的高地址
20            let stack_end = stack_start + STACK_SIZE;
21            stack_end
22        };
23        tss.interrupt_stack_table[DOUBLE_FAULT_LIST_INDEX as usize] = stack_len;
24        tss
25    };
26}
27
28

注册双重异常处理函数

然后我们可以注册我们的双重异常处理函数了


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1lazy_static! {
2    static ref IDT: InterruptDescriptorTable = {
3       ....
4        idt.simd_floating_point.set_handler_fn(simd_floating_point);
5        idt.overflow.set_handler_fn(overflow);
6        idt.debug.set_handler_fn(debug);
7        // 在IDT中为双重错误处理程序设置堆栈索引
8        unsafe {
9            idt.double_fault.set_handler_fn(double_fault).set_stack_index(gdt::DOUBLE_FAULT_LIST_INDEX);
10        }
11        idt
12    };
13}
14
15

我们将堆栈索引的地址放到IST位(具体请查看使用Rust开发操作系统(中断描述符表–IDT)拆分中断门/陷进门的结构一节)

这样我们的双重处理函数就注册完毕

下一步要做什么?

在下一篇文章中我们将要学习如何编写中断处理函数,以及提供基本的时钟中断

给TA打赏
共{{data.count}}人
人已打赏
安全技术

c++ list, vector, map, set 区别与用法比较

2022-1-11 12:36:11

安全运维

Docker的Jenkins更新war文件

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索