中断大多都是由外部硬件产生的,例如,键盘,硬盘,光驱等,产生中断后会向处理器发送事件请求信号,中断请求信号可能是数据的读写操作,也可能是控制外部设备,这种中断称为硬件中断,还有一种是软件中断,例如使用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表项是由门描述符组成,可以使用陷进门,中断门,任务门等3类门(在IA-32e中没有任务门)
中断门描述符和陷进门描述符都包含远跳转地址(段选择子和段内偏移),远跳转为处理器提供中断/异常处理程序的入口地址,以下是中断门描述符和陷进门描述符的结构
中断门描述符结构如下
|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标志位进行操作
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将会执行以下步骤
SS
寄存器和RSP
寄存器得值压入栈中,这样从中断处理程序返回时,这可以恢复原始堆栈指针RIP
和CS
寄存器的值压入栈中,这与普通函数一样#PF
)CPU会将用于描述错误信息的错误码压入栈中我们要创建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 {
pointer_low: u16,
gdt_selector: u16,
options: EntryOptions, // u16
pointer_middle: u16,
pointer_high: u32,
reserved: u32,
handler_func: PhantomData, // 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
方便解引用(Debug
trait就不在啰嗦的写出来了)
// 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 ABI
System V应用程序二进制接口)
rdi
, rsi
, rdx
, rcx
, r8
, r9
rax
和rdx
返回extern "C"
声明的函数(遵循C的ABI约定)当一个异常发生后,CPU大概会做一下操作
Preserved 和 Scratch 寄存器
调用将寄存器分为两个部分,Preserved寄存器和Scratch寄存器
在x86_64位系统中:
x86-interrut
调用约定是一个强大的抽象,它几乎隐藏了异常处理过程的所有杂乱细节,它的实现主要归功phil-opp详情可查看Add support for the x86-interrupt calling convention
以下是x86-interrupt
所关注的内容
x86-interrut
调用约定处理所有这些复杂性。但是,它不知道哪个处理程序函数用于哪个异常,因此需要从多个函数参数中推断出该信息。这意味着我们需要为每个异常使用正确的函数类型SSE
指令)需要16字节的堆栈对齐。 CPU会在发生异常时确保这种对齐方式,但是对于某些异常,它会在以后推送错误代码时再次销毁它。在这种情况下,x86-interrut
调用约定通过重新对齐堆栈来解决此问题在Entry
结构中我们使用了handler_func
来保存异常处理函数,在Rust中我们不能将一个指针设置为NULL,并且我们的对于Entry来说,handler_func
是具有拥有关系的,因此使用PhantomData来告诉编译器协变逆变方面的消息(它是一个0大小的,特殊泛型类型)。
我们需要自定义异常处理函数,因此可以编写一个设置自定义异常处理函数的方法
// in system/src/ia_32e/descriptor/idt.rs
impl Entry {
#[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{
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{
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{
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 Entry {
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,
/// #DB
pub debug: Entry,
/// NMI 中断
pub non_maskable_interrupt: Entry,
/// #BP
pub breakpoint: Entry,
/// #OF
pub overflow: Entry,
/// #BR
pub bound_range_exceeded: Entry,
/// #UD
pub invalid_opcode: Entry,
/// #NM
pub device_not_available: Entry,
/// #DF
pub double_fault: Entry,
/// 协处理器段溢出
coprocessor_segment_overrun: Entry,
/// #TS
pub invalid_tss: Entry,
/// #NP
pub segment_not_present: Entry,
/// #SS
pub stack_segment_fault: Entry,
/// #GP
pub general_protection_fault: Entry,
/// #PF
pub page_fault: Entry,
/// 保留
reserved_1: Entry,
/// #MF
pub x87_floating_point: Entry,
/// #AC
pub alignment_check: Entry,
/// #MC
pub machine_check: Entry,
/// #XM
pub simd_floating_point: Entry,
/// #VE
pub virtualization: Entry,
/// #CP 被注释的地方按照Intel卷3的IDT布局加载时会产生segment_not_present异常,原因未知
// pub control_protection_exception: Entry,
/// 22-31 Intel保留使用
// reserved_2: [Entry; 9],
reserved_2: [Entry; 9],
/// #SX
pub security_exception: Entry,
reserved_3: Entry,
/// 用户自定义中断
interrupts: [Entry; 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::() - 1) as u16,
};
unsafe {
lidt(&ptr);
}
}
别忘了self的声明周期是
'static
最后我们希望InterruptDescriptorTable
像操作数组一样操作对应的选项例如
let mut idt = InterruptDescriptorTable::new();
idt[0].set_handler_fn(....);
要实现这样的效果我们需要使用Index
和IndexMut
trait完成这个功能
impl Index for InterruptDescriptorTable {
type Output = Entry;
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 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)相应的功能