proc.c void scheduler(void)

トップページ
jupiteroak.hatenablog.com


proc.c
https://github.com/mit-pdos/xv6-public/blob/master/proc.c#L322

void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();
  c->proc = 0;
  
  for(;;){
    // Enable interrupts on this processor.
    sti();

    // Loop over process table looking for process to run.
    acquire(&ptable.lock);
    for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
      if(p->state != RUNNABLE)
        continue;

      // Switch to chosen process.  It is the process's job
      // to release ptable.lock and then reacquire it
      // before jumping back to us.
      c->proc = p;
      switchuvm(p);
      p->state = RUNNING;

      swtch(&(c->scheduler), p->context);
      switchkvm();

      // Process is done running for now.
      // It should have changed its p->state before coming back.
      c->proc = 0;
    }
    release(&ptable.lock);

  }
}

scheduler関数は、プロセスの切り替えとハードウェアコンテキストの切り替えを行います。

処理の内容

cpu構造体が持つプロセスディスクリプタの参照をなくす

struct cpu *c = mycpu();
c->proc = 0;

mycpu関数を呼び出して、scheduler関数を現在実行しているプロセッサに対応したcpu構造体を取得します。
これから、プロセッサの実行している処理をカーネルスレッド(ユーザー空間を利用しないプロセス、あるいは、プロセスとして管理されていないカーネルプログラム)からプロセスに切り替えるので、cpu構造体が持つプロセスディスクリプタの参照を一度なくします。

無限ループ内でプロセスとハードウェアコンテキストの切り替え処理を行う

ハードウェア割り込みを有効化する

for(;;){
     sti();

sti関数を呼び出して、ハードウェア割り込みを有効化させます。

クリティカルセクションの入口を定める

acquire(&ptable.lock);

ptable(proc構造体の配列)を排他制御するために、acquire関数を呼び出してptable(proc構造体の配列)に関連しているロックを取得し、クリティカルセクションの入口とします。

プロセス切り替えとハードウェアコンテキストの切り替えを行う

for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
    ...

for文を使ってptableを走査し、RUNNABLE状態であるプロセス(p->state = = RUNNABLE が真 となるプロセスディスクリプタ)を探します。

プロセスの状態がRUNNABLE状態ではない場合
if(p->state != RUNNABLE)
        continue;

プロセスの状態がRUNNABLEではない場合は(p->state != RUNNABLE が真となるプロセスディスクリプタの場合は)、continueし、これ以降の処理を行わないようにします。

プロセスを参照できるようにする
c->proc = p;

RUNNABLE状態であるプロセス(p->state = = RUNNABLE が真 となるプロセスディスクリプタ)が見つかった場合は、cpu構造体がそのRUNNABLE状態であるプロセスに対応したプロセスディスクリプタを参照できるようにします(scheduler関数を実行しているプロセッサが、swtch(&(c->scheduler), p->context)の処理を完了させた後に、プロセスディスクリプタpに対応するプロセスを実行することを表現します)。

プロセスの切り替えを行う
switchuvm(p);

switchuvm関数を呼び出して、現在使用されているページディレクトリから、プロセスディスクリプタpに対応しているプロセス(ユーザプログラム)において使用されるページディレクトリへ、切り替えます。

プロセスの状態をRUNNING状態にする
p->state = RUNNING;
ハードウェアコンテキストの切り替えを行う
swtch(&(c->scheduler), p->context);

swtch(&(c->scheduler), p->context)の処理を完了すると、プロセッサの実行している処理が、カーネルスレッド(scheduler関数)から、プロセスディスクリプタpに対応するプロセスに切り替わります(swtchサブルーチンは、カーネルスレッドのハードウェアコンテキストをカーネルスレッドで使用されているスタックに退避させ、その時のスタックのトップアドレスをc->schedulerに格納します)。
プロセッサがカーネルスレッドの続き(scheduler関数の処理の続き)を再び実行するのは、プロセッサが切り替え先のプロセスでswtch(p->context, &(c->scheduler))を実行した後です。

カーネルスレッドが使用するぺージディレクトリに切り替える
switchkvm();

プロセッサがカーネルスレッドの続き(scheduler関数の処理の続き)を再び実行し始めたら、switchkvm関数を呼び出して、ユーザプロセスにおいて使用されていたページディレクトリから、カーネルスレッドにおいて使用されるぺージディレクトリへの切り替え処理を行います。

cpu構造体が持つプロセスディスクリプタの参照をなくす
c->proc = 0;

cpu構造体が持つプロセスディスクリプタの参照を一度なくします(scheduler関数を現在実行しているプロセッサが、プロセスを実行していないことを表現します)。

クリティカルセクションの出口とする

release(&ptable.lock);

release関数を呼び出してptable(proc構造体の配列)に関連しているロックを解放し、クリティカルセクションの出口とします。