中断,异常,以及中断描述符表–IDT
-
关于异常,中断
-
中断描述符表
-
IDT表项
- 保护模式的IDT
- IA-32e模式的IDT
- 中断堆栈帧
-
开始干活
-
拆分中断门/陷进门的结构
- 中断堆栈帧
-
关于调用约定
* Preserved 和 Scratch 寄存器
* x86-interrut调用约定
* 为IDT设置异常处理函数 -
下一步做什么
关于异常,中断
中断大多都是由外部硬件产生的,例如,键盘,硬盘,光驱等,产生中断后会向处理器发送事件请求信号,中断请求信号可能是数据的读写操作,也可能是控制外部设备,这种中断称为硬件中断,还有一种是软件中断,例如使用INT n指令,中断可以在程序执行的过程中触发,每种架构都会对处理器的中断/异常将其归类,并使用数字对同一种类型的中断/异常进行标示(唯一的),这个标示称为中断向量,处理器通过向量号从IDT(中断描述符表Interrupt Descriptor Table)索引出中断/异常处理程序的入口地址,向量号的数值范围是0-255,其中0-31号(共32个)向量被Intel作为异常向量(个别保留使用),剩余32-255供用户使用
在这篇文章中我们只关注与中断向量表,并使用Rust来实现IDT,硬件中断编程和异常处理会有单独的文章介绍
中断描述符表
IDT借助门描述符将中断/异常向量号与处理程序关联起来(就像GDT一样),IDT是一个门描述符数组,每个门描述符占8Byte(64位),第一个表项是有效的,为了使处理器达到最佳性能,将IDT按照8Byte边界对齐,处理器借助IDTR寄存器定位出IDT的位置,使用IDT前需要使用LIDT指令将IDT的线性地址(32位)和长度(16位)加载到IDTR寄存器中(我们可以复用之前编写的DescriptorTablePointer),LIDT指令只能在CPL=0时执行
IDT表项
IDT表项是由门描述符组成,可以使用陷进门,中断门,任务门等3类门(在IA-32e中没有任务门)
保护模式的IDT
中断门描述符和陷进门描述符都包含远跳转地址(段选择子和段内偏移),远跳转为处理器提供中断/异常处理程序的入口地址,以下是中断门描述符和陷进门描述符的结构
中断门描述符结构如下
1
2
3
4
5
6 1|63 - 48|47|46 - 45|44|43|42|41|40|39-37|36 - 32|31 - 16 | 15 - 0 |
2+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
3| Offset |P | DPL |0 |D |1 |1 |0 | 0 | reserved | Selector | Offset |
4+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
5
6
陷进门描述符结构如下
1
2
3
4
5
6 1|63 - 48|47|46 - 45|44|43|42|41|40|39-37|36 - 32|31 - 16 | 15 - 0 |
2+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
3| Offset |P | DPL |0 |D |1 |1 |1 | 0 | reserved | Selector | Offset |
4+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
5
6
中断门和陷进门不同的地方就是在对IF标志位的操作上,处理器执行通过中断门描述符执行程序时,处理器会复位IF标志位防止其他中断请求干扰当前中断程序的执行,处理器会在最后执行IRET指令还原保存的EFLAGS寄存器的值,陷进门不会对IF标志位进行操作
IDT索引过程如下
IA-32e模式的IDT
IA-32e模式的中断/异常处理机制和保护模式的处理机制相似,IA-32e中断发生时的栈空间保存方式由选择性保存(特权级变化时保存),改为无条件保存,IA-32e模式引入了全新的中断栈切换机制
中断门和陷进门描述符结构如下
1
2
3
4
5
6
7
8
9
10
11 1| 127 - 96 | 95 - 64|
2+--------------------------------------------------+----------------------+
3| reserved | Segment Offset |
4+--------------------------------------------------+----------------------+
5
6| 63 - 48 |47|46-45|44|43-40|39-37|36|35|34-32|31 - 16| 15 - 0 |
7+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
8| SgemntOffset |P | DPL |0 | Type| 0 |0 |0 | IST |Selecotr|SegmentOffset|
9+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
10
11
中断门和陷进门描述符都用8B(64位)扩展至16B(128位),高64位保存段内偏移(32-64),低64位用于IST功能
IST只有在IA-32e模式下有效,程序通过IST功能可以让处理无条件的进行栈切换,在IDT的任意一个门描述符都可以使用IST机制或原来的栈切换机制,IST复位时使用旧的栈切换机制,否则使用IST机制
IST位区域用于IST栈表索引,当确定目标IST后,处理器会强制将SS段寄存器赋值为NULL段选择子,并将中断栈地址加载到RSP寄存器中,最后将原SS,RSP,RFLAGS,CS和RIP值压入新栈中
中断堆栈帧
普通的函数通过CALL指令调用,在调用前CPU会将当前的执行地址压入栈中,当函数使用RET指令返回时,CPU会将上次执行的地址从栈中弹出并跳转
函数调用的示意图如下
函数调用后示意图如下
函数远调用示意图如下
函数远返回示意图如下
还记得我们之前写过set_cs函数吗?其中我们用到了一个指令就是LRET我们将段选择子压入栈中并使用LRET指令完成了一个远返回,这样相当于间接设置了CS寄存器,用到的就是这个原理
对于异常/中断的处理,不仅仅只保存返回地址这么简单了,由于中断处理程序通常会在不同的上下文中运行,当一个异常发生时CPU将会执行以下步骤
- 对齐堆栈指针: 异常可能在执行任何指令时发生,所以栈指针也可能指向任意地址,一些CPU的指令要求栈指针必须以16字节边界上对齐,因此,在发生中断后CPU需要立即执行这种对齐
- 切换堆栈: 在特权级改变时将会切换堆栈(例如内核切换到用户),例如当用户模式程序中发生CPU异常时。可以使用中断堆栈表为特定中断切换配置堆栈(IST)
- 保存当前的栈帧(压入栈中):当一个异常/中断发生时,CPU会将当前的SS寄存器和RSP寄存器得值压入栈中,这样从中断处理程序返回时,这可以恢复原始堆栈指针
- 将保存并更新RFLAGS:RFLAGS寄存器包含了各种控制和状态位,进入中断时,CPU更改IF标志位(中断门)并将旧值压入栈中
- 压入栈指针: 在跳转到异常/中断处理程序之前,CPU将会把RIP和CS寄存器的值压入栈中,这与普通函数一样
- 保存错误码(如果有的话):对于一些特殊的异常(例如#PF)CPU会将用于描述错误信息的错误码压入栈中
- 调用异常处理程序:CPU从IDT中的相应字段读取中断处理程序功能的地址和段描述符,然后,通过将值加载到rip和cs寄存器中来调用此处理程序。
发生中断前后栈指针的变化如下
开始干活
拆分中断门/陷进门的结构
我们要创建IDT的结构,提供配套的操作方法以及辅助创建IDT的子结构
我们先明确以下IA-32e中断门/陷进门的结构分解方式
1
2
3
4
5
6
7
8
9
10
11 1| 127 - 96 | 95 - 64|
2+--------------------------------------------------+----------------------+
3| reserved | Segment Offset |
4+--------------------------------------------------+----------------------+
5
6| 63 - 48 |47|46-45|44|43-40|39-37|36|35|34-32|31 - 16| 15 - 0 |
7+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
8| SgemntOffset |P | DPL |0 | Type| 0 |0 |0 | IST |Selecotr|SegmentOffset|
9+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
10
11
偏移地址
第0-15位(共16位)作为段偏移的低地址(low)
第48-63位(共16位)作为段偏移的中地址(middle)
低64-95位(共32位)作为段偏移的高地址(high)
门描述符选项
我们通过观察可以看到第32位到第47位(共16位)是包含门描述符属性等内容我们单独划分出来
段选择子
第16-31位是16位段选择子
保留位和函数和处理函数
保留位总共有64位我们可以将其中的32位作为存放处理函数的地址
根据以上的划分我们编写出以下的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1// in system/src/ia_32e/descriptor/idt.rs
2#[repr(transparent)]
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub struct EntryOptions(u16);
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7#[repr(C)]
8pub struct Entry<F> {
9 pointer_low: u16,
10 gdt_selector: u16,
11 options: EntryOptions, // u16
12 pointer_middle: u16,
13 pointer_high: u32,
14 reserved: u32,
15 handler_func: PhantomData<F>, // u32
16}
17
18
注意字段的顺序一定要按照位图的结构依次排列
随后我们开始为EntryOptions编写功能函数,我们把第32-47位单独提取出来
1
2
3
4
5
6 1|15|14-13|12|11|10|9|8|7 - 5|4|3|2 - 0|
2+--+-----+--+--+--+-+-+-----+-+-+-----+
3|P | DPL |0 |1 |1 |1|1| 0 |0|0| IST |
4+--+-----+--+--+--+-+-+-----+-+-+-----+
5
6
我要对每一个字段提供对应的功能函数
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 1// in system/src/ia_32e/descriptor/idt.rs
2impl EntryOptions {
3 /// 用于设置第47位(表示`已存在`)
4 pub fn set_present(&mut self, present: bool) -> &mut Self {
5 self.0.set_bit(15, present);
6 self
7 }
8 /// 用于设置第46-45位(注意不包含15实际范围是13-14),用于设置特权级
9 pub fn set_privilege_level(&mut self, dpl: PrivilegeLevel) -> &mut Self {
10 self.0.set_bits(13..15, dpl as u16);
11 self
12 }
13 /// 用于设置第40位(用于置1表示陷进门,指令表示中断门),所以我们需要使用取反布尔值来完成此操作
14 pub fn disable_interrupts(&mut self, disable: bool) -> &mut Self {
15 self.0.set_bit(8, !disable);
16 self
17 }
18 /// 设置第34-32位(IST)
19 /// 如果index的范围不再0-7之间将会panic
20 pub unsafe fn set_stack_index(&mut self, index: u16) -> &mut Self {
21 self.0.set_bits(0..3, index + 1);
22 self
23 }
24 /// 创建一个最小选项字段并设置所有必须为1的位
25 const fn minimal() -> Self {
26 EntryOptions(0b1110_0000_0000)
27 }
28}
29
30
中断堆栈帧
在发生异常的时候我们需要获取到CPUpush的中断堆栈帧,这个中断栈帧结构在上文中已经讲述过了,我们定义了以下结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1// in system/src/ia_32e/descriptor/idt.rs
2#[derive(Clone)]
3#[repr(C)]
4pub struct InterruptStackFrameValue {
5 /// RIP寄存器值
6 pub instruction_pointer: VirtAddr,
7 /// CS寄存器值
8 pub code_segment: u64,
9 /// 在调用处理器程序前rflags寄存器的值
10 pub rflags: u64,
11 /// 中断时的堆栈指针。
12 pub stack_pointer: VirtAddr,
13 /// 中断时的堆栈段描述符(在64位模式下通常为零)
14 pub stack_segment: u64,
15}
16
17
我们在建立一个InterruptStackFrame结构对InterruptStackFrameValue做一次封装(主要方便获取地址并能防止意外修改堆栈帧)
1
2
3
4
5
6
7
8
9
10
11
12
13 1// in system/src/ia_32e/descriptor/idt.rs
2#[repr(C)]
3pub struct InterruptStackFrame {
4 value: InterruptStackFrameValue,
5}
6
7impl InterruptStackFrame {
8 pub unsafe fn as_mut(&mut self) -> &mut InterruptStackFrameValue {
9 &mut self.value
10 }
11}
12
13
注意as_mut被声明为unsafe的,这是因为在执行中断处理程序过程中,可能会修改堆栈指针(例如使用时钟中断进行任务调度时),在修改的过程中会写入非法值,这样会导致未定义行为(例如修改了cs,rsp的值)
堆栈帧在调用异常/中断处理函数时由CPU创建并压入栈中,因此我们不需要提供创建栈帧的方法,我们只需要编写Deref方便解引用(Debugtrait就不在啰嗦的写出来了)
1
2
3
4
5
6
7
8
9
10 1// in system/src/ia_32e/descriptor/idt.rs
2impl Deref for InterruptStackFrame {
3 type Target = InterruptStackFrameValue;
4
5 fn deref(&self) -> &Self::Target {
6 &self.value
7 }
8}
9
10
好了,定义完堆栈帧结构后我们着手定义异常处理函数的结构,异常处理函数分为带有错误码的和不带错误码的,但是对于#PF异常错误码有些许不同,我们需要为此单独定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1// in system/src/ia_32e/descriptor/idt.rs
2/// 普通不带有错误返回码的异常处理函数
3pub type HandlerFunc = extern "x86-interrupt" fn(&mut InterruptStackFrame);
4/// 带有错误返回码的异常处理函数
5pub type HandlerFuncWithErrCode = extern "x86-interrupt" fn(&mut InterruptStackFrame,code: u64);
6/// `#PF`异常处理函数
7pub type PageFaultHandlerFunc = extern "x86-interrupt" fn(&mut InterruptStackFrame, code: PageFaultErrorCode); // PageFaultErrorCode需要单独定义
8
9bitflags! {
10 #[repr(transparent)]
11 pub struct PageFaultErrorCode: u64 {
12 const PROTECTION_VIOLATION = 1 << 0;
13 const CAUSED_BY_WRITE = 1 << 1;
14 const USER_MODE = 1 << 2;
15 const MALFORMED_TABLE = 1 << 3;
16 const INSTRUCTION_FETCH = 1 << 4;
17 }
18}
19
20
这里我们可以看到异常处理函数的函数声明有些奇怪,多了extern "x86-interrupt"这样的字段,在签名中extern关键字定义具有外部调用约定的函数通常用于C的调用,例如我们写的pub extern "C" fn _start()函数,但是x86-interrupt调用约定又是什么鬼?
关于调用约定
调用约定指定函数调用的详细信息。例如,指定函数参数的放置位置(例如在寄存器中或在堆栈中)以及如何返回结果,在x86_64 Linux中,C函数也遵循一下约定(特指System V ABISystem V应用程序二进制接口)
- 前六个参数在通用寄存器中传递如rdi, rsi, rdx, rcx, r8, r9
- 其他参数在堆栈上传递
- 结果以rax和rdx返回
Rust是不遵循C的ABI约定的(Rust也没有自己的ABI)这些规则只适用于extern "C"声明的函数(遵循C的ABI约定)
Preserved 和 Scratch 寄存器
当一个异常发生后,CPU大概会做一下操作
- 将某个寄存器的值压入栈中,其中包含指令寄存器和RFLAGS寄存器
- 从IDT中读取响应的条目,例如当发生了段错误后CPU会读取第13号异常
- 检查对应条目是否存在,如果没有则再次抛出异常(Double fault)
- 如果是中断门则关闭硬件中断
- 从GDT中指定特定的选择子加载到CS寄存器中
- 跳转到对应的处理函数
Preserved 和 Scratch 寄存器
调用将寄存器分为两个部分,Preserved寄存器和Scratch寄存器
- Preserved寄存器: 在Preserved寄存器中的值在函数调用的过程中必须保持不变,被调用方在恢复原始值时才可以改变这些寄存器,因此被称为“被调用者保存”,常见模式为在函数开始时将这些寄存器保存在堆栈中,并在函数返回前恢复
- Scratch寄存器:可以允许调用不加限制的更改其中的值,如果调用方想在函数调用时保留暂存寄存器的值,则它需要在函数调用之前备份和还原它,常见模式为在函数调用前将这些寄存器保存在堆栈中,函数结束后再将其恢复
在x86_64位系统中:
- Preserved寄存器(被调用者保存):rbp, rbx, rsp, r12, r13, r14, r15
- Scratch 寄存器 (调用者保存) :rax, rcx, rdx, rsi, rdi, r8, r9, r10, r11
x86-interrut调用约定
x86-interrut调用约定是一个强大的抽象,它几乎隐藏了异常处理过程的所有杂乱细节,它的实现主要归功phil-opp详情可查看Add support for the x86-interrupt calling convention
以下是x86-interrupt所关注的内容
- 大多数调用约定都希望参数在寄存器中传递。对于异常处理程序来说会很麻烦,因为在将它们备份到堆栈之前,一定不能覆盖任何寄存器值。相反,x86-interrupt调用约定知道参数已在堆栈上的特定偏移处
- 由于中断堆栈帧与普通函数调用的堆栈帧完全不同,我们无法通过普通ret指令从处理程序函数中返回,我们必须使用iretq指令来返回
- 一些含有错误代码的异常会变得更加复杂。它会更改堆栈对齐方式,并且需要在返回之前将其弹出堆栈。x86-interrut调用约定处理所有这些复杂性。但是,它不知道哪个处理程序函数用于哪个异常,因此需要从多个函数参数中推断出该信息。这意味着我们需要为每个异常使用正确的函数类型
- 有些指令(尤其是SSE指令)需要16字节的堆栈对齐。 CPU会在发生异常时确保这种对齐方式,但是对于某些异常,它会在以后推送错误代码时再次销毁它。在这种情况下,x86-interrut调用约定通过重新对齐堆栈来解决此问题
为IDT设置异常处理函数
在Entry结构中我们使用了handler_func来保存异常处理函数,在Rust中我们不能将一个指针设置为NULL,并且我们的对于Entry来说,handler_func是具有拥有关系的,因此使用PhantomData来告诉编译器协变逆变方面的消息(它是一个0大小的,特殊泛型类型)。
我们需要自定义异常处理函数,因此可以编写一个设置自定义异常处理函数的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1// in system/src/ia_32e/descriptor/idt.rs
2impl<F> Entry<F> {
3 #[cfg(target_arch = "x86_64")]
4 fn set_handler_addr(&mut self, addr: u64) -> &mut EntryOptions {
5 use crate::ia_32e::instructions::segmention::cs;
6
7 self.pointer_low = addr as u16;
8 self.pointer_middle = (addr >> 16) as u16;
9 self.pointer_high = (addr >> 32) as u32;
10 self.gdt_selector = cs().0;
11 self.options.set_present(true);
12 &mut self.options
13 }
14}
15
16
我们使用了之前编写的cs()函数来获取当前执行的代码段描述符,异常处理函数的地址是64位的Canonical型地址,我们通过位移运算将地址拆分为低,中,高地址
我们针对定义的3种异常处理函数提供如下内容(在进行双重异常处理时会发生无限双重异常,待解决)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1// in system/src/ia_32e/descriptor/idt.rs
2#[cfg(target_arch = "x86_64")]
3impl Entry<HandlerFunc>{
4 pub fn set_handler_fn(&mut self, handler:HandlerFunc) -> &mut EntryOptions{
5 self.set_handler_addr(handler as u64)
6 }
7}
8
9#[cfg(target_arch = "x86_64")]
10impl Entry<HandlerFuncWithErrCode>{
11 pub fn set_handler_fn(&mut self, handler:HandlerFuncWithErrCode) -> &mut EntryOptions{
12 self.set_handler_addr(handler as u64)
13 }
14}
15#[cfg(target_arch = "x86_64")]
16impl Entry<PageFaultHandlerFunc>{
17 pub fn set_handler_fn(&mut self, handler:PageFaultHandlerFunc) -> &mut EntryOptions{
18 self.set_handler_addr(handler as u64)
19 }
20}
21
22
因为Rust的重叠规则(和孤儿规则一样都是为了保持trait的一致性,避免发生混乱),会影响代码的复用,为了更好的性能,只好为每个具体类型实现一遍(也可以使用宏的形式来完成)
最后我们的IDT创建时需要初始化每一项内容,因此我们编写一个初始化方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1// in system/src/ia_32e/descriptor/idt.rs
2impl<F> Entry<F> {
3 pub const fn missing() -> Self {
4 Entry {
5 pointer_low: 0,
6 gdt_selector: 0,
7 options: EntryOptions::minimal(),
8 pointer_middle: 0,
9 pointer_high: 0,
10 reserved: 0,
11 handler_func: PhantomData,
12 }
13 }
14}
15
16
好了,最后我们开始定义IDT的结构
我们需要定义以下异常
0
#DE
Divide Error Fault No DIV and IDIV instructions.
1
#DB
Debug Exception Fault/ Trap No Instruction, data, and I/O breakpoints; single-step; and others.
2
—
NMI Interrupt Interrupt No Nonmaskable external interrupt.
3
#BP
Breakpoint Trap No INT3 instruction.
4
#OF
Overflow Trap No INTO instruction.
5
#BR
BOUND Range Exceeded Fault No BOUND instruction.
6
#UD
Invalid Opcode (Undefined Opcode) Fault No UD instruction or reserved opcode.
7
#NM
Device Not Available (No MathCoprocessor) Fault No Floating-point or WAIT/FWAIT instruction.
8
#DF
Double Fault Abort Yes(zero) Any instruction that can generate anexception, an NMI, or an INTR.
9
Coprocessor Segment Overrun (reserved) Fault No Floating-point instruction.
10
#TS
Invalid TSS Fault Yes Task switch or TSS access.
11
#NP
Segment Not Present Fault Yes Loading segment registers or accessingsystem segments.
12
#SS
Stack-Segment Fault Fault Yes Stack operations and SS register loads.
13
#GP
General Protection Fault Yes Any memory reference and otherprotection checks.
14
#PF
Page Fault Fault Yes Any memory reference.
15
reserved
16
#MF
x87 FPU Floating-Point Error
17
#AC
Alignment Check Exception
18
#MC
Machine Check Exception
19
#XM
SIMD Floating-Point Exception
20
#VE
Virtualization Exception
21
#CP
Contorl Protection Exception
22-31
Intel 保留使用
32-255
用户自定义
没错!就是这么多,一个一个来吧~
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 1// in system/src/ia_32e/descriptor/idt.rs
2#[allow(missing_debug_implementations)]
3#[derive(Clone)]
4#[repr(C)]
5#[repr(align(16))]
6pub struct InterruptDescriptorTable {
7 /// #DE
8 pub divide_by_zero: Entry<HandlerFunc>,
9 /// #DB
10 pub debug: Entry<HandlerFunc>,
11 /// NMI 中断
12 pub non_maskable_interrupt: Entry<HandlerFunc>,
13 /// #BP
14 pub breakpoint: Entry<HandlerFunc>,
15 /// #OF
16 pub overflow: Entry<HandlerFunc>,
17 /// #BR
18 pub bound_range_exceeded: Entry<HandlerFunc>,
19 /// #UD
20 pub invalid_opcode: Entry<HandlerFunc>,
21 /// #NM
22 pub device_not_available: Entry<HandlerFunc>,
23 /// #DF
24 pub double_fault: Entry<HandlerFuncWithErrCode>,
25 /// 协处理器段溢出
26 coprocessor_segment_overrun: Entry<HandlerFunc>,
27 /// #TS
28 pub invalid_tss: Entry<HandlerFuncWithErrCode>,
29 /// #NP
30 pub segment_not_present: Entry<HandlerFuncWithErrCode>,
31 /// #SS
32 pub stack_segment_fault: Entry<HandlerFuncWithErrCode>,
33 /// #GP
34 pub general_protection_fault: Entry<HandlerFuncWithErrCode>,
35 /// #PF
36 pub page_fault: Entry<PageFaultHandlerFunc>,
37 /// 保留
38 reserved_1: Entry<HandlerFunc>,
39 /// #MF
40 pub x87_floating_point: Entry<HandlerFunc>,
41 /// #AC
42 pub alignment_check: Entry<HandlerFuncWithErrCode>,
43 /// #MC
44 pub machine_check: Entry<HandlerFunc>,
45 /// #XM
46 pub simd_floating_point: Entry<HandlerFunc>,
47 /// #VE
48 pub virtualization: Entry<HandlerFunc>,
49 /// #CP 被注释的地方按照Intel卷3的IDT布局加载时会产生segment_not_present异常,原因未知
50 // pub control_protection_exception: Entry<HandlerFuncWithErrCode>,
51 /// 22-31 Intel保留使用
52 // reserved_2: [Entry<HandlerFunc>; 9],
53 reserved_2: [Entry<HandlerFunc>; 9],
54 /// #SX
55 pub security_exception: Entry<HandlerFuncWithErrCode>,
56 reserved_3: Entry<HandlerFunc>,
57 /// 用户自定义中断
58 interrupts: [Entry<HandlerFunc>; 256 - 32],
59}
60
61
62
我们开始编写初始化和重置函数
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 1// in system/src/ia_32e/descriptor/idt.rs
2impl InterruptDescriptorTable {
3 pub const fn new() -> InterruptDescriptorTable {
4 InterruptDescriptorTable {
5 divide_by_zero: Entry::missing(),
6 debug: Entry::missing(),
7 non_maskable_interrupt: Entry::missing(),
8 breakpoint: Entry::missing(),
9 overflow: Entry::missing(),
10 bound_range_exceeded: Entry::missing(),
11 invalid_opcode: Entry::missing(),
12 device_not_available: Entry::missing(),
13 double_fault: Entry::missing(),
14 coprocessor_segment_overrun: Entry::missing(),
15 invalid_tss: Entry::missing(),
16 segment_not_present: Entry::missing(),
17 stack_segment_fault: Entry::missing(),
18 general_protection_fault: Entry::missing(),
19 page_fault: Entry::missing(),
20 reserved_1: Entry::missing(),
21 x87_floating_point: Entry::missing(),
22 alignment_check: Entry::missing(),
23 machine_check: Entry::missing(),
24 simd_floating_point: Entry::missing(),
25 virtualization: Entry::missing(),
26 // reserved_2: [Entry::missing(); 9],
27 // control_protection_exception: Entry::missing(),
28 reserved_2: [Entry::missing(); 9],
29 security_exception: Entry::missing(),
30 reserved_3: Entry::missing(),
31 interrupts: [Entry::missing(); 256 - 32],
32 }
33 }
34
35pub fn reset(&mut self) {
36 self.divide_by_zero = Entry::missing();
37 self.debug = Entry::missing();
38 self.non_maskable_interrupt = Entry::missing();
39 self.breakpoint = Entry::missing();
40 self.overflow = Entry::missing();
41 self.bound_range_exceeded = Entry::missing();
42 self.invalid_opcode = Entry::missing();
43 self.device_not_available = Entry::missing();
44 self.double_fault = Entry::missing();
45 self.coprocessor_segment_overrun = Entry::missing();
46 self.invalid_tss = Entry::missing();
47 self.segment_not_present = Entry::missing();
48 self.stack_segment_fault = Entry::missing();
49 self.general_protection_fault = Entry::missing();
50 self.page_fault = Entry::missing();
51 self.reserved_1 = Entry::missing();
52 self.x87_floating_point = Entry::missing();
53 self.alignment_check = Entry::missing();
54 self.machine_check = Entry::missing();
55 self.simd_floating_point = Entry::missing();
56 self.virtualization = Entry::missing();
57 // self.control_protection_exception = Entry::missing();
58 // self.reserved_2 = [Entry::missing(); 9];
59 self.reserved_2 = [Entry::missing(); 9];
60 self.security_exception = Entry::missing();
61 self.reserved_3 = Entry::missing();
62 self.interrupts = [Entry::missing(); 256 - 32];
63 }
64}
65
66
IDT创建之后我们需要通过lidt指令加载IDT
1
2
3
4
5
6
7
8 1// in system/src/ia_32e/instructions/tables.rs
2....
3#[inline]
4pub unsafe fn lidt(idt: &DescriptorTablePointer) {
5 asm!("lidt ($0)" :: "r" (idt) : "memory");
6}
7
8
我们复用了DescriptorTablePointer来加载IDT,跟GDT一样我们提供一个load方法来加载IDT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1// in system/src/ia_32e/descriptor/idt.rs
2 #[cfg(target_arch = "x86_64")]
3 pub fn load(&'static self) {
4 use crate::ia_32e::instructions::tables::lidt;
5 use crate::ia_32e::descriptor::DescriptorTablePointer;
6 use core::mem::size_of;
7
8 let ptr = DescriptorTablePointer {
9 base: self as *const _ as u64,
10 limit: (size_of::<Self>() - 1) as u16,
11 };
12
13 unsafe {
14 lidt(&ptr);
15 }
16 }
17
18
别忘了self的声明周期是'static
最后我们希望InterruptDescriptorTable像操作数组一样操作对应的选项例如
1
2
3
4 1let mut idt = InterruptDescriptorTable::new();
2idt[0].set_handler_fn(....);
3
4
要实现这样的效果我们需要使用Index和IndexMuttrait完成这个功能
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 1impl Index<usize> for InterruptDescriptorTable {
2 type Output = Entry<HandlerFunc>;
3
4 fn index(&self, index: usize) -> &Self::Output {
5 match index {
6 0 => &self.divide_by_zero,
7 1 => &self.debug,
8 2 => &self.non_maskable_interrupt,
9 3 => &self.breakpoint,
10 4 => &self.overflow,
11 5 => &self.bound_range_exceeded,
12 6 => &self.invalid_opcode,
13 7 => &self.device_not_available,
14 9 => &self.coprocessor_segment_overrun,
15 16 => &self.x87_floating_point,
16 18 => &self.machine_check,
17 19 => &self.simd_floating_point,
18 20 => &self.virtualization,
19 i @ 32..=255 => &self.interrupts[i - 32],
20 i @ 15 | i @ 31 | i @ 22..=29 => panic!("entry {} is reserved", i),
21 // i @ 8 | i @ 10..=14 | i @ 17 | i @ 30 | i @ 22 => {
22 i @ 8 | i @ 10..=14 | i @ 17 | i @ 30 => {
23 panic!("entry {} is an exception with error code", i)
24 },
25 i => panic!("no entry with index {}", i),
26 }
27 }
28}
29
30impl IndexMut<usize> for InterruptDescriptorTable {
31 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
32 match index {
33 0 => &mut self.divide_by_zero,
34 1 => &mut self.debug,
35 2 => &mut self.non_maskable_interrupt,
36 3 => &mut self.breakpoint,
37 4 => &mut self.overflow,
38 5 => &mut self.bound_range_exceeded,
39 6 => &mut self.invalid_opcode,
40 7 => &mut self.device_not_available,
41 9 => &mut self.coprocessor_segment_overrun,
42 16 => &mut self.x87_floating_point,
43 18 => &mut self.machine_check,
44 19 => &mut self.simd_floating_point,
45 20 => &mut self.virtualization,
46 i @ 32..=255 => &mut self.interrupts[i - 32],
47 i @ 15 | i @ 31 | i @ 22..=29 => panic!("entry {} is reserved", i),
48 // i @ 8 | i @ 10..=14 | i @ 17 | i @ 30 | i @ 22 => {
49 i @ 8 | i @ 10..=14 | i @ 17 | i @ 30 => {
50 panic!("entry {} is an exception with error code", i)
51 },
52 i => panic!("no entry with index {}", i),
53 }
54 }
55}
56
57
在Rust中match可以使用范围作为匹配条件,使用..表示一个前闭后开区间,使用..=表示一个闭区间,例如
1
2
3
4
5
6
7
8 1let x = 'h';
2match x{
3 'a' ..= 'z' => println!("lower case"),
4 'A' ..= 'Z' => println!("upper case"),
5 other => println!("{} is not alphabet",other),
6}
7
8
Rust还支持使用@作为变量绑定,@符号前面时新声明的变量,后面是需要匹配的模式
1
2
3
4
5
6
7 1let x = 1;
2match x{
3 e @ 1 ..= 5 => println!("get a range elemnt {}",e),
4 _ => println!("Anything!"),
5}
6
7
至此我们的IDT结构编写完成
下一步做什么
在下一篇文章中我们开始编写PIC(可编程中断控制器,Programmable Interrupt Controller 8259A)相应的功能