使用Rust开发操作系统(中断描述符表--IDT)
阅读原文时间:2021年04月20日阅读:1

中断,异常,以及中断描述符表--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

中断门描述符和陷进门描述符都包含远跳转地址(段选择子和段内偏移),远跳转为处理器提供中断/异常处理程序的入口地址,以下是中断门描述符和陷进门描述符的结构
中断门描述符结构如下

|63 -  48|47|46 - 45|44|43|42|41|40|39-37|36  -    32|31  -   16 | 15 - 0 |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
| Offset |P |  DPL  |0 |D |1 |1 |0 | 0   | reserved  | Selector  | Offset |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+

陷进门描述符结构如下

|63 -  48|47|46 - 45|44|43|42|41|40|39-37|36  -    32|31  -   16 | 15 - 0 |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
| Offset |P |  DPL  |0 |D |1 |1 |1 | 0   | reserved  | Selector  | Offset |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+

中断门和陷进门不同的地方就是在对IF标志位的操作上,处理器执行通过中断门描述符执行程序时,处理器会复位IF标志位防止其他中断请求干扰当前中断程序的执行,处理器会在最后执行IRET指令还原保存的EFLAGS寄存器的值,陷进门不会对IF标志位进行操作

IDT索引过程如下

IA-32e模式的IDT

IA-32e模式的中断/异常处理机制和保护模式的处理机制相似,IA-32e中断发生时的栈空间保存方式由选择性保存(特权级变化时保存),改为无条件保存,IA-32e模式引入了全新的中断栈切换机制

中断门和陷进门描述符结构如下

|   127              -              96             |   95     -         64|
+--------------------------------------------------+----------------------+
|                    reserved                      |     Segment Offset   |
+--------------------------------------------------+----------------------+

|  63  -  48   |47|46-45|44|43-40|39-37|36|35|34-32|31  - 16| 15   -   0  |
+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
| SgemntOffset |P | DPL |0 | Type|  0  |0 |0 | IST |Selecotr|SegmentOffset|
+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+

中断门和陷进门描述符都用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将会把RIPCS寄存器的值压入栈中,这与普通函数一样
  6. 保存错误码(如果有的话):对于一些特殊的异常(例如#PF)CPU会将用于描述错误信息的错误码压入栈中
  7. 调用异常处理程序:CPU从IDT中的相应字段读取中断处理程序功能的地址和段描述符,然后,通过将值加载到rip和cs寄存器中来调用此处理程序。
    发生中断前后栈指针的变化如下

开始干活

拆分中断门/陷进门的结构

我们要创建IDT的结构,提供配套的操作方法以及辅助创建IDT的子结构
我们先明确以下IA-32e中断门/陷进门的结构分解方式

|   127              -              96             |   95     -         64|
+--------------------------------------------------+----------------------+
|                    reserved                      |     Segment Offset   |
+--------------------------------------------------+----------------------+

|  63  -  48   |47|46-45|44|43-40|39-37|36|35|34-32|31  - 16| 15   -   0  |
+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
| SgemntOffset |P | DPL |0 | Type|  0  |0 |0 | IST |Selecotr|SegmentOffset|
+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+

偏移地址
第0-15位(共16位)作为段偏移的低地址(low)
第48-63位(共16位)作为段偏移的中地址(middle)
低64-95位(共32位)作为段偏移的高地址(high)

门描述符选项
我们通过观察可以看到第32位到第47位(共16位)是包含门描述符属性等内容我们单独划分出来

段选择子
第16-31位是16位段选择子

保留位和函数和处理函数
保留位总共有64位我们可以将其中的32位作为存放处理函数的地址

根据以上的划分我们编写出以下的结构

// in system/src/ia_32e/descriptor/idt.rs
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EntryOptions(u16);

#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct Entry<F> {
    pointer_low: u16,
    gdt_selector: u16,
    options: EntryOptions, // u16
    pointer_middle: u16,
    pointer_high: u32,
    reserved: u32,
    handler_func: PhantomData<F>, // u32
}

注意字段的顺序一定要按照位图的结构依次排列

随后我们开始为EntryOptions编写功能函数,我们把第32-47位单独提取出来

|15|14-13|12|11|10|9|8|7 - 5|4|3|2 - 0| 
+--+-----+--+--+--+-+-+-----+-+-+-----+
|P | DPL |0 |1 |1 |1|1|  0  |0|0| IST |
+--+-----+--+--+--+-+-+-----+-+-+-----+

我要对每一个字段提供对应的功能函数

// in system/src/ia_32e/descriptor/idt.rs
impl  EntryOptions {
    /// 用于设置第47位(表示`已存在`)
     pub fn set_present(&mut self, present: bool) -> &mut Self {
        self.0.set_bit(15, present);
        self
    }
    /// 用于设置第46-45位(注意不包含15实际范围是13-14),用于设置特权级
    pub fn set_privilege_level(&mut self, dpl: PrivilegeLevel) -> &mut Self {
        self.0.set_bits(13..15, dpl as u16);
        self
    }
    /// 用于设置第40位(用于置1表示陷进门,指令表示中断门),所以我们需要使用取反布尔值来完成此操作
    pub fn disable_interrupts(&mut self, disable: bool) -> &mut Self {
        self.0.set_bit(8, !disable);
        self
    }
    /// 设置第34-32位(IST)
    /// 如果index的范围不再0-7之间将会panic
    pub unsafe fn set_stack_index(&mut self, index: u16) -> &mut Self {
        self.0.set_bits(0..3, index + 1);
        self
    }
    /// 创建一个最小选项字段并设置所有必须为1的位
    const fn minimal() -> Self {
        EntryOptions(0b1110_0000_0000)
    }
}

中断堆栈帧

在发生异常的时候我们需要获取到CPUpush的中断堆栈帧,这个中断栈帧结构在上文中已经讲述过了,我们定义了以下结构

// in system/src/ia_32e/descriptor/idt.rs
#[derive(Clone)]
#[repr(C)]
pub struct InterruptStackFrameValue {
    /// RIP寄存器值
    pub instruction_pointer: VirtAddr,
    /// CS寄存器值
    pub code_segment: u64,
    /// 在调用处理器程序前rflags寄存器的值
    pub rflags: u64,
    /// 中断时的堆栈指针。
    pub stack_pointer: VirtAddr,
    /// 中断时的堆栈段描述符(在64位模式下通常为零)
    pub stack_segment: u64,
}

我们在建立一个InterruptStackFrame结构对InterruptStackFrameValue做一次封装(主要方便获取地址并能防止意外修改堆栈帧)

// in system/src/ia_32e/descriptor/idt.rs
#[repr(C)]
pub struct InterruptStackFrame {
    value: InterruptStackFrameValue,
}

impl InterruptStackFrame {
    pub unsafe fn as_mut(&mut self) -> &mut InterruptStackFrameValue {
        &mut self.value
    }
}

注意as_mut被声明为unsafe的,这是因为在执行中断处理程序过程中,可能会修改堆栈指针(例如使用时钟中断进行任务调度时),在修改的过程中会写入非法值,这样会导致未定义行为(例如修改了cs,rsp的值)

堆栈帧在调用异常/中断处理函数时由CPU创建并压入栈中,因此我们不需要提供创建栈帧的方法,我们只需要编写Deref方便解引用(Debugtrait就不在啰嗦的写出来了)

// in system/src/ia_32e/descriptor/idt.rs
impl Deref for InterruptStackFrame {
    type Target = InterruptStackFrameValue;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

好了,定义完堆栈帧结构后我们着手定义异常处理函数的结构,异常处理函数分为带有错误码的和不带错误码的,但是对于#PF异常错误码有些许不同,我们需要为此单独定义

// in system/src/ia_32e/descriptor/idt.rs
/// 普通不带有错误返回码的异常处理函数
pub type HandlerFunc = extern "x86-interrupt" fn(&mut InterruptStackFrame);
/// 带有错误返回码的异常处理函数
pub type HandlerFuncWithErrCode = extern "x86-interrupt" fn(&mut InterruptStackFrame,code: u64);
/// `#PF`异常处理函数
pub type PageFaultHandlerFunc = extern "x86-interrupt" fn(&mut InterruptStackFrame, code: PageFaultErrorCode); // PageFaultErrorCode需要单独定义

bitflags! {
    #[repr(transparent)]
    pub struct PageFaultErrorCode: u64 {
        const PROTECTION_VIOLATION = 1 << 0;
        const CAUSED_BY_WRITE = 1 << 1;
        const USER_MODE = 1 << 2;
        const MALFORMED_TABLE = 1 << 3;
        const INSTRUCTION_FETCH = 1 << 4;
    }
}

这里我们可以看到异常处理函数的函数声明有些奇怪,多了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
  • 其他参数在堆栈上传递
  • 结果以raxrdx返回
    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大小的,特殊泛型类型)。
我们需要自定义异常处理函数,因此可以编写一个设置自定义异常处理函数的方法

// in system/src/ia_32e/descriptor/idt.rs
impl<F> Entry<F> {
        #[cfg(target_arch = "x86_64")]
        fn set_handler_addr(&mut self, addr: u64) -> &mut EntryOptions {
            use crate::ia_32e::instructions::segmention::cs;

            self.pointer_low = addr as u16;
            self.pointer_middle = (addr >> 16) as u16;
            self.pointer_high = (addr >> 32) as u32;
            self.gdt_selector = cs().0;
            self.options.set_present(true);
            &mut self.options
        }
}

我们使用了之前编写的cs()函数来获取当前执行的代码段描述符,异常处理函数的地址是64位的Canonical型地址,我们通过位移运算将地址拆分为低,中,高地址
我们针对定义的3种异常处理函数提供如下内容(在进行双重异常处理时会发生无限双重异常,待解决)

// in system/src/ia_32e/descriptor/idt.rs
#[cfg(target_arch = "x86_64")]
impl Entry<HandlerFunc>{
    pub fn set_handler_fn(&mut self, handler:HandlerFunc) -> &mut EntryOptions{
        self.set_handler_addr(handler as u64)
    }
}

#[cfg(target_arch = "x86_64")]
impl Entry<HandlerFuncWithErrCode>{
    pub fn set_handler_fn(&mut self, handler:HandlerFuncWithErrCode) -> &mut EntryOptions{
        self.set_handler_addr(handler as u64)
    }
}
#[cfg(target_arch = "x86_64")]
impl Entry<PageFaultHandlerFunc>{
    pub fn set_handler_fn(&mut self, handler:PageFaultHandlerFunc) -> &mut EntryOptions{
        self.set_handler_addr(handler as u64)
    }
}

因为Rust的重叠规则(和孤儿规则一样都是为了保持trait的一致性,避免发生混乱),会影响代码的复用,为了更好的性能,只好为每个具体类型实现一遍(也可以使用宏的形式来完成)
最后我们的IDT创建时需要初始化每一项内容,因此我们编写一个初始化方法

// in system/src/ia_32e/descriptor/idt.rs
impl<F> Entry<F> {
     pub const fn missing() -> Self {
        Entry {
            pointer_low: 0,
            gdt_selector: 0,
            options: EntryOptions::minimal(),
            pointer_middle: 0,
            pointer_high: 0,
            reserved: 0,
            handler_func: PhantomData,
        }
    }
}

好了,最后我们开始定义IDT的结构
我们需要定义以下异常

Vector

Mne-monic

Description Type Error Code Source

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

用户自定义

没错!就是这么多,一个一个来吧~

// in system/src/ia_32e/descriptor/idt.rs
#[allow(missing_debug_implementations)]
#[derive(Clone)]
#[repr(C)]
#[repr(align(16))]
pub struct InterruptDescriptorTable {
    /// #DE
    pub divide_by_zero: Entry<HandlerFunc>,
    /// #DB
    pub debug: Entry<HandlerFunc>,
    /// NMI 中断
    pub non_maskable_interrupt: Entry<HandlerFunc>,
    /// #BP
    pub breakpoint: Entry<HandlerFunc>,
    /// #OF
    pub overflow: Entry<HandlerFunc>,
    /// #BR
    pub bound_range_exceeded: Entry<HandlerFunc>,
    /// #UD
    pub invalid_opcode: Entry<HandlerFunc>,
    /// #NM
    pub device_not_available: Entry<HandlerFunc>,
    /// #DF
    pub double_fault: Entry<HandlerFuncWithErrCode>,
    /// 协处理器段溢出
    coprocessor_segment_overrun: Entry<HandlerFunc>,
    /// #TS
    pub invalid_tss: Entry<HandlerFuncWithErrCode>,
    /// #NP
    pub segment_not_present: Entry<HandlerFuncWithErrCode>,
    /// #SS
    pub stack_segment_fault: Entry<HandlerFuncWithErrCode>,
     /// #GP
    pub general_protection_fault: Entry<HandlerFuncWithErrCode>,
     /// #PF
    pub page_fault: Entry<PageFaultHandlerFunc>,
    /// 保留
    reserved_1: Entry<HandlerFunc>,
    /// #MF
    pub x87_floating_point: Entry<HandlerFunc>,
    /// #AC
    pub alignment_check: Entry<HandlerFuncWithErrCode>,
    /// #MC
    pub machine_check: Entry<HandlerFunc>,
    /// #XM
    pub simd_floating_point: Entry<HandlerFunc>,
    /// #VE
    pub virtualization: Entry<HandlerFunc>,
    /// #CP 被注释的地方按照Intel卷3的IDT布局加载时会产生segment_not_present异常,原因未知
    // pub control_protection_exception: Entry<HandlerFuncWithErrCode>,
    /// 22-31 Intel保留使用
    // reserved_2: [Entry<HandlerFunc>; 9],
    reserved_2: [Entry<HandlerFunc>; 9],
    /// #SX
    pub security_exception: Entry<HandlerFuncWithErrCode>,
    reserved_3: Entry<HandlerFunc>,
    /// 用户自定义中断
    interrupts: [Entry<HandlerFunc>; 256 - 32],
}

我们开始编写初始化和重置函数

// in system/src/ia_32e/descriptor/idt.rs
impl InterruptDescriptorTable {
    pub const fn new() -> InterruptDescriptorTable {
        InterruptDescriptorTable {
            divide_by_zero: Entry::missing(),
            debug: Entry::missing(),
            non_maskable_interrupt: Entry::missing(),
            breakpoint: Entry::missing(),
            overflow: Entry::missing(),
            bound_range_exceeded: Entry::missing(),
            invalid_opcode: Entry::missing(),
            device_not_available: Entry::missing(),
            double_fault: Entry::missing(),
            coprocessor_segment_overrun: Entry::missing(),
            invalid_tss: Entry::missing(),
            segment_not_present: Entry::missing(),
            stack_segment_fault: Entry::missing(),
            general_protection_fault: Entry::missing(),
            page_fault: Entry::missing(),
            reserved_1: Entry::missing(),
            x87_floating_point: Entry::missing(),
            alignment_check: Entry::missing(),
            machine_check: Entry::missing(),
            simd_floating_point: Entry::missing(),
            virtualization: Entry::missing(),
            // reserved_2: [Entry::missing(); 9],
            // control_protection_exception: Entry::missing(),
            reserved_2: [Entry::missing(); 9],
            security_exception: Entry::missing(),
            reserved_3: Entry::missing(),
            interrupts: [Entry::missing(); 256 - 32],
        }
    }

pub fn reset(&mut self) {
        self.divide_by_zero = Entry::missing();
        self.debug = Entry::missing();
        self.non_maskable_interrupt = Entry::missing();
        self.breakpoint = Entry::missing();
        self.overflow = Entry::missing();
        self.bound_range_exceeded = Entry::missing();
        self.invalid_opcode = Entry::missing();
        self.device_not_available = Entry::missing();
        self.double_fault = Entry::missing();
        self.coprocessor_segment_overrun = Entry::missing();
        self.invalid_tss = Entry::missing();
        self.segment_not_present = Entry::missing();
        self.stack_segment_fault = Entry::missing();
        self.general_protection_fault = Entry::missing();
        self.page_fault = Entry::missing();
        self.reserved_1 = Entry::missing();
        self.x87_floating_point = Entry::missing();
        self.alignment_check = Entry::missing();
        self.machine_check = Entry::missing();
        self.simd_floating_point = Entry::missing();
        self.virtualization = Entry::missing();
        // self.control_protection_exception = Entry::missing();
        // self.reserved_2 = [Entry::missing(); 9];
        self.reserved_2 = [Entry::missing(); 9];
        self.security_exception = Entry::missing();
        self.reserved_3 = Entry::missing();
        self.interrupts = [Entry::missing(); 256 - 32];
    }
}

IDT创建之后我们需要通过lidt指令加载IDT

// in system/src/ia_32e/instructions/tables.rs
....
#[inline]
pub unsafe fn lidt(idt: &DescriptorTablePointer) {
    asm!("lidt ($0)" :: "r" (idt) : "memory");
}

我们复用了DescriptorTablePointer来加载IDT,跟GDT一样我们提供一个load方法来加载IDT

// in system/src/ia_32e/descriptor/idt.rs
 #[cfg(target_arch = "x86_64")]
    pub fn load(&'static self) {
        use crate::ia_32e::instructions::tables::lidt;
        use crate::ia_32e::descriptor::DescriptorTablePointer;
        use core::mem::size_of;

        let ptr = DescriptorTablePointer {
            base: self as *const _ as u64,
            limit: (size_of::<Self>() - 1) as u16,
        };

        unsafe {
            lidt(&ptr);
        }
    }

别忘了self的声明周期是'static

最后我们希望InterruptDescriptorTable像操作数组一样操作对应的选项例如

let mut idt = InterruptDescriptorTable::new();
idt[0].set_handler_fn(....);

要实现这样的效果我们需要使用IndexIndexMuttrait完成这个功能

impl Index<usize> for InterruptDescriptorTable {
    type Output = Entry<HandlerFunc>;

    fn index(&self, index: usize) -> &Self::Output {
        match index {
            0 => &self.divide_by_zero,
            1 => &self.debug,
            2 => &self.non_maskable_interrupt,
            3 => &self.breakpoint,
            4 => &self.overflow,
            5 => &self.bound_range_exceeded,
            6 => &self.invalid_opcode,
            7 => &self.device_not_available,
            9 => &self.coprocessor_segment_overrun,
            16 => &self.x87_floating_point,
            18 => &self.machine_check,
            19 => &self.simd_floating_point,
            20 => &self.virtualization,
            i @ 32..=255 => &self.interrupts[i - 32],
            i @ 15 | i @ 31 | i @ 22..=29 => panic!("entry {} is reserved", i),
            // i @ 8 | i @ 10..=14 | i @ 17 | i @ 30 | i @ 22 => {
            i @ 8 | i @ 10..=14 | i @ 17 | i @ 30  => {
                panic!("entry {} is an exception with error code", i)
            },
            i => panic!("no entry with index {}", i),
        }
    }
}

impl IndexMut<usize> for InterruptDescriptorTable {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        match index {
            0 => &mut self.divide_by_zero,
            1 => &mut self.debug,
            2 => &mut self.non_maskable_interrupt,
            3 => &mut self.breakpoint,
            4 => &mut self.overflow,
            5 => &mut self.bound_range_exceeded,
            6 => &mut self.invalid_opcode,
            7 => &mut self.device_not_available,
            9 => &mut self.coprocessor_segment_overrun,
            16 => &mut self.x87_floating_point,
            18 => &mut self.machine_check,
            19 => &mut self.simd_floating_point,
            20 => &mut self.virtualization,
            i @ 32..=255 => &mut self.interrupts[i - 32],
            i @ 15 | i @ 31 | i @ 22..=29 => panic!("entry {} is reserved", i),
            // i @ 8 | i @ 10..=14 | i @ 17 | i @ 30 | i @ 22 => {
            i @ 8 | i @ 10..=14 | i @ 17 | i @ 30  => {
                panic!("entry {} is an exception with error code", i)
            },
            i => panic!("no entry with index {}", i),
        }
    }
}

在Rust中match可以使用范围作为匹配条件,使用..表示一个前闭后开区间,使用..=表示一个闭区间,例如

let x = 'h';
match x{
    'a' ..= 'z' => println!("lower case"),
    'A' ..= 'Z' => println!("upper case"),
    other => println!("{} is not alphabet",other),
}

Rust还支持使用@作为变量绑定,@符号前面时新声明的变量,后面是需要匹配的模式

let x = 1;
match x{
    e @ 1 ..= 5 => println!("get a range elemnt {}",e),
    _ => println!("Anything!"),
}

至此我们的IDT结构编写完成

下一步做什么

在下一篇文章中我们开始编写PIC(可编程中断控制器,Programmable Interrupt Controller 8259A)相应的功能

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器