You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust开发首个OS:保存CPU状态及上下文切换遇页错误求助

x86_64 操作系统上下文切换的正确实现方法

你的代码核心问题分析

  1. 栈帧构造错误:手动push的寄存器/段值顺序完全不符合x86_64的栈帧规则,call指令会自动将下一条指令的rip压栈,你额外push的0、0x8、0x10直接破坏了栈结构,导致后续ret执行时栈指针指向非法地址,触发页错误。
  2. 上下文结构体布局错误:CPUStatus的字段顺序与x86_64栈帧/上下文保存顺序不匹配,且遗漏了rflags、rbp、r12-r15等关键寄存器——这些非易失性寄存器在上下文切换时必须保存,否则会破坏任务状态。
  3. 汇编指令逻辑错误: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)
        );
    }
}

关键实现要点

  1. 遵循调用约定:x86_64 SysV调用约定中,rbx、rbp、r12-r15是非易失性寄存器,必须手动保存;易失性寄存器(rax、rcx等)无需保存,因为任务切换时这些寄存器的值会被覆盖。
  2. 利用CPU自动栈帧:如果是通过中断触发上下文切换,CPU会自动将ss、rsp、rflags、cs、rip压入栈,这是最安全的上下文保存方式,你可以直接在中断处理函数中读取这些值构造上下文结构体。
  3. 独立栈空间:每个任务必须拥有独立的栈空间,切换上下文时必须更新rsp到目标任务的栈指针,避免栈冲突或溢出。
  4. 避免手动构造栈帧:手动push段寄存器、rip等操作极易出错,优先利用CPU的自动栈帧机制或严格遵循栈帧顺序。

内容的提问来源于stack exchange,提问作者Gabu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 01:45:08