QEMU 印出 init: starting sh,接著出現 $。
這次不必重新編譯核心才能換一個測試,我們可以在 Shell 輸入 echo day19-ready,再執行另一個程式。
前面的 Tiny Kernel 已經讓我們碰過組成這個畫面的零件,現在要看一個較完整的系統如何把它們接起來。
今天不急著修改 scheduler,而是先取得可重現的 xv6,再替熟悉的功能找到原始碼位置。
xv6 是 MIT 用於教學的 Unix-like 作業系統,這裡採用 RISC-V 版本。
它提供行程、系統呼叫與檔案系統等功能,程式規模仍適合沿著單一呼叫路徑閱讀。
它的教學定位不代表已具備生產系統的完整安全性或裝置支援。
《Operating System Concepts》第 10 版第 2.8.1 節介紹 monolithic structure,也就是多項核心服務位於同一個核心位址空間。
xv6 的不同 .c 檔案是程式碼分工,不代表每個服務都在獨立的保護區域。
今天建立的 source map 要回答「誰負責這件事」,不能直接把檔案邊界解讀成權限邊界。
本系列從這一天切換為 RV64。
兩個專案保留在不同目錄,不把 Tiny Kernel 的 Makefile 或韌體直接覆蓋到 xv6。
| 項目 | Day 11~18 範例 | 本文 xv6-riscv |
|---|---|---|
| CPU 目標 | RV32 | RV64 |
| 一般暫存器寬度 | 32 bits | 64 bits |
| 分頁 | Sv32,兩層 | Sv39,三層 |
| PTE 大小 | 4 bytes | 8 bytes |
| 每層索引 | 10 bits | 9 bits |
| 核心入口 | OpenSBI 交給 S-mode payload | -bios none,核心入口從 M-mode 開始 |
| 磁碟驅動 | 本系列 legacy VirtIO MMIO | xv6 modern VirtIO MMIO |
ra、sp 與 s0 等暫存器名稱看起來相同,但資料寬度已不同。
Day 11 的 sw/lw 與 context offset 不能直接貼進 xv6 的 swtch.S。
同樣地,Sv32 的 VPN 拆法也不能拿來解讀 Sv39 頁表。
以下在 Kali 或 Debian 系開發環境操作,套件安裝需要管理員權限:
sudo apt update
sudo apt install -y build-essential git gcc-riscv64-linux-gnu \
binutils-riscv64-linux-gnu qemu-system-misc gdb-multiarch
確認工具能被找到:
riscv64-linux-gnu-gcc --version
qemu-system-riscv64 --version
gdb-multiarch --version
本文固定的 xv6 Makefile 要求 QEMU 至少 7.2,本文編寫時的啟動測試使用 8.2.2。
請以實際版本輸出確認,不只看套件安裝指令是否成功。
從 30-days-os-kernel/ 目錄建立一份新的 checkout:
git clone https://github.com/mit-pdos/xv6-riscv.git examples/xv6-riscv
cd examples/xv6-riscv
git checkout --detach 9e3161a9abf5f51ea402562d1874caf6c4926597
git rev-parse HEAD
若目的目錄已存在,先確認是不是你之前修改過的 xv6,不要刪除它來重跑教學。
可以改用另一個空目錄,並相應調整後面的路徑。
固定 commit 能避免文章中的函式名稱與你下載的版本不同。
這裡的 detach 只是閱讀基準,之後要修改 xv6 時可以從這個位置建立自己的分支。
目前位於 30-days-os-kernel/examples/xv6-riscv/:
make -j4 TOOLPREFIX=riscv64-linux-gnu- kernel/kernel fs.img
make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu
先使用單一 hart,讓第一輪觀察不受多核心輸出交錯影響。
預期終端機會出現:
xv6 kernel is booting
init: starting sh
$
接著在 xv6 的 Shell 輸入,不是在 Kali 的 Shell 輸入:
echo day19-ready
ls
echo 應印出測試字串,ls 則列出磁碟映像中的檔案。
這比只看見開機標語多驗證了一段使用者程式、系統呼叫與檔案系統的整合路徑,但仍不是完整回歸測試。
按 Ctrl+A 再按 X 離開 QEMU,磁碟映像仍保留在 checkout 中。
不要從第一個檔案一路讀到最後一個檔案。
先問一個具體問題,再找到回答它的資料結構與函式。
| 想找的責任 | xv6 入口 | 前面做過的事 |
|---|---|---|
| 建立核心入口與堆疊 | kernel/entry.S、start.c、main.c |
Boot 與 kernel_main() |
| 配置實體頁面 | kernel/kalloc.c |
alloc_pages()/free_pages() |
| 保存行程資料 | kernel/proc.h、proc.c |
struct process 與 state |
| 轉移執行上下文 | kernel/swtch.S |
switch_context() |
| 建立與查詢頁表 | kernel/vm.c |
vm_map()/vm_walk() |
| 接收使用者 Trap | kernel/trampoline.S、trap.c |
Trap Frame 與堆疊切換 |
| 分派系統呼叫 | kernel/syscall.c |
依 a7 選擇 handler |
| 讀取裝置與檔案 | kernel/virtio_disk.c、bio.c、fs.c |
disk_read() 與 fs_read() |
這是責任對照,不是函式一對一移植表。
例如 xv6 的 user trap 路徑要處理使用者與核心頁表切換,複雜度已超過 Day 17 共用核心映射的做法。
在 xv6 checkout 執行:
rg -n 'struct proc|scheduler\(|allocproc\(' kernel/proc.h kernel/proc.c
rg -n 'walk\(|mappages\(' kernel/vm.c
rg -n 'usertrap\(|syscall\(' kernel/trap.c kernel/syscall.c
若環境沒有 rg,可先安裝 ripgrep,或使用編輯器搜尋相同符號。
把搜尋結果和 source-map.md 對照,挑一條熟悉的責任寫下「輸入狀態、修改狀態、下一個函式」,不要只抄行號。
本日成果包含固定的 xv6 commit、成功啟動的 Shell,以及 版本與操作筆記 和 source-map.md。
上游 checkout 有自己的 Git repository,外層文章 repository 不要把它當成一般目錄直接加入,應使用獨立 repository 或明確設定的 submodule 管理。
建議文章與筆記的 commit:
day19: pin xv6 baseline and map kernel responsibilities
明天從 _entry 停住 CPU,沿著 start() 走到 main(),確認這個較完整的核心到底如何開始執行。