7.排程相關

INnoVation-V2發表於2024-04-21

一.基礎

使用者執行緒狀態

  • 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

  1. 線上程切換的過程中需要一直持有p->lock

  2. XV6中,不允許程序在執行switch函式的過程中,持有任何其他的鎖,因為可能會影響其他程序的後續執行

    例如獲取鎖的函式acquire()需要關閉中斷,因為不關閉中斷,在獲取到鎖後,發生了中斷,而中斷切換到的程式執行也需要這把鎖,那就會發生死鎖

相關文章