Multiplexing#
- 如果 process 需要等待 I/O 或是子 process 結束,XV6 讓其進入睡眠狀態,接著將處理器 switch 給其他 process。
- 此機制使 process 有獨佔 CPU 的假象。
- 完成 switch 的動作由 context switch 完成:
- 透明化 -> 使用 timer interrupt handler 驅動 context switch。
- 同時多個 process 需要 switch -> lock
Code: Context switch#
- 從 process 的 kernel stack -> schedluler kernel stack (CPU) -> 另一個 process 的 kernel stack。
- 永遠不會從 process 的 kernel stack -> process 的 kernel stack。

context switch
- 每個 process 都有自己的 kernel stack 及暫存器集合,每個 CPU 有自己的 scheduler stack。
- context 即 process 的暫存器集合,用一個
struct context *表示。
File: proc.h
44 | struct context { |
- trap 有可能會呼叫
yield,yield會呼叫sched,最後sched呼叫swtch(&proc->context, cpu->scheduler);。
File: swtch.S#
- swtch 有兩個參數:
struct context ** old、struct context * new。
1 | # Context switch |
- 將
%eax指向struct context ** old,%ebx指向struct context * new。

swtch
12 | # Save old callee-save registers |
- 依序將 context push 進堆疊

swtch
17 | # Switch stacks |
- 將
struct context ** old(%eax) 指向%esp(當前堆疊的 top) - 將
%esp指向struct context * new(%ebx)

swtch
20 | # Load new callee-save registers |
- 恢復保存的暫存器,用
ret指令跳回
Scheduling#
- process 要讓出 CPU 前需要先取得 ptable.lock,釋放其他擁有的鎖,修改 p->state,呼叫
sched。 sched會再次檢查以上動作,並且確定此時中斷是關閉的,最後呼叫swtch,將 process 的暫存器保存在 proc->context,switch 到 cpu->scheduler。
File: proc.c
sched#
| 功能 | 回傳值 |
|---|---|
| 檢查並跳至 swtch.h | void |
292 | void |
swtch會 return 回 scheduler 的堆疊上,scheduler 繼續執行迴圈:找到一個 process 運行,swtch到該 process,repeat。
scheduler#
| 功能 | 回傳值 |
|---|---|
| 執行調度,指定執行的 process | void |
249 | void |
scheduler找到一個RUNNABLE的 process,將 per-cpu 設為此 process,呼叫switchuvm切換到該 process 的頁表,更新狀態為RUNNING,swtch到該 process 中運行。
Code: mycpu and myproc#
- CPU 需要辨識目前正在執行的 process,XV6 有一個 struct cpu 的陣列,裡面包含了一些 CPU 的資訊,及當前 process 的資訊。
mycpu尋找當前的lapicid是哪顆 CPU 的。
mycpu#
| 功能 | 回傳值 |
|---|---|
| 找到目前所在的 CPU | CPU 結構指標 |
37 | struct cpu* |
myproc呼叫mycpu,從正確的 CPU 上尋找當前的 process。
| 功能 | 回傳值 |
|---|---|
| 找到當前的 process | proc 結構指標 |
57 | struct proc* |
睡眠與喚醒(例子)#
struct q { |
send直到隊伍q為空時,將要傳送的資料p放入隊中,recv直到q有東西時將資料取出,把q再次設為0- 這允許不同的 process 交換資料,但是很耗資源。
方案 1#
- 加入
sleep及wakeup機制,sleep(chan)將 process 在chan中睡眠(一個 wait channel),wakeup(chan)將chan上睡眠的 process 喚醒。
void* |
- 如此一來
recv能讓出 CPU,直到有人(send)將它喚醒。
問題:遺失的喚醒:

遺失的喚醒
- 假設
recv在 215 行查看隊伍,發現需要睡眠,就在要呼叫sleep之前發生中斷,send在其他的 CPU 將資料放進隊伍中,呼叫wakeup,發現沒有正在休眠的 process,於是不做事;接著recv終於呼叫sleep進入睡眠。 - 此時,
revc正在等待send將它喚醒,但是send正在等待隊伍清空,清空的動作須由recv完成(休眠中),於是就產生了 deadlock。
方案 2#
- 讓
send及recv在睡眠及喚醒前都持有鎖。
struct q { |
- 但這一樣有問題:
recv帶著鎖進入睡眠,send也同時需要鎖來喚醒,於是就產生了 deadlock。
方案 3#
- 在
sleep時一併釋放鎖,也就是將鎖當成參數傳進去。
struct q { |
Code: 睡眠與喚醒#
| 副程式 | 目標 |
|---|---|
sleep |
將狀態改成 SLEEPING,呼叫 sched 釋放 CPU |
wakeup |
尋找狀態為 SLEEPING 的 process,改成 RUNNABLE |
sleep#
| 功能 | 回傳值 |
|---|---|
| 讓 process 睡眠 | void |
*chan |
*lk |
|---|---|
| 欲睡眠的頻道 | 持有的鎖 |
418 | void |
- 首先確保 process 存在,及持有鎖。
428 | if(lk != &ptable.lock){ //DOC: sleeplock0 |
- 接著檢查是否持有
ptable->lock,如果沒有則要求一個,把lk釋出。
432 | // Go to sleep. |
- 睡眠,並呼叫
sched
437 | // Tidy up. |
wakeup1#
| 功能 | 回傳值 | *chan |
|---|---|---|
| 叫醒頻道上的 process | void | 頻道 |
458 | // |
wakeup#
| 功能 | 回傳值 | *chan |
|---|---|---|
| 叫醒頻道上的 process | void | 頻道 |
468 | void |
wakeup找到chan上在睡眠的 process,並喚醒。
Code: Pipes#
File: pipe.c
- pipe 為一個結構,包含一個鎖,一個
buf等。 - 當 pipe 為空時,
nread == nwrite - 當 pipe 為滿時,
nwrite == nread % PIPESIZE
12 | struct pipe { |
pipewrite#
| 功能 | 回傳值 |
|---|---|
| 寫入 pipe | 寫入的大小 |
*p |
*addr |
n |
|---|---|---|
| 欲寫入的 pipe | 欲寫入的值 | 欲寫入的大小 |
77 | int |
- 首先須取得鎖。
pipewrite從 0 開始將資料讀入buf,更新nwrite計數器,當buf滿了,則喚醒piperead並睡眠;或是讀入完畢,一樣喚醒pipiread。
piperead#
| 功能 | 回傳值 |
|---|---|
| 讀取 pipe | 讀入的大小 |
*p |
*addr |
n |
|---|---|---|
| 欲讀取的 pipe | 讀取資料存放區 | 欲讀入的大小 |
99 | int |
- 首先須取得鎖。
piperead確認 pipe 是否為空,為空則進入睡眠。- 當 pipe 不為空時,寫入資料,更新
nread計數器。 - 讀取完畢後,喚醒
pipewrite。
Code: Wait, exit and kill#
File: proc.c
wait#
| 功能 | 回傳值 |
|---|---|
| 等待子 process 結束 | pid (ok) / -1 (err) |
208 | int |
- 首先須取得鎖。
- 接著尋找是否有子 process,如果有,並且還沒退出,則睡眠,等待子 process 退出。
- 如果找到已退出的子 process,紀錄該子 process 的 pid,清理
struct proc,釋放相關記憶體。
exit#
| 功能 | 回傳值 |
|---|---|
| 自行結束 process | void |
166 | void |
- 首先須取得鎖。
- 喚醒父 process,將子 process 交給 initproc,修改狀態,呼叫
schedswitch 至 scheduler。
kill#
| 功能 | 回傳值 | pid |
|---|---|---|
| 使 process 終止 | 0 (ok) / -1 (err) | 欲終止的 process id |
402 | int |
kill將p->killed設為 1 ,當此 process 發生中斷或是 system call 進入 kernel,離開時trap會檢查p->killed,如果被設置了,則呼叫exit。- 當要被 kill 的 process 處於睡眠狀態,則喚醒它。