關於RISC-V OS開發 1 OS啟動,開始起飛
///插播一條:我自己在今年年初錄製了一套還比較系統的入門單片機教程,想要的同學找我拿就行了免費的,私信我就可以哦~點我頭像黑色字體加我地球呺也能領取哦。最近比較閒,帶做畢設,帶學生參加省級或以上比賽///
QEMU模擬的是整個SoC,我們需要驗證並調試OS,外設是必不可少的。QEMU會把bootloader映射到物理地址空間的0x1000-0xf000的這段ROM中,把RAM映射到0x8000000處。剩下主要是各種外設和支持QEMU自身。
xv6的結構:
xv6是宏內核,kernel下的文件(.c/.h/.S)會被編譯成一個叫做kernel的二進位文件,然後這個二進位文件會被運行在kernle mode中。
第二個部分是user。這基本上是運行在user mode的程序。這也是為什麼一個目錄稱為kernel,另一個目錄稱為user的原因。
第三部分叫做mkfs。它會使用fs.img創建一個空的文件鏡像,我們會將這個鏡像存在磁碟上,這樣我們就可以直接使用一個空的文件系統。
makefile會將kernel下的文件編譯成.o文件,之後會使用ld連結成可執行文件kernel,並使用objdump創建kernel.asm便於調試。
最後會調用qemu來運行起來xv6。
xv6編譯過程
目標qemu有兩個依賴文件:$K/kernel和fs.img。其中$K/kernel這樣生成:
$K/kernel: $(OBJS) $K/kernel.ld $U/initcode
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)
$(OBJDUMP) -S $K/kernel > $K/kernel.asm
$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym
$K/kernel就是整個xv6內核的二進位文件。它的依賴文件就是/kernel下的所有內容。.c和.S文件由隱含規則自動生成.o文件,xv6的編譯首先就是編譯/kernel下的所有.c和.S文件生成相應.o文件,之後再根據kernel.ld進行連結生成kernel二進位文件,並將其反彙編成用於調試的彙編文件。此外對於$U/initcode.S這個文件也要編譯、連結、反彙編,具體作用見後文。
再之後是對user文件夾下的用戶程序進行編譯、連結、反彙編。然後使用makefs把這些用戶程序的二進位文件寫到fs.img這個磁碟鏡像上。最後就可以使用qemu-system-riscv64命令啟動虛擬機了。這裡本質上是通過C語言來模擬仿真RISC-V處理器。
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic
-drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
我們來看傳給QEMU的幾個參數:
-kernel:這裡傳遞的是內核文件(kernel目錄下的kernel文件),這是將在QEMU中運行的程序文件。
-m:這裡傳遞的是RISC-V虛擬機將會使用的內存數量
-smp:這裡傳遞的是虛擬機可以使用的CPU核數
-drive:傳遞的是虛擬機使用的磁碟驅動,這裡傳入的是fs.img文件
這樣,XV6就在QEMU中啟動了。QEMU就是個C程序,它通過C語言來完全模擬硬體的行為。
xv6啟動過程
RISC-V採用內存映射I/O的方式,主板的設計人員決定了,在完成了虛擬到物理地址的翻譯之後,如果得到的物理地址大於0x80000000會走向DRAM晶片,如果得到的物理地址低於0x80000000會走向不同的I/O設備。這是由這個主板的設計人員決定的物理結構。如果你想要查看這裡的物理結構,你可以閱讀主板的手冊,手冊中會一一介紹物理地址對應關係。QEMU也會默認0x80000000是物理內存的起始處,它會從這裡開始執行指令。而kernel/kernel.ld會把kernel/entry.S中的指令放到0x80000000處,讓每個CPU都從這裡開始執行。kernel/entry.S的作用就是為每個CPU接下來運行C程序設置了4KB大小的棧。之後會跳到kernel/start.c中的start()函數。它在M模式下進行一些初始化,然後通過mret以S模式進入到main.c中。在main()中,CPU0會完成絕大多數初始化並且分配init進程。接著等待CPU1和CPU2完成自己的一些per-CPU初始化之後,進入scheduler()中,開始選擇進程來執行。從此就進入了OS的「死循環」當中,永遠不會返回了。