2.6 代码(启动XV6和第一个进程)
为了使xv6更加具体,我们将概述内核如何启动和运行第一个进程。接下来的章节将更详细地描述本概述中显示的机制。
当RISC-V计算机上电时,它会初始化自己并运行一个存储在只读内存中的引导加载程序。引导加载程序将xv6内核加载到内存中。然后,在机器模式下,中央处理器从_entry
(kernel/entry.S:6)开始运行xv6。Xv6启动时页式硬件(paging hardware)处于禁用模式:也就是说虚拟地址将直接映射到物理地址。
加载程序将xv6内核加载到物理地址为0x80000000
的内存中。它将内核放在0x80000000
而不是0x0
的原因是地址范围0x0:0x80000000
包含I/O设备。
_entry
的指令设置了一个栈区,这样xv6就可以运行C代码。Xv6在start. c (kernel/start.c:11)文件中为初始栈stack0声明了空间。由于RISC-V上的栈是向下扩展的,所以_entry
的代码将栈顶地址stack0+4096
加载到栈顶指针寄存器sp
中。现在内核有了栈区,_entry
便调用C代码start
(kernel/start.c:21)。
函数start
执行一些仅在机器模式下允许的配置,然后切换到管理模式。RISC-V提供指令mret
以进入管理模式,该指令最常用于将管理模式切换到机器模式的调用中返回。而start
并非从这样的调用返回,而是执行以下操作:它在寄存器mstatus
中将先前的运行模式改为管理模式,它通过将main
函数的地址写入寄存器mepc
将返回地址设为main
,它通过向页表寄存器satp
写入0来在管理模式下禁用虚拟地址转换,并将所有的中断和异常委托给管理模式。
在进入管理模式之前,start
还要执行另一项任务:对时钟芯片进行编程以产生计时器中断。清理完这些“家务”后,start
通过调用mret
“返回”到管理模式。这将导致程序计数器(PC)的值更改为main
(kernel/main.c:11)函数地址。
[!TIP|label:TIPS] 注:
mret
执行返回,返回到先前状态,由于start
函数将前模式改为了管理模式且返回地址改为了main
,因此mret
将返回到main
函数,并以管理模式运行
在main
(kernel/main.c:11)初始化几个设备和子系统后,便通过调用userinit
(kernel/proc.c:212)创建第一个进程,第一个进程执行一个用RISC-V程序集写的小型程序:initcode. S (user/initcode.S:1),它通过调用exec
系统调用重新进入内核。正如我们在第1章中看到的,exec
用一个新程序(本例中为 /init
)替换当前进程的内存和寄存器。一旦内核完成exec
,它就返回/init
进程中的用户空间。如果需要,init
(user/init.c:15)将创建一个新的控制台设备文件,然后以文件描述符0、1和2打开它。然后它在控制台上启动一个shell。系统就这样启动了。