🧭 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? 因为 0x00x80000000 用于内存映射 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.csyscall.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 可执行文件(位于内核镜像中)
  • 创建新页表,加载代码段、数据段、栈

⑨ 运行 /inituser/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 的实现