スケジューラ
Plan9のスケジューリング方式は基本的にはUNIXと同じ優先度つきラウンドロビンである.リアルタイムプロセス用にEDFスケジューリングも可能だが,まずはEDFは無視して,読み進めようと思う.
優先度は22レベルあり,最上位の二つはEDFスケジューリング用に予約されている.UNIXと違って,優先度は数字が大きな方が優先度が高い.優先度関係はport/portdef.hで定義されている.
582: Npriq = 20, /* number of scheduler priority levels */ 583: Nrq = Npriq+2, /* number of priority levels including real time */ 584: PriRelease = Npriq, /* released edf processes */ 585: PriEdf = Npriq+1, /* active edf processes */ 586: PriExtra = 0, /* edf processes we don't care about */ 587: PriNormal = 10, /* base priority for normal processes */ 588: PriKproc = 13, /* base priority for kernel processes */ 589: PriRoot = 13, /* base priority for root processes */
昨日書いたように,スケジューラの本体の関数はrunproc (port/proc.c)である.レディキューのrunqはSched構造体の配列であり,各優先度のエントリからProc構造体が(片方向)リンクリストで連結されている.runvecはrunqの各エントリにプロセスが存在するか示すビットマップである.
少し注目すべきところは,マルチプロセッサに対応しているので,プロセッサアフィニティも考慮してスケジューリングしている点だろうか.つまり,以前プロセッサAで実行されたプロセスは,次もプロセッサAで実行することで,キャッシュの恩恵を受けやすくなるように工夫している.なお,p->mpは,前回実行されたプロセッサ(struct Mach)を指している.また,p->wiredを設定することで,必ず特定のプロセッサで実行するように指定できる(procwired関数経由かな).
スケジューラの核は529行から始まるforループである.最初に(i == 0のとき)このループを実行するときは,優先度の高いプロセスから探索し,プロセッサアフィニティがあれば(p->mp == MACHP(m->machno)),そのプロセスを選択する.最初のループで実行可能なプロセスが見つからなかった場合は,wiredされていないプロセスも含め,選択の幅を広げて再探索する.
プロセスを発見したら,546行に飛び,レディキューからプロセスを取り出し,p->stateとp->mpを設定し,リターンする.
40: Schedq runq[Nrq]; 41: ulong runvec; 495: Proc* 496: runproc(void) 497: { 498: Schedq *rq; 499: Proc *p; 501: int i; 516: loop: 522: spllo(); 523: for(i = 0;; i++){ 529: for(rq = &runq[Nrq-1]; rq >= runq; rq--){ 530: for(p = rq->head; p; p = p->rnext){ 531: if(p->mp == nil || p->mp == MACHP(m->machno) 532: || (!p->wired && i > 0)) 533: goto found; 534: } 535: } 536: 537: /* waste time or halt the CPU */ 538: idlehands(); 544: } 545: 546: found: 547: splhi(); 548: p = dequeueproc(rq, p); 549: if(p == nil) 550: goto loop; 551: 552: p->state = Scheding; 553: p->mp = MACHP(m->machno); 562: return p; 563: }
今日はここまでにするが,キュー操作用の関数として,dequeueprocとqueueprocという関数が使われている.