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。
- 每個 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
。
12 | # Save old callee-save registers |
- 依序將 context push 進堆疊
17 | # Switch stacks |
- 將
struct context ** old
(%eax
) 指向%esp
(當前堆疊的 top) - 將
%esp
指向struct context * new
(%ebx
)
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,修改狀態,呼叫
sched
switch 至 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 處於睡眠狀態,則喚醒它。