Starting xv6 and the first process

_entry

Loader将xv6内核加载到物理地址0x80000000,然后CPU从kernel/entry.S开始执行代码。在kernel/start.c中为每一个CPU都定义了一个4096字节的栈,将栈顶的地址存入sp寄存器中,然后调用kernel/start.c中定义的start()函数。

# kernel/entry.S

# qemu -kernel loads the kernel at 0x80000000
# and causes each CPU to jump there.
# kernel.ld causes the following code to
# be placed at 0x80000000.
.section .text
_entry:
        # set up a stack for C.
        # stack0 is declared in start.c,
        # with a 4096-byte stack per CPU.
        # sp = stack0 + (hartid * 4096)
        la sp, stack0
        li a0, 1024*4
        csrr a1, mhartid
        addi a1, a1, 1
        mul a0, a0, a1
        add sp, sp, a0
        # jump to start() in start.c
        call start
spin:
        j spin

start

start()函数执行了一些只可以在machine态下执行的配置,然后转换到supervisor态。

machine态转换到supervisor态是通过mret指令实现的。mret指令的作用是在之前的函数A从supervisor态转换到machine态后,重新转回supervisor态继续执行函数A剩下的代码。而start函数之前并没有这么一个函数调用,所以通过修改mstatus寄存器的值将之前的状态设置为supervisor态,并且修改mepc寄存器的值为main函数的地址,设置页表寄存器satp的值为0使得在supervisor态下不可以进行虚拟地址转换。

kernel main

初始化设备和子系统,然后调用kernel/proc.c中的userinit()函数。

userinit

调用了user/initcode.S

initcode.S

通过调用exec函数执行user/init.c中的main()函数。

user main

打开标准输入、输出、错误流,并且通过fork()创建一个子进程,在子进程中调用exec()执行sh程序创建shell。