Avatar OS Kernel — RISC-V 64 课前准备
本文档面向学习本课程的学员,聚焦 RISC-V 64(RV64IMAC,Sv48 分页,S-mode) 这一架构实现, 整理「课程内容速览」「寄存器与指令(含特权)」「速查手册」三部分,作为每一节课动手前的背景速查。
文中所有寄存器编号、位域、指令、内存布局均取自
kernel/真实实现 (boot/riscv64/、include/riscv64/、mm/riscv64/、kernel/task/riscv64/、driver/)与Makefile,可与课程正文对照。参考权威细节请查阅《The RISC-V Instruction Set Manual, Volume II: Privileged Architecture》。
0. 一句话背景
本内核运行在 QEMU virt 机器 + OpenSBI(-bios default) 之上:OpenSBI 跑在 M-mode, 把控制权以 S-mode 移交给我们的内核(_start)。内核自己开启 Sv48 分页、安装异常向量、 接管定时器与 PLIC 中断,并实现多任务、用户态、系统调用与多核(SMP)。
- DRAM 物理基址:
0x80000000 - 内核加载地址(OpenSBI 跳转入口):
0x80200000 - 内核高半区直接映射偏移:
KERNEL_VMA_OFF = 0xFFFFFFFF00000000(即VA = PA + 0xFFFFFFFF00000000) - 工具链:
riscv64-linux-musl-gcc;运行:qemu-system-riscv64
1. 课程内容速览
24 节课分 5 个阶段,4 个里程碑。下表标注每课在 RISC-V 64 上的关键实现文件,便于课前定位。
第一阶段:裸机基础(97–102)—— 最小内核骨架
| 课 | 标题 | RISC-V 64 关键文件 / 知识点 |
|---|---|---|
| 97 | Bare-Metal Hello | boot/riscv64/boot.S:直接写 UART 物理地址 0x10000000 输出 "Hello" |
| 98 | Freestanding Types | include/types.h:自定义 uint64_t、size_t、vaddr_t/paddr_t、NULL、ALIGN_UP;无标准库 |
| 99 | Kernel Logging | include/klog.h、lib/klog.c、lib/vsnprintf.c:KLOG_* 宏 → kprintf → UART |
| 100 | Linker Script | boot/riscv64/link.ld:双 VMA/LMA(KERNEL_LOAD_BASE=0x80200000,KERNEL_VMA_OFF) |
| 101 | Boot Assembly | boot/riscv64/boot.S:_start 关中断、设引导栈、建页表、开 MMU、trampoline 到高半区 |
| 102 | Arch Abstraction | include/arch.h(__riscv + __riscv_xlen==64 → ARCH_RISCV64)、include/barrier.h/riscv64/barrier_impl.h |
第二阶段:内存与同步(103–108)—— 虚拟内存完备(里程碑 M2 @108)
| 课 | 标题 | RISC-V 64 关键文件 / 知识点 |
|---|---|---|
| 103 | Memory Barriers | riscv64/barrier_impl.h:fence rw,rw / fence r,r / fence w,w |
| 104 | Spinlocks | riscv64/spinlock_impl.h:LR/SC(lr.w/sc.w)+ fence;中断开关用 csrrci sstatus,2 |
| 105 | Physical Memory | kernel/mm/pmm.c:物理页帧分配(里程碑 M1 基础设施) |
| 106 | Page Tables | riscv64/mmu.h:PTE 标志位(V/R/W/X/U/G/A/D)、make_satp、pte_table/pte_leaf |
| 107 | MMU Enable | mm/riscv64/mmu.S:csrw satp + sfence.vma;mm/riscv64/vm_early.c:2 级页表(恒等 + 高半区) |
| 108 | Kernel Address Space | include/mm_vm.h:virt_to_phys/phys_to_virt 直接映射;main.c 反推 kernel_main 的 PA/VA |
第三阶段:中断与定时器(109–112)—— 让内核有"心跳"
| 课 | 标题 | RISC-V 64 关键文件 / 知识点 |
|---|---|---|
| 109 | Exception Vectors | boot/riscv64/trap.S:trap_vector(.align 4,direct 模式写入 stvec=0x105) |
| 110 | Exception Handling | boot/riscv64/exception.c:handle_exception 解析 scause,分发 timer/plic/ecall/ebreak |
| 111 | Timer Driver | driver/timer/riscv64/timer.c:经 SBI legacy set_timer(EID=0) 编程 ACLINT;irq_install(5,...) |
| 112 | Interrupt Controllers | driver/irq/riscv64/plic.c:PLIC claim/complete(IRQ 10=UART),sie 置 SEIE(bit9) |
第四阶段:多任务(113–116)—— 抢占式并发(里程碑 M3 @116)
| 课 | 标题 | RISC-V 64 关键文件 / 知识点 |
|---|---|---|
| 113 | Task Control Block | kernel/task/task.h、task.c:任务结构体、就绪队列 |
| 114 | Context Switch | kernel/task/riscv64/switch.S:仅保存 callee-saved(ra + s0–s11,104 字节),csrs sstatus,0x2 开 SIE |
| 115 | Round-Robin Scheduler | kernel/task/sched.c:协作式 schedule(),显式 ebreak/yield 让出 |
| 116 | Preemptive Scheduling | kernel/task/preempt.c:定时器中断置 need_resched,返回路径 sched_check_and_yield() 延迟切换 |
第五阶段:用户态与多核(117–120)—— 完整 OS(里程碑 M4 @120)
| 课 | 标题 | RISC-V 64 关键文件 / 知识点 |
|---|---|---|
| 117 | User Mode | kernel/task/riscv64/user.S:arch_user_enter 经 sret 进 U-mode;sscratch 区分陷入来源 |
| 118 | System Calls | boot/riscv64/exception.c:cause==8(U-mode ecall)→ syscall_dispatch,a7=调用号,sepc+=4 |
| 119 | ELF Loader | kernel/loader/elf_loader.c + apps/riscv64/welcome.c:加载独立 ELF 到 U-mode 地址空间运行 |
| 120 | SMP Multicore | boot.S _secondary_start:BSP 经 SBI hart_start 唤醒次级核,复用页表 csrw satp+sfence.vma |
里程碑
- M1 (105) 最小内核基础设施:UART + 日志 + 栈 + 锁 + 物理内存
- M2 (108) 虚拟内存完备:页表 + MMU + 地址空间隔离
- M3 (116) 抢占式多任务:定时器驱动的上下文切换
- M4 (120) 完整操作系统:用户态 + 系统调用 + 多核
2. RISC-V 64 通用寄存器
RV64 有 32 个 64 位通用寄存器 x0–x31,约定(ABI 名)如下。内核全程使用 S-mode,调用约定遵循 RISC-V ELF psABI。
| 寄存器 | ABI 名 | 用途 | 是否易失(调用约定) |
|---|---|---|---|
| x0 | zero | 恒为 0(写无效) | — |
| x1 | ra | 返回地址 | 调用者保存 |
| x2 | sp | 栈指针 | 被调用者保存 |
| x3 | gp | 全局指针(本内核基本不用,靠 lla PC 相对寻址) | — |
| x4 | tp | 线程指针(SMP 中可放 per-hart 信息) | — |
| x5 | t0 | 临时寄存器 | 调用者保存 |
| x6 | t1 | 临时寄存器 | 调用者保存 |
| x7 | t2 | 临时寄存器 | 调用者保存 |
| x8 | s0/fp | 被保存寄存器 / 帧指针 | 被调用者保存 |
| x9 | s1 | 被保存寄存器 | 被调用者保存 |
| x10 | a0 | 函数参数 1 / 返回值 | 调用者保存 |
| x11 | a1 | 函数参数 2 / 返回值(64 位高/次) | 调用者保存 |
| x12–x17 | a2–a7 | 函数参数 3–8 | 调用者保存 |
| x18–x27 | s2–s11 | 被保存寄存器 | 被调用者保存 |
| x28–x31 | t3–t6 | 临时寄存器 | 调用者保存 |
关键点(与内核代码强相关):
- 参数/返回值:C 函数前 8 个参数走
a0–a7(x10–x17);系统调用号走a7(x17);返回值走a0。 - callee-saved 何处保存:
trap.S保存全部x1–x31(跳过x0);switch.S只保存ra + s0–s11(13 个,104 字节),因为a/t由编译器在调用点自己处理。 gp不用:本工具链下全局符号走GOT在-kernel加载时不稳定,故引导期一律用lla(PC 相对,auipc+addi) 取地址;高 VMA 符号靠链接脚本提供的*_pa别名。fp(s0) 在这里只是普通被保存寄存器,没有开启栈回溯帧指针。
3. RISC-V 64 通用指令速查
下面列出本内核及裸机开发最常用指令(RV64I + 原子扩展 + 系统扩展)。
3.1 寄存器搬运 / 算术 / 逻辑(imm 版 = i 后缀)
| 指令 | 含义 |
|---|---|
mv rd, rs | 伪指令,等价 addi rd, rs, 0 |
li rd, imm | 加载 32/64 位常量(自动拆 lui+addi) |
la rd, sym | 加载符号地址(GOT,引导期慎用) |
lla rd, sym | PC 相对加载符号地址(auipc+addi),引导期首选 |
add/sub rd,rs,rt / addi rd,rs,imm | 加 / 减 |
sll/srl/sra / slli/srli/srai | 逻辑/算术移位 |
and/or/xor / andi/ori/xori | 位运算 |
slt/sltu / slti/sltiu | 有/无符号比较置 1 |
lui rd, imm | 加载到高 20 位(rd = imm<<12) |
auipc rd, imm | rd = pc + (imm<<12),PC 相对基址 |
3.2 访存(load/store,均按 imm(rs) 寻址)
| 指令 | 含义 |
|---|---|
lb/lbu lh/lhu lw/lwu ld | 加载 8/16/32/64 位(有/无符号) |
sb sh sw sd | 存 8/16/32/64 位 |
例:sd x1, 1*8(sp) | 把 x1 存到 sp + 8 |
3.3 控制流
| 指令 | 含义 |
|---|---|
jal rd, offset / jalr rd, rs, imm | 跳转并链接(jal 用于 call/la 等;jalr 用于 ret/jr) |
call sym / ret / jr rd | 伪指令(基于 jal/jalr ra) |
beq/bne blt/bltu bge/bgeu | 分支(相等/不等/有符号小于/…) |
bnez t0, 1f / bgeu t0,t1,2f | 1f/2f 表示前方标号(页内) |
3.4 原子指令(自旋锁核心,见 104 课)
| 指令 | 含义 |
|---|---|
lr.w rd, (rs) | Load-Reserved:读 32 位并"预约"该地址 |
sc.w rd, rs, (rt) | Store-Conditional:若期间无人改过则写,成功 rd=0,否则 rd≠0 |
amoswap.w/sw.aqrl 等 | 若后续扩展到更重锁会用到(本内核只用 LR/SC) |
4. RISC-V 64 特权级与特权资源
4.1 特权级(Privilege Modes)
| 级别 | 名 | 本内核中的角色 |
|---|---|---|
| 0 | U-mode | 用户态程序(117+,ELF loader 加载的应用) |
| 1 | S-mode | 内核运行于此(OpenSBI 移交后) |
| 3 | M-mode | OpenSBI 固件(我们不直接写,仅用 SBI 调用) |
- 进入内核前:
csrw sie, zero/csrw sip, zero关中断,避免无向量表时收中断崩溃。 - 切换到内核态或用户态的"开关"靠
sstatus的SPP位 +sret完成(见 117)。
4.2 特权寄存器(CSR)—— 本内核用到的全部
CSR 地址为 12 位;读写用 CSR 指令(见 4.3)。下表是课程中实际出现的 CSR:
| CSR | 地址 | 全称 | 在内核中的用途 |
|---|---|---|---|
sstatus | 0x100 | Supervisor Status | 全局中断开关 SIE(bit1)、返回态 SPP(bit8)、SPIE(bit5)、SUM(bit18) |
satp | 0x180 | Supervisor Address Translation & Protection | 页表根 + 分页模式(MODE=9 → Sv48) |
sie | 0x104 | Supervisor Interrupt Enable | 使能 STIE(bit5)/SEIE(bit9)/SSIE(bit1) |
sip | 0x144 | Supervisor Interrupt Pending | 中断挂起位(清 SSIP 用 csrci sip,0x2) |
stvec | 0x105 | Supervisor Trap Vector | 异常入口地址(.align 4 → direct 模式) |
sepc | 0x141 | Supervisor Exception PC | 陷入前 PC;ecall/ebreak 后需 sepc += 4 跳过 |
scause | 0x142 | Supervisor Cause | 陷入原因(最高位=中断;低 8 位=异常/中断号) |
stval | 0x143 | Supervisor Trap Value | 附加信息(如 page fault 的故障地址) |
sscratch | 0x140 | Supervisor Scratch | 用户态陷入时存内核陷阱栈顶;0 表示当前在内核态 |
time | 0xC01(U/S 可读) | 定时器计数器 | 读当前时间,配合 SBI set_timer 设定下次中断 |
cycle/instret | 0xC00/0xC02 | 周期 / 指令数 | 可选性能计数(本内核未强依赖) |
注:本工具链(riscv64-linux-musl-gcc)中
stvec、satp、sie、sip、sepc、scause、stval、sscratch等助记符可直接用,无需写裸地址。
4.3 特权指令
| 指令 | 含义 | 本内核出现处 |
|---|---|---|
ecall | 触发环境调用(陷入 S/M,取决于当前特权级) | 用户态 syscall;SBI 调用(sbi_set_timer) |
sret | 从 S-mode 陷阱返回(PC←sepc,特权级←sstatus.SPP) | trap.S 返回;user.S 进入用户态 |
sfence.vma | 刷新 TLB(地址/ASID 可选) | 开 MMU 后、切页表后 |
wfi | Wait For Interrupt,进入低功耗直到中断 | 内核 idle 循环、hang 兜底 |
fence | 内存屏障(前序访存对后续可见) | 自旋锁、barrier 实现 |
csrr rd, csr | 读 CSR 到寄存器 | csrr t0, sepc 等 |
csrw csr, rs | 写寄存器到 CSR | csrw stvec, t0、csrw satp, a0 |
csrs csr, rs | 置位 CSR 指定位 | csrs sstatus, 0x2(开 SIE)、csrs sie, t0 |
csrc csr, rs | 清位 CSR 指定位 | csrc sstatus, t0(清 SPP) |
csrrw rd, csr, rs | 读改写(原子交换) | trap.S 用 csrrw sp, sscratch, sp 切栈 |
csrrs/csrrc rd, csr, rs | 读并置位/清位 | csrrci sstatus,2(读并清 SIE) |
csrci/csrsi csr, imm | 立即数清/置位(仅 5 位立即数) | csrci sip, 0x2(清 SSIP) |
陷阱:
csrsi/csrci立即数仅 5 位宽。开STIE(bit5) 与SEIE(bit9) 已超过范围,故代码先li t0, (1<<5)再csrs sie, t0。
4.4 sstatus 关键位域(最重要)
| 位 | 名 | 含义 |
|---|---|---|
| 1 | SIE | 全局中断使能(S 态)。0 = 关中断 |
| 5 | SPIE | 陷入前的中断使能快照;sret 时恢复为 SIE |
| 8 | SPP | 陷入前特权级:1=S-mode,0=U-mode;sret 据此决定返回到哪一级 |
| 18 | SUM | 1 = S-mode 可执行/读写用户页(write 系统调用读用户缓冲区需要) |
- 进入用户态前:
csrc sstatus, (1<<8)(清 SPP→0,回 U-mode)、csrs sstatus, (1<<5)|(1<<18)(开 SPIE、开 SUM)。 - 首次切入任务前:
csrs sstatus, 0x2(开 SIE),否则任务以关中断运行,抢占永不触发(switch.S)。
4.5 scause 编码(异常/中断号)
scause 最高位(bit63)=1 表示中断,否则为同步异常。低 8 位为编号。本内核关心的:
| 值 | 类型 | 名 | 内核处理 |
|---|---|---|---|
| 1 | 中断 | Supervisor Software Interrupt(IPI,核间) | 清 sip SSIP(csrci sip,0x2) |
| 5 | 中断 | Supervisor Timer Interrupt | timer_handler(递减时间片) |
| 9 | 中断 | Supervisor External(PLIC) | plic_external_irq |
| 3 | 异常 | Breakpoint(ebreak) | 若 a7==TASK_YIELD_MAGIC 则让出;否则死停 |
| 8 | 异常 | Environment call from U-mode(ecall) | 系统调用分发,sepc+=4 |
| 13 | 异常 | Load page fault | 打印 fault 地址 |
| 15 | 异常 | Store/AMO page fault | 打印 fault 地址(main.c 主动触发做演示) |
中断号提取:
irq = cause & 0xFF;区分中断/异常:cause & (1ULL<<63)。
4.6 satp 分页模式(RV64)
| MODE | 值(<<60) | 含义 |
|---|---|---|
| Bare | 0 | 无转换(物理=虚拟) |
| Sv39 | 8 | 3 级页表,39 位 VA |
| Sv48 | 9 | 4 级页表,48 位 VA(本内核采用) |
- 本内核用 2 级简化映射(root L0 + 1 GiB gigapage L1),但
satp仍按 Sv48 模式(make_satp置 MODE=9)。 - PTE 布局(
include/riscv64/mmu.h):V(b0) R(b1) W(b2) X(b3) U(b4) G(b5) A(b6) D(b7);PPN 占 bits[53:10]。 - 早期页表映射(
vm_early.c):- 恒等:
VA[0,1G)→PA[0,1G)、VA[2G,3G)→PA[2G,3G)(覆盖 UART0x10000000与内核0x80000000) - 高半区:
VA[0xFFFFFFFF80000000, +1G) → PA[0x80000000, +1G) - 只读测试区:
VA[1G,2G)映射为仅 R(触发 store page fault 演示)
- 恒等:
4.7 SBI(Supervisor Binary Interface)
内核通过 ecall(在 S-mode 即陷入 M-mode 的 OpenSBI)调用固件服务。本内核用到:
| 调用 | EID(a7) | FID(a6) | 用途 |
|---|---|---|---|
set_timer | 0 | 0 | 设定下一次定时器中断(必须用 legacy EID=0,v0.2 TIME 扩展在本环境不转发中断) |
hart_start | 0x48534D("HSM") | 0 | 唤醒次级核(120 课 SMP) |
5. 内核中的关键"帧"布局(务必记住)
这两块布局是后续 110/114/117 的调试基础。
5.1 Trap 帧 trap_frame_t(288 字节,exception.h)
trap.S 在陷入时 addi sp, sp, -288,按如下顺序保存(每个 8 字节):
offset 内容
0..255 x1, x2, x3, ..., x31 (32 个通用寄存器,x0 不存)
256 sepc
264 scause
272 stval
280 sstatus
───────── 共 288 字节 ─────────x2(sp) 特殊处理:来自用户态时原用户 sp 在sscratch;来自内核态时 =sp+288。- C 侧
handle_exception(frame_ptr)直接按此结构读取/改写寄存器(如f->x[10]=ret设系统调用返回值,f->sepc+=4跳过ecall)。
5.2 上下文切换帧(104 字节,switch.S)
arch_task_switch 仅保存 callee-saved:ra, s0–s11(13×8=104 字节)。每个任务 task->sp 指向自己内核栈上的这帧。帧布局:[ra][s0][s1]…[s11]。
6. 速查手册(Cheat Sheet)
6.1 CSR 地址速查
sstatus 0x100 satp 0x180 stvec 0x105 sepc 0x141
sie 0x104 sip 0x144 scause 0x142 stval 0x143
sscratch 0x140 time 0xC016.2 sstatus 位(1<<n)
SIE=1 SPIE=5 SPP=8 SUM=186.3 sie/sip 中断使能/挂起位
SSIE=1 (IPI) STIE=5 (Timer) SEIE=9 (PLIC)6.4 scause 速记
中断(bit63=1): 1=IPI 5=Timer 9=PLIC
异常(bit63=0): 3=ebreak 8=ecall(U) 13=load pf 15=store pf6.5 常用立即数 / 掩码
关闭 SIE: csrrci sstatus, 2 ; csrw sstatus, 0 也行(但要先存)
开启 SIE: csrs sstatus, 0x2
开启 STIE: li t0,(1<<5); csrs sie, t0
开启 SEIE: li t0,(1<<9); csrs sie, t0
清 SSIP(IPI): csrci sip, 0x2
satp Sv48: (9ULL << 60) | (root_ppn)
页表映射偏移: KERNEL_VMA_OFF = 0xFFFFFFFF00000000
UART 物理地址: 0x10000000
内核加载地址: 0x802000006.6 指令模式易错点
| 想做 | 正确 | 错误 |
|---|---|---|
| PC 相对取符号(引导期/MMU 关) | lla t0, sym | la t0, sym(走 GOT,不稳定) |
| 开 STIE/SEIE | li t0,(1<<5); csrs sie,t0 | csrsi sie, 0x20(立即数超 5 位) |
| 刷新 TLB(全) | sfence.vma zero, zero | 仅 sfence.vma(省略操作数依赖汇编器) |
stvec direct 模式 | .align 4 后 csrw stvec, addr | .align 2(会落入 vectored 模式被 WARL 改写) |
6.7 构建与运行命令
课程使用 Makefile 构建与运行内核,需自己实现。
# 在仓库根目录;ARCH 由根 ARCH 文件决定(当前 riscv64),也可用 make ARCH= 覆盖
make -C kernel clean
make -C kernel build # 生成 kernel/build/kernel.elf
make -C kernel qemu # QEMU 运行并输出串口(含 -bios default)
# 评测
bash grading/grade.sh -l 107 -v # 单课,显示串口完整输出与匹配行
bash grading/grade.sh -l 97,98,99 # 多课
# 调试(GDB 远程)
make -C kernel qemu-gdb # 加 -s -S,另起 gdb 连 localhost:1234工具链与 QEMU:
| 项 | 值 |
|---|---|
| 交叉编译器 | riscv64-linux-musl-gcc |
| QEMU | qemu-system-riscv64 -machine virt -m 128M -nographic -bios default |
| 加载地址 | 0x80200000 |
| 编译选项 | -ffreestanding -nostdlib -nostdinc(无标准库/头) |
7. 课前自测(Checklist)
开始写代码前,请确认你能在脑中/纸上回答:
_start为什么在 MMU 关闭时要用lla而不是la取boot_stack_top?- 双 VMA/LMA 设计里,
KERNEL_VMA_OFF=0xFFFFFFFF00000000解决了什么问题? trap_vector为什么必须.align 4?sscratch为 0 和不为 0 分别意味着什么?trap.S开头那段csrrw sp,sscratch,sp在做什么?switch.S只保存ra + s0–s11,为什么不保存a0–a7/t0–t6?- 用户态
ecall后为什么sepc += 4?ebreak当yield用时要靠哪个寄存器区分? - 开
STIE(bit5) 用csrsi sie, 0x20会怎样?正确写法是什么? satp的 MODE 位写 9 代表什么?写 0 呢?- 定时器为什么必须用 legacy SBI
set_timer(EID=0) 而非 v0.2 TIME 扩展? - SMP 次级核醒来后,为什么可以直接复用 BSP 已经建好的
satp(页表)?
8. 参考资料(外部链接)
权威规范与实用资料,课前/调试时按需查阅:
8.1 官方规范(第一手,最准确)
- RISC-V Instruction Set Manual(合订本,含 Unprivileged + Privileged)https://github.com/riscv/riscv-isa-manual
- 特权架构(卷 II)覆盖本文档所有 CSR(
sstatus/satp/stvec/sepc/scause…)、ecall/sret/sfence.vma等特权指令与scause编码表。
- 特权架构(卷 II)覆盖本文档所有 CSR(
- RISC-V Unprivileged ISA Spec(指令集、寄存器、LR/SC、fence)https://riscv.github.io/riscv-isa-manual/snapshot/spec/#vol:unpriv (最新版 PDF 与草稿)
- RISC-V Privileged ISA Spec(Sv39/Sv48 分页、
satpMODE、PLIC/中断)https://riscv.github.io/riscv-isa-manual/snapshot/spec/#vol:priv
8.2 ABI / 调用约定
- RISC-V ELF psABI(寄存器 ABI 名、
a0–a7参数传递、callee-saved 约定)https://github.com/riscv-non-isa/riscv-elf-psabi-doc - RISC-V Assembly Programmer's Manual(汇编语法、
la/lla/call/ret伪指令)https://github.com/riscv-non-isa/riscv-asm-manual
8.3 平台 / 固件 / 设备
- OpenSBI(M-mode 固件,
-bios default,SBIset_timer/hart_start调用约定)https://github.com/riscv-software-src/opensbi - RISC-V PLIC Specification(
plic.c的 claim/complete、优先级/使能寄存器布局)https://github.com/riscv/riscv-plic-spec - QEMU RISC-V
virt机器(-machine virt、DRAM 基址0x80000000、UART0x10000000、PLIC 基址0xC000000等内存映射)https://www.qemu.org/docs/master/system/riscv/virt.html
提示:课程每课的
README.md「参考资料」小节会给出与该课最相关的手册章节与源码文件,建议配合本速查手册一起使用。
文档随课程演进维护;寄存器/指令细节以 RISC-V 官方手册与本仓库
kernel/实现为准。