🧭 xv6 启动流程概览(整体脉络)
Power On → Bootloader → xv6 内核加载并开始执行
→ _entry.S → start.c → 切换到 S-mode → main.c → userinit()
→ 加载 initcode.S → 首次系统调用 exec → /init 运行
→ shell 启动 → 系统就绪
✅ 步骤详解(跟源码位置一起看)
① 开机后,Bootloader 从 ROM 加载 xv6
- 位于启动芯片固件(模拟器中 emulated)
- 加载 xv6 的 ELF 文件(内核映像)到物理地址
0x80000000
为什么不放在
0x0? 因为0x0到0x80000000用于内存映射 I/O 区间。
② 入口:_entry(汇编) — kernel/entry.S:7
.globl _entry
_entry:
# 设置内核栈 sp
la sp, stack0 + 4096
call start
- 设置栈顶(因为 RISC-V 栈向下增长)
- 跳转到 C 语言的
start(),位于kernel/start.c
③ start() 函数:kernel/start.c
void start() {
// 设定中断委托、关闭分页
...
// 设置 mepc = main, 等效于跳转到 C 的 main()
w_mepc((uint64)main);
...
mret(); // 从 M-mode “返回” 到 S-mode,开始执行 main()
}
做了这些事情:
- 配置特权级 mstatus,让它进入 Supervisor Mode (S-mode)
- 设置 返回地址
mepc,指向main()函数 - 禁用分页,设置
satp = 0 - 开启时钟中断(用于实现时间片)
然后用 mret 进入 S-mode,正式执行 main()
④ main() 启动内核组件:kernel/main.c
int main() {
consoleinit(); // 初始化终端设备
...
userinit(); // 启动第一个用户进程
scheduler(); // 启动调度器
}
这一步初始化了多个子系统:console、trap、memory、plicit、proc 等,最后调用 userinit()。
⑤ userinit() 创建第一个用户进程:kernel/proc.c:233
void userinit(void) {
p = allocproc(); // 分配进程结构
...
memmove(init, initcode, sizeof(initcode)); // 拷贝 initcode 到用户页
...
p->trapframe->epc = 0; // 设置入口为 initcode 的地址
...
p->state = RUNNABLE;
}
核心点:
- 进程结构
proc创建成功 - 把内嵌的
initcode.S写入用户内存 - 设置
epc为入口地址,让用户态从那里开始执行
⑥ initcode.S:第一个执行的用户程序(位于 user/initcode.S)
li a7, SYS_exec
la a0, init
ecall
- 设置系统调用号为
SYS_exec - 参数是
/init程序的路径 - 调用
ecall,进入内核
⑦ trap.c → syscall.c:处理中断并分发系统调用
void usertrap(void) {
syscall();
}
系统识别 scause == 8,说明是 ecall,就调用 syscall()。
void syscall(void) {
num = p->trapframe->a7;
arg0 = p->trapframe->a0;
...
syscalls[num]()
}
找到 sys_exec() 并调用。
⑧ sys_exec():加载并运行 /init
- 替换进程的整个地址空间
/init实际是 ELF 可执行文件(位于内核镜像中)- 创建新页表,加载代码段、数据段、栈
⑨ 运行 /init:user/init.c
open("/dev/console", ...);
dup(0); dup(0); // 设置 stdin, stdout, stderr
exec("sh", ...); // 启动 shell
- 首先确保控制台设备准备好
- 启动 shell
⑩ shell 启动成功,用户可以交互操作
$ ls
$ cat README
$ echo hi
至此,xv6 就“活”起来了。
🧠 图示结构简图
[Bootloader]
↓
[entry.S] → 设置栈 → 调用 start()
↓
[start.c] → 切换到 S-mode → 设置 mepc → mret
↓
[main.c] → 初始化设备、调用 userinit()
↓
[proc.c::userinit] → 创建第一个进程 → initcode
↓
[initcode.S] → 执行 ecall 调用 exec("/init")
↓
[kernel/syscall.c::sys_exec] → 加载 /init
↓
[init.c] → 打开 console → exec("sh")
↓
[shell 启动] → 系统就绪
📌 你可以查看的文件顺序建议
| 阶段 | 文件 | 说明 |
|---|---|---|
| 启动 | kernel/entry.S | 启动入口,设置栈指针 |
| 启动 | kernel/start.c | 切换到 S-mode |
| 内核主程序 | kernel/main.c | 初始化系统,启动用户进程 |
| 用户进程 | kernel/proc.c | userinit 创建第一个进程 |
| 初始用户代码 | user/initcode.S | 调用 exec(“/init”) |
| 内核系统调用 | kernel/syscall.c | 分发系统调用 |
| exec 实现 | kernel/exec.c | 加载并运行 ELF 程序 |
| init 程序 | user/init.c | 启动 shell |
| shell | user/sh.c | shell 的实现 |