iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Software Development

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

Day 23|深入 xv6 Scheduler:Process 到底怎麼拿到 CPU?

  • 分享至 

  • xImage
  •  

三個子行程同時做計算,它們都沒有主動呼叫 Tiny Kernel 的 yield(),卻仍然能輪流取得 CPU。
今天要觀察 xv6 的計時器路徑,並把昨天加入的 scheduled 變成可以比較的資料。

先說清楚驗收標準:三個行程都能完成,而且核心確實記錄了多次 dispatch。
我們不要求每個計數完全相等,也不把次數當成 CPU 使用率。

從計時器走回排程器

固定版本的 usertrap() 在處理完 timer interrupt 後,會呼叫 yield()。
yield() 將目前行程設成 RUNNABLE,再透過 sched() 回到 CPU 的排程器。
因此,即使使用者程式只跑計算迴圈,也可能被中斷並換成另一個行程。

排程器本身掃描行程表,不是具有完整公平性保證的現代公平排程器。
多核心下還有誰先取得 p->lock、行程當時是否等待 I/O 等因素,單看一個輪替名稱不足以預測每次結果。

建立不在熱迴圈中印字的工作負載

在 xv6 checkout 新增 user/cpuburn.c:

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

int
main(void)
{
  int made = 0;
  for (int k = 0; k < 3; k++) {
    int pid = fork();
    if (pid < 0)
      break;
    if (pid == 0) {
      volatile uint64 value = 1;
      for (uint64 i = 0; i < 30000000; i++)
        value = value * 1664525 + 1013904223;
      printf("worker pid=%d result=%lu\n", getpid(), value);
      exit(0);
    }
    made++;
  }
  int failed = made != 3;
  for (int k = 0; k < made; k++) {
    int status;
    if (wait(&status) < 0 || status != 0)
      failed = 1;
  }
  exit(failed);
}

volatile 讓編譯器不能直接刪掉反覆讀寫這個值的工作,unsigned 算術的溢位則依模數運算處理。
它不是多核心同步機制,這裡每個子行程操作的也是自己位址空間中的變數。

在 Makefile 的 UPROGS 清單加入 $U/_cpuburn\,位置放在既有清單最後一項之前,保留反斜線續行。
不要修改自動產生的 ELF 或 fs.img 內容來加入程式。

把最後一次計數留下來

Day 22 已經在 scheduler() 增加一次 scheduled,今天不要再增加第二次。
為了觀察短命行程,在 kernel/proc.c 的 kexit() 中,找到 p->state = ZOMBIE;,緊接其後加入:

printk("finish pid=%d scheduled=%lu\n", p->pid, p->scheduled);

這個位置仍持有行程鎖,也尚未讓父行程回收該格。
輸出只是暫時的診斷工具,後面會改由統計 API 取得資料。
不要在 freeproc() 重設 PID 後才嘗試列出原本行程。

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

make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu

於 xv6 Shell 輸入 cpuburn。
應有三筆 worker 結果與對應的 finish 紀錄,PID 和先後順序以實際結果為準。

一核與三核分開比較

結束 QEMU,再用以下設定啟動同一份核心:

make TOOLPREFIX=riscv64-linux-gnu- CPUS=3 qemu

再次執行 cpuburn,記下 hart 數、工作迴圈次數及三個子行程的計數。
多核時可能有多個行程同時執行,不應期待輸出維持單核的循環順序。
工作太短而看不到多次 dispatch 時,可以增加迴圈上限,但不要把更長時間的測試與舊結果混在一起比較。

這份資料還不能回答什麼?

scheduled 只告訴我們被選中的次數,沒有記錄每次執行長度。
一個行程因為頻繁 I/O 而反覆睡眠,也可能有很高的 dispatch 次數,卻沒有消耗最多 CPU 時間。

《Operating System Concepts》的等待時間與周轉時間需要明確的時間起訖點,不能由這個計數直接推算。
目前輸出中還包含核心列印的干擾,所以這篇是排程行為觀察,不是效能 benchmark。

本日修改 Makefile 與 proc.c,新增 user/cpuburn.c,建議 commit:

day23: compare xv6 dispatch counts with cpu-bound workers

明天把其中一種「暫時不能執行」具體化,使用 pipe 讓行程等待資料,再看它如何被喚醒。

參考資料


上一篇
Day 22|swtch.S:把 Tiny OS 與 xv6 Context Switch 放在一起比較
系列文
打造 OS Kernel:從 OS in 1,000 Lines 到 xv6 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言