iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Software Development

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

Day 13|Scheduler 不只要選人:加入 Process State 與排程觀察功能

  • 分享至 

  • xImage
  •  

今天 P1 一開始就進入等待,暫時離開可執行的集合。
等 P2 發出喚醒通知,P1 才重新取得排程資格,並繼續原本的工作。

我們會把每次狀態轉換印出來,讓「沒輪到它」和「它目前不能執行」成為兩件能從紀錄辨認的事。

Ready 與 Waiting 在等不同的東西

《Operating System Concepts》第 10 版第 3.1.2 節,區分正在執行、已準備好與等待事件的行程。
Ready 等的是 CPU,Waiting 等的則是某個條件,例如 I/O 完成。

本系列用 RUNNABLE 對應 Ready,用 WAITING 表示暫時不能繼續。
若把所有工作都當成可執行,等待者就只能一直檢查條件,浪費執行機會。
把它移出可執行集合,排程器才有辦法把 CPU 留給能推進的工作。

今天用另一個行程呼叫喚醒函式模擬事件,不接實際裝置,也不按經過幾秒判定喚醒。
這讓每次狀態改變都有一個可追蹤的來源。

跑一次等待與喚醒

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

make DAY=13 run

閱讀 scheduler.ctask(),P1 先呼叫 sleep_current(),P2 則呼叫 wakeup_pid(1)
之後三個工作再各自完成三輪。

void sleep_current(void) {
    KASSERT(current && current->state == RUNNING);
    current->sleep_count++;
    current->state = WAITING;
    trace("RUNNING -> WAITING");
    switch_context(&current->context, &scheduler_context);
}

這裡先改狀態再切回核心,排程器之後搜尋時就會跳過 P1。
如果誤設成 RUNNABLE,它只是普通的 yield(),並沒有真正等待事件。

喚醒代表重新取得資格

wakeup_pid() 只把符合 PID 且狀態為 WAITING 的行程改成 RUNNABLE,並增加 wake_count
它不直接呼叫行程入口,也不立刻執行 context switch。

因此正常順序是:

P1:RUNNING → WAITING
P2:將 P1 改成 RUNNABLE
核心稍後選中 P1:RUNNABLE → RUNNING

這個區分很重要,否則容易把「有人通知事件已發生」誤讀成「等待者已經跑完接下來的工作」。
本次維持循環搜尋,所以 P1 會依序等到自己的下一個執行機會。

統計要能說明每一次額外執行

預期結果:

pid=1 scheduled=5 yield=3 sleep=1 wake=1
pid=2 scheduled=4 yield=3 sleep=0 wake=0
pid=3 scheduled=4 yield=3 sleep=0 wake=0

P1 比昨天多取得一次 CPU,因為第一次執行先去等待,喚醒後才開始三輪工作。
這次額外切入並不是它完成了更多迴圈,而是執行路徑增加了一次睡眠與恢復。

run_count 計算 dispatch,yield_count 只計算明確的 yield(),所以 sleep 不會偷偷算進 yield。
先把指標的定義固定下來,之後做 Kernel Observer 才能比較不同測試。

所有人都在等待時,不能假裝完成了

排程器找不到 RUNNABLE 時,還要區分所有工作都已 DONE,或仍有工作停在 WAITING
範例對後者印出 Panic,因為目前沒有計時器或裝置中斷會帶來新事件。
如果改成無條件 wfi,很可能只會得到一個永遠等不到進展的畫面。

可在實驗副本中暫時移除 P2 的 wakeup_pid(1),重跑後預期看到:

all live tasks waiting; no event source

完成後恢復喚醒呼叫。
這個對照用來抓出遺漏通知的情況,不是通用 deadlock detector。

目前為什麼還不需要鎖?

我們固定單核心、沒有搶占,而且檢查與狀態修改之間不會主動 yield()
因此這個小實驗中的轉換不會被另一個工作插入。

若加入多核心或可在中途介入的中斷,條件檢查、進入等待與喚醒之間就需要更嚴格的同步。
Day 24 讀 xv6 的 sleep()wakeup() 時,會看到鎖如何避免 Lost Wakeup,也就是事件已通知但等待者卻錯過了它。

今天的主要檔案仍是 scheduler.clab.h,建議 commit 訊息:

day13: add process states and scheduling trace

Day 14 轉向記憶體視角,讓相同實體頁透過不同虛擬位址被看見,開始準備行程的位址空間。

參考資料

  • Silberschatz、Galvin、Gagne,《Operating System Concepts》第 10 版,第 3.1.2~3.1.3 節,pp. 107–110
  • OS in 1,000 Lines:行程

上一篇
Day 12|第一次寫 Scheduler:Round-Robin 如何分享 CPU?
系列文
打造 OS Kernel:從 OS in 1,000 Lines 到 xv613
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言