iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Software Development

打造 OS Kernel:從 OS in 1,000 Lines 到 xv6系列 第 16 篇

Day 16|User Process:第一次讓 Kernel 執行自己的 Application

  • 分享至 

  • xImage
  •  

今天會產生兩份 ELF:核心的 kernel.elf,以及使用者程式的 user.elf。
核心要把後者的機器碼放進一頁 RAM,映射到固定的使用者位址,再準備另一頁堆疊。

這篇先走到「已載入並可檢查」的位置,尚不執行 sret 進入 U-mode。
真正的權限轉換與系統呼叫會在 Day 17 完成,這樣程式載入和 Trap 返回的問題可以分開驗證。

程式的連結位址與存放位置可以不同

《Operating System Concepts》第 10 版第 2.5 節,區分來源程式、連結後的執行檔,以及載入記憶體的執行映像。
我們昨天已經能把 VA 映射到 PA,現在可以讓使用者程式以固定 VA 連結,實際 backing page 則由配置器決定。

本次採用《OS in 1,000 Lines》〈應用程式〉與〈使用者模式〉的 raw binary 嵌入方法。
這個載入器有明確限制:只有一頁唯讀可執行映像,程式使用手寫組語,沒有 .bss、heap 或動態連結需求。
因此不需要在核心中解析 ELF program headers,但也不能直接拿一般 Linux 執行檔套用。

先看今天要建立的位址空間

虛擬範圍 權限 用途
0x01000000~0x01000fff U、R、X 使用者程式與唯讀文字
0x01001000~0x01001fff 未映射 堆疊下方的 guard page
0x01002000~0x01002fff U、R、W 使用者堆疊
核心所需範圍 無 U 位元 核心程式、資料與核心堆疊

初始 user sp 是 0x01003000,第一次使用時往低位址保留空間。
Guard page 只能協助發現跨入該頁的存取,不能保證捕捉所有形式的堆疊溢位。

編譯出使用者映像

在 30-days-os-kernel/examples/tiny-kernel/ 執行:

make DAY=16
llvm-readelf -h build/day16/user.elf
llvm-objdump -d build/day16/user.elf
od -Ax -tx1 -N 64 build/day16/user.bin

完整來源為 user.S 與 user.ld。
Linker 將 user_start 放在 0x01000000,並檢查程式是否超出一頁。
Makefile 用 llvm-objcopy -O binary 取得 raw bytes,再透過 image.S 的 .incbin 嵌入核心唯讀區。

user_image_start:
    .incbin USER_IMAGE
user_image_end:

USER_IMAGE 由 Makefile 指向本日的 user.bin,因此不必手動複製二進位內容。
核心用起訖符號算出大小,再逐 byte 複製到新配置的程式頁。

用不同頁面承接程式與堆疊

閱讀 user.c 的 user_demo(),載入部分包括:

memcpy(code, user_image_start, size);
vm_map(user_root, USER_BASE, (uint32_t) code, PTE_U | PTE_R | PTE_X);
vm_map(user_root, USER_STACK, (uint32_t) stack, PTE_U | PTE_R | PTE_W);

程式碼不可寫,堆疊不可執行,這是頁表權限真正開始區分用途的地方。
另外再配置一頁核心堆疊,給下一篇從 U-mode 進入核心時使用。
不能期待使用者提供的 sp 適合直接承接核心的 C 函式呼叫。

完成資料複製後執行 fence.i,使接下來取指令能與本 hart 的寫入保持一致。
它處理的是指令與資料的同步關係,和 sfence.vma 的位址轉譯同步不同。

啟動後檢查,而不是直接跳過驗收

執行:

make DAY=16 run

預期看到兩筆映射與準備完成訊息:

VA=0x1000000 vpn1=4 vpn0=0 offset=0
VA=0x1002000 vpn1=4 vpn0=2 offset=0
user image bytes=... prepared; entry=0x1000000 stack=0x1003000
experiment complete

程式還會檢查 guard page 不存在、程式頁沒有 W,以及堆疊頁沒有 X。
這些目前是頁表內容檢查,不是已經執行過違規存取的硬體保護測試。

可在 QEMU monitor 暫停後,使用 xp 讀取 dump 印出的程式 PA,與 od 的 raw bytes 比較。
這裡比較的是內容是否正確載入,不要把 VA 誤當成 xp 所需的實體位址。

這次還沒有把使用者工作接進排程器

Day 11~Day 13 展示核心行程切換,今天改用獨立開機路徑建立一個使用者位址空間。
把多個 U-mode 行程加入排程,還需要在切換時管理 satp、核心堆疊與每個行程的 Trap Frame。

目前先完成單一映像載入,之後閱讀 xv6 的 exec() 與 usertrapret() 時,再對照較完整的整合方式。
這也讓今天的可執行範圍保持明確,不會把只有兩個結構的狀態說成已完成多行程隔離。

本篇主要檔案是 user.S、user.ld、image.S、user.c 與 Makefile。
建議 commit 訊息:

day16: prepare user image and address space

Day 17 會使用這份映像,第一次進入 U-mode,再從 ecall 返回核心取得輸出服務。

參考資料


上一篇
Day 15|Page Table 實戰:自己走一次 Virtual Address Translation
下一篇
Day 17|System Call:User Program 第一次向 Kernel 求助
系列文
打造 OS Kernel:從 OS in 1,000 Lines 到 xv6 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言