Rust开发首个OS:保存CPU状态及上下文切换遇页错误求助
x86_64 操作系统上下文切换的正确实现方法
你的代码核心问题分析
- 栈帧构造错误:手动push的寄存器/段值顺序完全不符合x86_64的栈帧规则,
call指令会自动将下一条指令的rip压栈,你额外push的0、0x8、0x10直接破坏了栈结构,导致后续ret执行时栈指针指向非法地址,触发页错误。 - 上下文结构体布局错误:
CPUStatus的字段顺序与x86_64栈帧/上下文保存顺序不匹配,且遗漏了rflags、rbp、r12-r15等关键寄存器——这些非易失性寄存器在上下文切换时必须保存,否则会破坏任务状态。 - 汇编指令逻辑错误:
save函数使用noreturn选项,但save_inner是有返回值的,ret指令执行时栈已被错误修改,直接导致执行流崩溃。
修正后的上下文切换实现
1. 正确的上下文结构体定义
必须遵循x86_64寄存器保存顺序,包含所有非易失性寄存器、栈指针、指令指针、段寄存器和标志寄存器:
use core::arch::asm; #[derive(Debug, Clone, Copy, Default)] #[repr(C)] // 字段顺序必须与汇编中push的顺序严格对应 pub struct CPUContext { // 非易失性寄存器(SysV调用约定要求保存) rbx: u64, rbp: u64, r12: u64, r13: u64, r14: u64, r15: u64, // 栈指针 rsp: u64, // 指令指针 rip: u64, // 代码段、栈段寄存器 cs: u64, ss: u64, // 标志寄存器 rflags: u64, }
2. 安全的上下文保存与切换函数
impl CPUContext { /// 保存当前CPU上下文 pub unsafe fn save() -> Self { let mut ctx: Self; asm!( // 按顺序保存非易失性寄存器到栈 "push rbx", "push rbp", "push r12", "push r13", "push r14", "push r15", // 获取当前栈指针(指向刚push的r15值) "mov {0}, rsp", // 通过call指令获取当前rip:call会将下一条指令地址压栈,随后弹出到rip字段 "call 1f", "1: pop {1}", // 读取cs、ss段寄存器 "mov {2}, cs", "mov {3}, ss", // 读取rflags寄存器 "pushfq", "pop {4}", // 恢复栈指针(移除刚才push的6个寄存器) "add rsp, 48", // 将寄存器值写入ctx结构体 out(reg) ctx.rsp, out(reg) ctx.rip, out(reg) ctx.cs, out(reg) ctx.ss, out(reg) ctx.rflags, lateout("rbx") ctx.rbx, lateout("rbp") ctx.rbp, lateout("r12") ctx.r12, lateout("r13") ctx.r13, lateout("r14") ctx.r14, lateout("r15") ctx.r15, options(nostack, preserves_flags) ); ctx } /// 从当前上下文切换到目标上下文 /// `from`:保存当前上下文的指针 /// `to`:要切换到的目标上下文指针 pub unsafe fn switch(from: &mut Self, to: &Self) { asm!( // 保存当前上下文的非易失性寄存器到栈 "push rbx", "push rbp", "push r12", "push r13", "push r14", "push r15", // 将当前栈指针保存到from->rsp "mov [{0}], rsp", // 加载目标上下文的非易失性寄存器 "mov rbx, [{1}]", "mov rbp, [{1}+8]", "mov r12, [{1}+16]", "mov r13, [{1}+24]", "mov r14, [{1}+32]", "mov r15, [{1}+40]", // 切换到目标栈指针 "mov rsp, [{1}+48]", // 恢复目标上下文的rflags "push [{1}+72]", "popfq", // 跳转到目标指令指针(用jmp避免栈污染) "jmp [{1}+56]", in(reg) from, in(reg) to, options(nostack) ); } }
关键实现要点
- 遵循调用约定:x86_64 SysV调用约定中,
rbx、rbp、r12-r15是非易失性寄存器,必须手动保存;易失性寄存器(rax、rcx等)无需保存,因为任务切换时这些寄存器的值会被覆盖。 - 利用CPU自动栈帧:如果是通过中断触发上下文切换,CPU会自动将
ss、rsp、rflags、cs、rip压入栈,这是最安全的上下文保存方式,你可以直接在中断处理函数中读取这些值构造上下文结构体。 - 独立栈空间:每个任务必须拥有独立的栈空间,切换上下文时必须更新
rsp到目标任务的栈指针,避免栈冲突或溢出。 - 避免手动构造栈帧:手动push段寄存器、rip等操作极易出错,优先利用CPU的自动栈帧机制或严格遵循栈帧顺序。
内容的提问来源于stack exchange,提问作者Gabu
相关产品推荐
相关产品推荐

