2.6 Code: starting xv6, the first process and system call
运行 xv6 系统的 RISC-V 计算机启动的全过程:
- 电脑开机并初始化自身, 然后运行存储在某个只读内存中的一个称为启动加载器 (boot loader) 的程序.
 - 启动加载器将 xv6 内核加载至内存中的物理地址 
0x80000000(7 个零) 处, 然后 CPU 在机器模式 (machine mode) 下从_entry入口 (位于文件kernel/entry.S中) 开始运行 xv6.- RISC-V 机器一开始是没有启用页表功能的.
 - 之所以不将 xv6 加载至物理地址 
0x0处是因为从0x0开始到0x80000000为止的这段物理内存中存储着与 I/O 设备有关的信息. 
 _entry的作用是为 xv6 初始化一个用于执行 C 程序的栈. 为了运行 C 程序, xv6 需要使用一个栈, 为此 xv6 在文件start.c中已经定义了一个名为stack0的栈 (位于文件kernel/start.c中)._entry入口处的指令负责将堆栈指针寄存器sp设置为stack0 + (hartid * 4096)来为 xv6 初始化该栈. 在初始化好栈之后,_entry将调用start处的 C 代码 (位于文件kernel/start.c中).- 注: 在 RISC-V 下栈顶是向低地址方向增长的.
 
start处的 C 代码的主要任务是将 CPU 从特权等级最高的机器模式切换至管理员模式 (supervisor mode). RISC-V 提供了一个用于从机器模式返回管理员模式 (也有可能是其他模式, 具体由 MPP (Machine Previous Privilege) 寄存器字段指定) 的指令, 即mret, 但在当前情况下机器根本就没有从管理员模式进入到过机器模式, 因为机器本来就是以机器模式启动的.start处的代码负责的就是为mret指令建立一个与 "机器刚刚从管理员模式进入机器模式并即将返回" 等价的环境, 其所执行的操作包括将寄存器mstatus(用于指示上一个特权模式) 设置为管理员模式, 将 寄存器mepc设置为main函数 (位于文件kernel/main.c中) 的入口地址, 将寄存器satp的值设置为0以暂时禁用虚拟地址转换机制, 最后将所有中断和异常全部委托给管理员模式. 做好这些准备工作之后,start还将时钟芯片 (clock chip) 设置好以便产生计时器中断 (timer interrupt), 然后执行mret使 CPU "返回" 至管理员模式, 同时程序计数器也相应变为main函数的入口地址.main函数进行诸多初始化过程 (内容较多, 此处省略, 具体请查看代码), 然后调用userinit函数 (位于文件kernel/proc.c中).userinit函数将创建整个系统自开机以来的第一个进程 (通过调用allocproc函数), 这个进程将执行initcode数组中所包含的机器指令, 这些机器指令实际上就是文件user/initcode.S中的汇编代码所对应的机器指令.- 可以将 
userinit函数看作是 "没有父进程" 的fork函数, 因为userinit的作用正相当于创建子进程, 而该子进程负责调用exec系统调用, 加载/init程序并执行. 所不同的是在userinit中子进程的代码是直接复制于initcode数组中的, 毕竟此时根本就没有父进程. 
- 可以将 
 initcode.S的工作是调用exec("/init"), 具体操作为将字符串"/init"以及代表exec系统调用的整数代码SYS_EXEC(位于文件kernel/syscall.h中) 放置在寄存器中然后执行ecall指令.ecall指令将触发一次陷入, 经过蹦床 (trampoline) 代码的路由后最终将调用syscall函数 (位于文件kernel/syscall.c中).syscall函数使用系统调用表 (system call table)syscalls(位于文件kernel/syscall.c中) 将SYS_EXEC映射至sys_exec函数的入口地址.sys_exec函数即为exec系统调用的实际实现, 它将加载指定的目标程序 (此处即init) 替换当前进程的内存和寄存器. 当内核从exec返回后, 计算机将回到用户态并开始执行init程序.init程序 (位于文件user/init.c中) 将分配三个文件描述符, 即 stdin, stdout, 以及 stderr, 然后启动 shell. 至此整个系统便成功跑起来了.