RISC-V 通電以后,會運(yùn)行保存在ROM中的引導(dǎo)程序(BootLoader),引導(dǎo)程序?qū)v6內(nèi)核加載到物理地址為0x80000000。
然后在機(jī)器模式下,CPU從kernel/entry.S開始運(yùn)行xv6 _entry。
# 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
_entry函數(shù),主要用于開辟??臻g,以便后續(xù)C代碼運(yùn)行
每個CPU都有自己的棧,把棧地址存入sp寄存器,接著_entry跳轉(zhuǎn)到start執(zhí)行C代碼。
start.c文件中運(yùn)行start()函數(shù)
RISCV提供了mret指令,這個指令一般是從前一個Supervisor模式轉(zhuǎn)換到Machine模式的
調(diào)用返回。但是start并沒有從這樣的調(diào)用返回。
所以,start函數(shù)假裝自己從這樣的調(diào)用返回。
在mstatus寄存器設(shè)置運(yùn)行模式為Supervisor,以便進(jìn)入內(nèi)核
mepc寄存器設(shè)置為main函數(shù)地址,將返回地址設(shè)為main
satp寄存器寫入0,禁用虛擬地址轉(zhuǎn)換。
最后通過mret指令進(jìn)入main函數(shù)
void
start()
{
// set M Previous Privilege mode to Supervisor, for mret.
unsigned long x = r_mstatus();
x &= ~MSTATUS_MPP_MASK;
x |= MSTATUS_MPP_S;
w_mstatus(x);
// set M Exception Program Counter to main, for mret.
// requires gcc -mcmodel=medany
w_mepc((uint64)main);
// disable paging for now.
w_satp(0);
// delegate all interrupts and exceptions to supervisor mode.
w_medeleg(0xffff);
w_mideleg(0xffff);
w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);
// ask for clock interrupts.
timerinit();
// keep each CPU's hartid in its tp register, for cpuid().
int id = r_mhartid();
w_tp(id);
// switch to supervisor mode and jump to main().
asm volatile("mret");
}
在main函數(shù)中,初始化設(shè)備和子系統(tǒng)后,通過調(diào)用userinit創(chuàng)建第一個進(jìn)程
第一個進(jìn)程執(zhí)行initcode.S程序
# Initial process that execs /init.
# This code runs in user space.
#include "syscall.h"
# exec(init, argv)
.globl start
start:
la a0, init
la a1, argv
li a7, SYS_exec
ecall
# for(;;) exit();
exit:
li a7, SYS_exit
ecall
jal exit
# char init[] = "/init\0";
init:
.string "/init\0"
# char *argv[] = { init, 0 };
.p2align 2
argv:
.long init
.long 0
通過exec系統(tǒng)調(diào)用,重新進(jìn)入內(nèi)核。exec系統(tǒng)調(diào)用用一個新程序init替換當(dāng)前進(jìn)程的內(nèi)存和寄存器。
一旦內(nèi)核完成了exec,它就返回到init的用戶空間。init程序?qū)?chuàng)建一個新的控制臺設(shè)備文件,然后以文件描述符0、1和2打開它。然后在控制臺上啟動一個shell。這樣系統(tǒng)就啟動了。
使用vscode調(diào)試xv6
在程序的入口_entry設(shè)置一個斷點
xv6從entry.S開始啟動,運(yùn)行在Machine模式下,運(yùn)行到start.c后切換到supervisor模式,然后運(yùn)行main.c
在main函數(shù)中,做了設(shè)備和子系統(tǒng)的初始化。
然后通過userinit運(yùn)行第一個進(jìn)程,調(diào)用exec運(yùn)行第一個程序
# exec(init, argv)
.globl start
start:
la a0, init
la a1, argv
li a7, SYS_exec
ecall
將init內(nèi)容的指針加載到a0,argv參數(shù)的地址加載到a1,exec系統(tǒng)調(diào)用對應(yīng)的數(shù)字加載到a7,最后調(diào)用ecall
在syscall設(shè)置斷點
num = p->trapframe->a7,會讀取系統(tǒng)調(diào)用號
sys_exec系統(tǒng)調(diào)用,會從用戶空間讀取參數(shù),會讀取path,也就是要執(zhí)行程序的文件名。
init會為用戶空間設(shè)置好console,調(diào)用fork,并在fork出的子進(jìn)程中執(zhí)行shell。這樣OS就運(yùn)行起來了。