使用RUST編寫OS(2)-最小內核
學習RUST同時學習OS
引導啟動
當我們啟動電腦時,主板內的ROW儲存的固件(firmware)將會運行,他將會負責電腦的加電自檢(power-on self test)和可用內存的檢測(available ram) 以及CPU和其他硬件的預加載,之後將尋找一個可引導的儲存介質(bootable disk),並開始啟動內核(kernel)
x86架構支持兩種固件標準:BIOS和UEFI,本系列將會以BIOS為標準
BIOS 啟動
當電腦啟動時
- 主板上特殊的閃存中儲存的BIOS將會被加載
- BIOS將會加電自檢,初始化硬件,然後尋找可引導的儲存介質
- 當找到後,電腦控制全將轉移給引導程序(bootloader)
- 一段儲存在存儲介質的開頭,512byte的程序片段,大多數引導程序都不會超過512byte,所以通常情況下都會分為
- 優先啟動,長度不超過512byte,儲存在介質開頭的第一階段引導程序(first stage bootloader)
- 隨後加載,長度較第一段長,儲存在其他位置的第二階段引導程序(second stage bootloader)
- 一段儲存在存儲介質的開頭,512byte的程序片段,大多數引導程序都不會超過512byte,所以通常情況下都會分為
引導程序的作用
- 引導程序先決定內核位址,並將內核加載到內存
- 引導程序將CPU從16位元的實模式,切換到32位元的保護模式(protected mod),最終再切換到64位元的長模式(long mod),此時才能訪問所有64位元的內存器和主位元
- 引導程序能從BIOS查詢特定訊息,並轉傳到內核,如查詢和傳遞內存映射表 (memory map)
使用Assembly編寫一個簡單的BIOS
- 將會在畫面上顯示A到Z
mov ah, 0x0e
mov al, 65
int 0x10
loop:
inc al
cmp al, 'Z' + 1
je exit
int 0x10
jmp loop
exit:
jmp $
times 510-($-$$) db 0 #內存啟動位址
db 0x55, 0xaa
Multiboot 標準
Field | Type | Value |
---|---|---|
magic number | u32 | 0xE85250D6 |
architecture | u32 | 0 for i386, 4 for MIPS |
header length | u32 | total header size, including tags |
checksum | u32 | -(magic + architecture + header_length) |
tags | variable | |
end tag | (u16, u16, u32) | (0, 0, 8) |
section .multiboot_header
header_start:
dd 0xe85250d6 ; magic number (multiboot 2)
dd 0 ; architecture 0 (protected mode i386)
dd header_end - header_start ; header length
; checksum
dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))
; insert optional multiboot tags here
; required end tag
dw 0 ; type
dw 0 ; flags
dd 8 ; size
header_end:
- 0xe85250d6 為 multiboot 的 magic number
- dd 為 defined double 32 bit
- dw 為 defined word 16 bit
- 0x100000000 為特殊記憶體位置,可以在compiler的時候避免報錯
最小內核
安裝Rust Nightly
- stable
- beta
- nightly
安裝方法
- 使用rustup
rustup override add nightly
- 新增名為rust-toolchain的文件且內容為nightly
目標配置清單
- 使用目標配置清單是為了讓我們的系統不依賴底層配置系統
- 編寫目標系統可以使用json格式完成
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"executables": true
}
- 因為要在裸機上運行,因此llvm-target為none
添加額外配置
- 不使用平台默認連接器,使用跨平台LLD鏈接器
{
"linker-flavor": "ld.lld",
"linker": "rust-lld"
}
禁用紅區
- 紅區為一種優化方式,當有n個變量時,將會調整指針到一個合適的地方,來確保返回值和局部變量有足夠空間。不過CPU異常處理機制會覆蓋紅區,但被中斷的函數卻引用這些數據,因此從中斷恢復後反而會引發更大錯誤
- 為了處理中斷,我們需要禁用紅區(redzone),避免榐指針優化進而破壞榐
{
"disable-redzone": true
}
禁用SIMD
- 操作系統在處理硬件中斷時,需要保存所有寄存器信息到內存中,在中斷結束後再將其恢復以供使用。所以說,如果內核需要使用SIMD寄存器,那麼每次處理中斷需要備份非常多的數據(512-1600字節),這會顯著地降低性能。要避免這部分性能損失
- 對內核禁用SIMD,提高性能表現(不代表內核不支援SIMD)
{
"features": "-mmx,-sse,+soft-float"
}
完整目標配置清單
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float"
}