一.基礎
使用者執行緒狀態
- RUNNING,執行緒當前正在某個CPU上執行
- RUNABLE,執行緒還沒有在某個CPU上執行,但是一旦有空閒的CPU就可以執行
- SLEEPING,這節課不介紹,
一. 定時器中斷執行緒切換
防止個別程式長時間佔有執行緒導致其他執行緒餓死,透過定時器中斷定時釋放執行緒資源
定時器中斷是由硬體發起的
一.執行緒切換流程
P0使用者執行緒-->P0核心執行緒-->排程器執行緒-->P1核心執行緒-->P1使用者執行緒
排程器執行緒是每個CPU核獨有的,也是一個軟體,其實就是proc.c\scheduler()
函式,儲存在cpu->context
中
1. P0使用者執行緒-->P0核心執行緒
計時器發出中斷,使用者執行緒跳轉到核心執行緒
跳轉時,將使用者執行緒執行時資訊(暫存器,pc等)儲存在trapframe
中
2. P0核心執行緒-->CPU核對應的排程器執行緒
計時器中斷,所以會呼叫proc.c\yield()
void yield(void)
{
struct proc *p = myproc();
// 這裡必須加鎖,因為需要將程序的狀態修改為RUNNABLE
// 且在修改後的一段時間,程序實際還在執行,如果不加鎖
// 在修改程序狀態後,其他排程器可能就會開始執行此程序
// 從而就會發生一個程序在兩個CPU上同時執行的錯誤狀況
// 這裡加的鎖,在scheduler()中才會釋放
acquire(&p->lock);
p->state = RUNNABLE;
sched();
// 這裡釋放的不是上面加的鎖
release(&p->lock);
}
之後是proc.c\sched()
void sched(void)
{
int intena;
struct proc *p = myproc();
//一些合理性檢查
if(!holding(&p->lock))
panic("sched p->lock");
if(mycpu()->noff != 1)
panic("sched locks");
if(p->state == RUNNING)
panic("sched running");
if(intr_get())
panic("sched interruptible");
intena = mycpu()->intena;
// swtch(struct context *old, struct context *new)
// 將當前核心執行緒的狀態儲存到p->context中,
// 然後恢復排程器執行緒,mycpu()->context儲存
// 的就是排程器執行緒資訊
swtch(&p->context, &mycpu()->context);
mycpu()->intena = intena;
}
最後使用swtch.S
函式切換到排程器執行緒
mycpu()->context
中的ra指向scheduler()
函式,切換完成後,CPU將跳轉到scheduler()
函式中
.globl swtch
swtch:
sd ra, 0(a0)
sd sp, 8(a0)
sd s0, 16(a0)
sd s1, 24(a0)
sd s2, 32(a0)
sd s3, 40(a0)
sd s4, 48(a0)
sd s5, 56(a0)
sd s6, 64(a0)
sd s7, 72(a0)
sd s8, 80(a0)
sd s9, 88(a0)
sd s10, 96(a0)
sd s11, 104(a0)
ld ra, 0(a1)
ld sp, 8(a1)
ld s0, 16(a1)
ld s1, 24(a1)
ld s2, 32(a1)
ld s3, 40(a1)
ld s4, 48(a1)
ld s5, 56(a1)
ld s6, 64(a1)
ld s7, 72(a1)
ld s8, 80(a1)
ld s9, 88(a1)
ld s10, 96(a1)
ld s11, 104(a1)
ret
注:這裡只需要儲存Callee暫存器,因為按照約定,只有Callee Saved暫存器在函式呼叫的時候會儲存和恢復
3. 排程器執行緒-->P1核心執行緒
proc.c\scheduler()
void scheduler(void)
{
struct proc *p;
struct cpu *c = mycpu();
c->proc = 0;
for(;;){
// Avoid deadlock by ensuring that devices can interrupt.
intr_on();
for(p = proc; p < &proc[NPROC]; p++) {
//這裡加的鎖在yield()中釋放
acquire(&p->lock);
if(p->state == RUNNABLE) {
// Switch to chosen process.It is the process's job
// to release its lock and then reacquire it
// before jumping back to us.
p->state = RUNNING;
c->proc = p;
swtch(&c->context, &p->context);
// switch.S會跳轉到這裡繼續執行
c->proc = 0;
}
//這裡釋放的是yield()加的鎖
release(&p->lock);
}
}
}
4.P1核心執行緒-->P1使用者執行緒
就是正常的中斷返回,回到使用者執行緒繼續執行
執行緒切換工作完成
二.首次執行緒切換
二.Sleep & Wake up
-
線上程切換的過程中需要一直持有p->lock
-
XV6中,不允許程序在執行switch函式的過程中,持有任何其他的鎖,因為可能會影響其他程序的後續執行
例如獲取鎖的函式
acquire()
需要關閉中斷,因為不關閉中斷,在獲取到鎖後,發生了中斷,而中斷切換到的程式執行也需要這把鎖,那就會發生死鎖