中断处理
中断处理是由CPU和操作系统协作完成的,当中断发生时,CPU会根据中断号在系统数据结构IDT
(中断描述符表)中取出中断处理例程对应的gate descriptor
(中断描述符),gate descriptor
指向GDT
(全局描述符表)中的一个段描述符。段描述符和gate descriptor
中的段选择子被载入cs
(代码段)寄存器,最后CPU执行特权级的检查,如果目标代码段特权级比当前特权级高,将会从TSS
中取出对应的栈指针rsp
,切换栈(例如用户态发生中断,中断处理程序的目标代码段处于0特权级,这时从TSS中取出内核栈rsp并切换到内核栈)。最终CPU压入一些寄存器,跳转到中断号对应的中断向量去执行。
上述过程涉及大量x86体系结构的处理细节,若不能完全看懂也没关系,知道中断发生到中断处理例程中间的部分是由CPU完成的,并且可能涉及栈的切换即可。可以宏观上理解,TSS只是内存中的一个数据结构,但是其与CPU交互,其中偏移量为4的字段存放了当前线程的内核栈位置,当发生系统调用或中断从用户态进入内核时,需要手动(系统调用)或是CPU自动(用户态发生中断)地从TSS中取出内核栈并切换栈。
/// 中断处理程序的参数
///
/// 同时也是调用中断处理程序前保存的现场
#[derive(Debug, Default, Clone, Copy)]
#[repr(C)]
pub struct TrapFrame {
pub regs: CallerRegs,
// Pushed by Vector[i]
pub id: usize,
pub err: usize,
// Pushed by CPU
pub rip: usize,
pub cs: usize,
pub rflags: usize,
pub rsp: usize,
pub ss: usize,
}
中断发生时,忽略上面第一段描述的CPU复杂处理过程,系统程序员关注的动作是:
- 切换栈(用户态中断时)
- ss、rsp、rflags、cs、rip五个寄存器压栈
- 根据中断号跳转到中断向量执行
中断向量
为了统一处理,我们在中断向量中将中断号和错误码压栈,然后统一跳转到汇编代码片段__trap_entry
中执行,256个中断向量中的内容如下:
.section .text
vector0:
push 0
push 0
jmp __trap_entry
vector1:
push 0
push 1
jmp __trap_entry
vector2:
push 0
push 2
jmp __trap_entry
...
中断入口
__trap_entry
代码片段如下:
.macro save
push r11
push r10
push r9
push r8
push rdi
push rsi
push rdx
push rcx
push rax
.endm
.macro restore
pop rax
pop rcx
pop rdx
pop rsi
pop rdi
pop r8
pop r9
pop r10
pop r11
.endm
.global __trap_entry
__trap_entry:
save
mov rdi, rsp
call trap_handler
mov rax, [rsp + 96] # 96 = offsetof(TrapFrame, cs)
and rax, 0x3
jz __from_kernel
__from_user:
lea rax, [rsp + 128] # prepare new TSS.sp0, 128 = sizeof(TrapFrame)
mov [TSS + rip + 4], rax
__from_kernel:
restore
add rsp, 16 # skip TrapFrame.err and id
iretq
save
和restore
宏分别保存和恢复调用者保存寄存器:
首先将调用者保存寄存器保存在栈(内核栈)上,这时栈顶从低地址到高地址正好构成一个TrapFrame
结构,将其指针(rsp)作为参数,调用trap_handler
函数,这就是内核中真正的中断处理函数。
中断返回时,从TrapFrame
中取出中断发生前的cs寄存器,其第0、1比特表示了当前CPU特权级,利用cs寄存器,如果是从用户态触发的中断,将内核栈恢复为中断发生前的地址,内核态中断原本没有切换栈,无须处理,接下来不管是哪种中断,都恢复调用者保存寄存器,使用ireq指令返回对应的特权级。
内核中断处理函数
#[no_mangle]
pub extern "C" fn trap_handler(f: &'static mut TrapFrame) {
match f.id {
...
TIMER => {
pic::ack();
*pic::TICKS.get() += 1;
task::current_yield();
}
_ => {
println!("trap {:x?}", f);
task::current().exit(-1);
}
}
}
中断处理函数根据中断号进行分别处理。