iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Software Development

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

Day 30|終極實戰:完成自己的 xv6 Kernel Monitor

  • 分享至 

  • xImage
  •  

今天不再新增一個核心名詞,而是問現有工具能不能區分四種不同工作:持續計算、等待事件、擴充記憶體,以及反覆進行系統呼叫。
如果資料欄位沒有清楚的定義,最後只會得到一張有很多數字、卻無法解釋的表。

我們會讓同一個行程在工作前後各呼叫一次 pstat,再用差值整理結果。
這個 Monitor 是命令列觀察與驗證工具,不是全系統即時儀表板。

一個測試程式,四種工作

在 xv6 checkout 新增 user/observer.c,並將 $U/_observer\ 加入 Makefile 的 UPROGS:

#include "kernel/types.h"
#include "kernel/pstat.h"
#include "user/user.h"

int
main(int argc, char **argv)
{
  if (argc != 2) {
    fprintf(2, "usage: observer cpu|wait|memory|syscall\n");
    exit(1);
  }
  struct pstat before, after;
  if (pstat(&before, sizeof(before)) < 0)
    exit(1);
  char *allocated = 0;
  int expected_syscalls = -1;
  if (strcmp(argv[1], "cpu") == 0) {
    volatile uint64 value = 1;
    for (uint64 i = 0; i < 30000000; i++)
      value = value * 1664525 + 1013904223;
    expected_syscalls = 1;
  } else if (strcmp(argv[1], "wait") == 0) {
    if (pause(20) < 0)
      exit(1);
    expected_syscalls = 2;
  } else if (strcmp(argv[1], "memory") == 0) {
    allocated = sbrk(4 * 4096);
    if (allocated == SBRK_ERROR)
      exit(1);
    for (int i = 0; i < 4 * 4096; i += 4096)
      allocated[i] = 'M';
    expected_syscalls = 2;
  } else if (strcmp(argv[1], "syscall") == 0) {
    for (int i = 0; i < 1000; i++)
      getpid();
    expected_syscalls = 1001;
  } else {
    fprintf(2, "unknown workload\n");
    exit(1);
  }
  if (pstat(&after, sizeof(after)) < 0)
    exit(1);
  uint64 calls = after.syscalls - before.syscalls;
  printf("work=%s pid=%d scheduled=%lu calls=%lu sleep=%lu wake=%lu yield=%lu size_before=%lu size_after=%lu\n",
         argv[1], after.pid, after.scheduled - before.scheduled,
         calls, after.sleeps - before.sleeps,
         after.wakes - before.wakes, after.yields - before.yields,
         before.size_bytes, after.size_bytes);
  int failed = before.pid != after.pid || calls != expected_syscalls;
  if (allocated && after.size_bytes != before.size_bytes + 4 * 4096)
    failed = 1;
  if (allocated && sbrk(-4 * 4096) == SBRK_ERROR)
    failed = 1;
  printf("observer checks=%s\n", failed ? "failed" : "ok");
  exit(failed);
}

列印放在第二次取樣之後,因此 write 不會混入被比較的區間。
第二次 pstat 本身已先被 dispatcher 計入,這就是每種工作都額外多 1 次 syscall 的原因。
記憶體釋放也放在取樣之後,確保我們先看得到大小增加,再恢復配置。

先做單 hart 基準

在 30-days-os-kernel/examples/xv6-riscv/ 執行:

git rev-parse HEAD
git diff --stat
make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu

於 xv6 Shell 依序執行:

pstat
observer cpu
observer wait
observer memory
observer syscall
pipewait
racetest 2
trace pidprobe

不要先用 trace observer syscall 量測,因為它會為每次 getpid 印出一行,明顯改變工作條件。
最後的 trace pidprobe 只檢查選擇性追蹤是否仍可使用。

哪些數字可以嚴格檢查?

工作 可檢查的關係 不固定的數值
cpu 取樣區間 syscall 差值為 1 dispatch 與 timer yield 次數
wait syscall 差值為 2 實際睡眠與喚醒次數
memory syscall 差值為 2,sz 增加 16,384 bytes 實際 PA 與排程次數
syscall syscall 差值為 1,001 完成時間與是否被搶占

wait 的結果通常包含 sleep/wake,但不要把「等待 20 ticks」直接等同於「剛好睡眠 20 次」。
事件可能在進入睡眠前已經到達,計時與排程也會改變狀態轉移次數。

memory 的差值驗證的是 sz,不是 RSS。
本程式使用固定版本的 eager sbrk() 並碰觸每一頁,沒有宣稱已完成 lazy allocation、page replacement 或記憶體壓力測試。

再到多 hart 檢查假設

結束 QEMU,再以 CPUS=3 啟動,重跑同一組命令。
需要多個可執行工作時,可執行 Day 23 的 cpuburn,或在 Shell 分別執行 observer cpu & 與另一份 observer cpu。
每筆結果以自己的 PID 和前後差值為單位,不把兩個 PID 的累計值相減。

race 測試必須仍然逐次執行,不能同時啟動兩份含 reset 的 racetest。
這是教學 API 的限制,不應被批次工具忽略。

把結果整理成可重現紀錄

在文章 repository 另存一份測試紀錄,至少包含以下資訊:

  • xv6 基準 commit 與自己的修改 commit
  • QEMU/toolchain 版本與 CPUS 設定
  • 執行命令、trace 開關與完整結果
  • 通過的斷言,以及未測試的情況

不要只保留裁切後的成功截圖,失敗紀錄同樣能幫助後續定位問題。

回頭看 Tiny Kernel 與 xv6

責任 自己寫過的最小版本 現在能觀察的 xv6 行為
執行切換 保存 ra/sp/s 暫存器 CPU 與行程 context 交接
排程 合作式循環選擇 timer yield 與多 hart dispatch
位址空間 Sv32 映射與 walker Sv39、exec 與特殊頁面
系統呼叫 ecall 與簡單 dispatcher 使用者 stub、trampoline 與 pstat
等待 手動等待與喚醒 pipe/timer 的等待協定
觀察 Console 與單次 assertion 結構化統計、可選 trace 與差值測試

我們仍沒有實作全系統原子快照、CPU 使用率估算或持久化 trace,也沒有把單次 QEMU 成功等同於核心已無缺陷。
但現在每個觀察欄位都有更新位置,每個測試都有可檢查的關係,已經能用程式解釋核心的一段實際行為。

本日新增 user/observer.c、Makefile 項目與自己的測試報告。
建議 commit:

day30: validate kernel observer across four workloads

30 天的終點不是記住更多名詞,而是遇到一個核心行為時,知道要看哪份狀態、在哪個邊界量測,以及證據還不足以支持哪些結論。

參考資料


上一篇
Day 29|打造 Kernel Observer:把 Process、Syscall、Scheduler 資訊整合起來
系列文
打造 OS Kernel:從 OS in 1,000 Lines 到 xv6 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言