wyp-tg-rcore-tutorial-ch1-multicore 0.1.5

Chapter 1 Multicore Extension: RISC-V multi-core startup demonstration with HART identification and WFI parking mechanism
wyp-tg-rcore-tutorial-ch1-multicore-0.1.5 is not a library.

todo

  • 整理文档
  • 添加自旋锁练习
  • 添加测试脚本

第一章扩展:多核启动演示

Crates.io License Rust Platform

本项目在第一章(ch1)的基础上扩展多核支持,演示 RISC-V 多核启动和核间协调的基本原理。

🎯 项目特点

  • 📚 教育导向:配套完整的学习文档和调试记录
  • 🔬 自包含实现:包含内建 M-mode SBI,无需外部依赖
  • ⚡ 零开销:直接裸机启动,最小运行时
  • 🛠️ 易于调试:详细的文档和常见问题解答

学习目标

通过本章的学习和实践,你将理解:

  • HART(Hardware Thread):RISC-V 对"处理器核"的称呼
  • mhartid CSR:每个核独有的 ID 寄存器
  • 多核启动流程:Boot HART 与从核的启动时序
  • WFI 指令:让从核进入低功耗停放状态

前置知识:建议先完成第一章(ch1)的学习,理解裸机启动、SBI 调用等基础概念。

项目结构

wyp-tg-rcore-tutorial-ch1-multicore/
├── .cargo/
│   └── config.toml     # Cargo 配置:支持多核运行
├── .gitignore
├── doc
    └── llvm
    └── spec
    └── somemd
├── build.rs            # 构建脚本:生成链接脚本
├── Cargo.toml
├── README.md
├── run.sh              # 运行脚本
├── rust-toolchain.toml
└── src/
    └── main.rs         # 内核源码:多核启动演示

编译与运行

编译

cargo build --release

运行

方式一:使用脚本(推荐)

# 默认使用 2 核
./run.sh

# 指定核数
./run.sh 4

方式二:手动运行

# 单核模式
qemu-system-riscv64 -machine virt -nographic -bios none -smp 1 \
    -kernel target/riscv64gc-unknown-none-elf/release/wyp-tg-rcore-tutorial-ch1-multicore

# 双核模式
qemu-system-riscv64 -machine virt -nographic -bios none -smp 2 \
    -kernel target/riscv64gc-unknown-none-elf/release/wyp-tg-rcore-tutorial-ch1-multicore

# 四核模式
qemu-system-riscv64 -machine virt -nographic -bios none -smp 4 \
    -kernel target/riscv64gc-unknown-none-elf/release/wyp-tg-rcore-tutorial-ch1-multicore

预期输出

单核模式(-smp 1)

[HART 0] Hello from core 0
[HART 0] I am the boot HART, shutting down...

双核模式(-smp 2)

[HART 0] Hello from core 0
[HART 1] Hello from core 1
[HART 0] I am the boot HART, shutting down...
[HART 1] Parking (entering WFI loop)...

注意:多核输出可能交错,这是正常现象。

核心概念

1. 核 ID 传递机制

重要发现mhartid 是 M-mode 专用 CSR,S-mode 无权直接读取!

因此我们采用 M-mode 传递核 ID 到 S-mode 的方式:

# M-mode 入口 (_m_start)
csrr t0, mhartid      # 读取核 ID
mv   a0, t0           # 通过 a0 寄存器传递给 S-mode
mret                  # 切换到 S-mode

# S-mode 入口 (_start)
mv   s0, a0           # 保存 M-mode 传递的核 ID
# ... 设置栈 ...
mv   a0, s0           # 传递给 rust_main
j    rust_main

Rust 接收参数

extern "C" fn rust_main(hart_id: usize) -> ! {
    // 直接使用传入的 hart_id
    println!("[HART {}] Hello from core {}", hart_id, hart_id);
}

为什么这样做?

  • mhartid CSR 地址为 0xF14,属于 M-mode 控制状态寄存器
  • S-mode 尝试读取会触发非法指令异常
  • 使用 a0 寄存器传递参数符合 RISC-V 调用约定

2. 多核启动流程

┌─────────────────────────────────────────────────────────────┐
│  Step 1: 上电                                                │
│          只有 Boot HART (通常是 HART 0) 先运行 OpenSBI       │
├─────────────────────────────────────────────────────────────┤
│  Step 2: OpenSBI 初始化                                      │
│          初始化硬件,唤醒其他核                               │
├─────────────────────────────────────────────────────────────┤
│  Step 3: 跳转到内核                                          │
│          所有核跳转到 _start (0x80200000)                    │
├─────────────────────────────────────────────────────────────┤
│  Step 4: 读取 mhartid                                        │
│          每个核读取自己的 ID,区分身份                        │
├─────────────────────────────────────────────────────────────┤
│  Step 5: 分支执行                                            │
│          主核继续执行,从核进入停放状态                       │
└─────────────────────────────────────────────────────────────┘

3. WFI 指令

WFI(Wait For Interrupt)让处理器进入低功耗等待状态:

fn park_hart() -> ! {
    loop {
        unsafe {
            core::arch::asm!("wfi", options(nostack));
        }
    }
}

WFI 的特点

  • 不会阻塞程序执行,只是"建议"处理器暂停
  • 有中断时,处理器会退出低功耗状态
  • 使用循环确保持续处于等待状态

关键代码解析

1. 独立栈空间

每个核需要独立的栈空间,避免数据冲突:

// 在 _start 中:
// 栈顶 = STACK 基地址 + STACK_SIZE * (hart_id + 1)
// 每个核使用独立的 4KB 栈空间

2. 核角色判断

if hart_id == BOOT_HART_ID {
    // 主核:执行正常流程
    shutdown(false);
} else {
    // 从核:进入停放状态
    park_hart();
}

思考题

  1. 为什么每个核需要独立的栈空间? 如果共享栈会发生什么?
  2. 输出交错是 bug 吗? 如何解决输出交错问题?
  3. 如果想让从核后续执行任务,需要什么机制?

📚 完整文档

本项目提供完整的学习文档,帮助你深入理解多核编程:

核心文档

快速导航

文档 目标读者 预计时间
开发手册 想了解实现细节 30 分钟
Bug 分析 遇到问题需要参考 20 分钟
检查点问答 巩固概念理解 15 分钟
学习心得 初学者入门指导 25 分钟
同步机制 进阶学习同步原理 40 分钟

关键 Bug 分析

在 S-mode 直接读取 mhartid 导致程序卡死,因为:

  1. mhartid 是 M-mode 专用 CSR
  2. S-mode 无权访问,触发异常
  3. 解决方案:M-mode 读取后通过 a0 寄存器传递给 S-mode

详见:调试记录Bug 分析报告

参考资料

下一步

本 crate 是多核扩展的第一阶段,下一阶段将实现:

  • ch2 多核扩展:主核执行批处理应用,从核停放
  • 核间中断(IPI):唤醒从核的机制
  • 同步机制:自旋锁、原子操作等

Dependencies

本项目是自包含的,不需要外部依赖:

  • ✅ 内建 M-mode SBI 实现(src/sbi/
  • ✅ 直接硬件访问(UART、CLINT)
  • ✅ 无运行时依赖,最小化复杂度

适用人群

  • 🎓 正在学习操作系统课程的学生
  • 🔧 希望了解多核编程的开发者
  • 📖 对 RISC-V 架构感兴趣的爱好者
  • 🏫 教授操作系统课程的教师

参考项目

本项目基于以下项目开发:

贡献

欢迎提交 Issue 和 Pull Request!

License

Licensed under GNU General Public License v3.0 or later (LICENSE).

致谢

本项目基于 rCore-Tutorial 开发,感谢 rCore 团队的开源贡献。