使用Rust开发操作系统(中断描述符表–IDT)

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

中断,异常,以及中断描述符表–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将会执行以下步骤

  1. 对齐堆栈指针: 异常可能在执行任何指令时发生,所以栈指针也可能指向任意地址,一些CPU的指令要求栈指针必须以16字节边界上对齐,因此,在发生中断后CPU需要立即执行这种对齐
  2. 切换堆栈: 在特权级改变时将会切换堆栈(例如内核切换到用户),例如当用户模式程序中发生CPU异常时。可以使用中断堆栈表为特定中断切换配置堆栈(IST)
  3. 保存当前的栈帧(压入栈中):当一个异常/中断发生时,CPU会将当前的SS寄存器和RSP寄存器得值压入栈中,这样从中断处理程序返回时,这可以恢复原始堆栈指针
  4. 将保存并更新RFLAGS:RFLAGS寄存器包含了各种控制和状态位,进入中断时,CPU更改IF标志位(中断门)并将旧值压入栈中
  5. 压入栈指针: 在跳转到异常/中断处理程序之前,CPU将会把RIP和CS寄存器的值压入栈中,这与普通函数一样
  6. 保存错误码(如果有的话):对于一些特殊的异常(例如#PF)CPU会将用于描述错误信息的错误码压入栈中
  7. 调用异常处理程序: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大概会做一下操作

  1. 将某个寄存器的值压入栈中,其中包含指令寄存器和RFLAGS寄存器
  2. 从IDT中读取响应的条目,例如当发生了段错误后CPU会读取第13号异常
  3. 检查对应条目是否存在,如果没有则再次抛出异常(Double fault)
  4. 如果是中断门则关闭硬件中断
  5. 从GDT中指定特定的选择子加载到CS寄存器中
  6. 跳转到对应的处理函数

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)相应的功能

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

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

2022-1-11 12:36:11

安全资讯

710亿元!马斯克成2020年全球薪酬最高CEO

2021-8-16 15:36:11

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