# xv6 Kernel-16: Scheduling + swtch.S #### `kernel/proc.c: yeild()` `yeild()` 讓 `myproc()` 的 state 變成 `RUNNABLE` 並且呼叫 `sched()`,`sched()` 可以讓其他 function 也有機會可以被執行到 `yeild()` 想要做的事情是: "我用 CPU 用夠久了,可以換其他人使用了" 所謂的 "換其他人使用" 也就是呼叫 `sched()`,它會幫我們處理 ```Clike= // Give up the CPU for one scheduling round. void yield(void) { struct proc *p = myproc(); acquire(&p->lock); p->state = RUNNABLE; sched(); release(&p->lock); } ``` #### `kernel/proc.c: sched()` 解析 `sched()` 被以下幾個 function 呼叫: * `kernel/proc.c: exit()` * `kernel/proc.c: yeild()` * `kernel/proc.c: sleep()` 從這裡可以發現,這都是在一些 "該輪到別人使用 CPU 了" 的時間點 所以可以推測 `sched()` 是用來決定下一個使用 CPU 的人是誰 不過讓我比較意外地點是,`scheduler()` 並沒有呼叫到 `sched()`, 那麼 `scheduler()` 到底扮演了什麼樣的角色呢 ```C // Switch to scheduler. Must hold only p->lock // and have changed proc->state. Saves and restores // intena because intena is a property of this // kernel thread, not this CPU. It should // be proc->intena and proc->noff, but that would // break in the few places where a lock is held but // there's no process. void sched(void) { int intena; struct proc *p = myproc(); if(!holding(&p->lock)) // 我應該要 hold 我自己的 lock panic("sched p->lock"); if(mycpu()->noff != 1) // TODO: why noff should be equals to 1 ? panic("sched locks"); if(p->state == RUNNING) // sched() 的 caller 會先把 myproc() 設為 RUNNABLE panic("sched running"); if(intr_get()) // TODO: learn sstatus first panic("sched interruptible"); intena = mycpu()->intena; // why? swtch(&p->context, &mycpu()->context); mycpu()->intena = intena; } ``` 幾個困惑的點 * `swtch()` 實際上是如何運作的 * `mycpu()->context` 裡面到底裝著些什麼東西 * `mycpu()->context` 什麼時候會被初始化 * 為什麼 `mycpu()->noff` 應該要是 1 * `noff` 到底是為了什麼而存在? * 被呼叫到的地方: * `kernel/proc.c <<exit>>` * `kernel/proc.c <<yield>>` * `kernel/proc.c <<sleep>>` * 最後會藉由 `swtch` 跑到 `scheduler()` 執行 ### `schedul ```C // Per-CPU process scheduler. // Each CPU calls scheduler() after setting itself up. // Scheduler never returns. It loops, doing: // - choose a process to run. // - swtch to start running that process. // - eventually that process transfers control // via swtch back to the 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(); // 所有的 process 都被 紀錄在 proc[] 這個 array 中 // scheduler() 負責依序把所有 RUNNABLE 的 process 拿來跑 for(p = proc; p < &proc[NPROC]; p++) { 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 在 sched() 也有被執行到 // Process is done running for now. // It should have changed its p->state before coming back. c->proc = 0; // 此時此刻這個 cpu 沒有執行任何的 process } release(&p->lock); } } } ``` * 被呼叫到的地方: * `kernel/main.c: main()` 的最後一行被執行到 * `sched()` 藉由 `swtch()` 跑來這裡 * 最大的功用就是確保每個 process 都有機會被執到 * 畢竟每個當下可能都有數個 process 正在執行當中 ### `cpuid()` ```C int cpuid() { int id = r_tp(); return id; } ``` ### `mycpu()` ```C struct cpu* mycpu(void) { int id = cpuid(); struct cpu *c = &cpus[id]; return c; } ``` * 利用 `cpuid()` 拿到 id 之後再去 `cpu[]` 中拿到 `struct cpu *c` ### `myproc()` ```C struct proc* myproc(void) { push_off(); struct cpu *c = mycpu(); struct proc *p = c->proc; pop_off(); return p; } ``` * 為什麼要 `push_off()` ? * 就以行為上來說 * push 了幾次,就要 pop 幾次才可以回到 interrupt enable 的狀態 * 背後的動機? * `push_off()` * `intr_off()` // interrupt off ## `switch.S` ## 參考資料 [xv6 Kernel-16: Scheduling + swtch.S](https://www.youtube.com/watch?v=-O_JX5mMMHY&list=PLbtzT1TYeoMhTPzyTZboW_j7TPAnjv9XB&index=9)