〆(.. )カリカリッ!! Minix3のpick_proc()

たまに気になるMinixカーネルの仕組みということでプロセス切り替えの仕組みというか次に動くプロセスを選択する仕組みであるpick_proc()を読んでみる。
読んでいるソースは2013/05/04時点でのgitツリーなので将来的には変更があるかも。

s/2014/2013/ だった\(^o^)/

ファイルはkernel/proc.c

1706 /*===========================================================================*
1707  *                              pick_proc                                    * 
1708  *===========================================================================*/
1709 static struct proc * pick_proc(void)
1710 {
1711 /* Decide who to run now.  A new process is selected an returned.
1712  * When a billable process is selected, record it in 'bill_ptr', so that the 
1713  * clock task can tell who to bill for system time.
1714  *
1715  * This function always uses the run queues of the local cpu!
1716  */
1717   register struct proc *rp;                     /* process to run */
1718   struct proc **rdy_head;
1719   int q;                                /* iterate over queues */
1720 
1721   /* Check each of the scheduling queues for ready processes. The number of
1722    * queues is defined in proc.h, and priorities are set in the task table.
1723    * If there are no processes ready to run, return NULL.
1724    */
1725   rdy_head = get_cpulocal_var(run_q_head);
1726   for (q=0; q < NR_SCHED_QUEUES; q++) { 
1727         if(!(rp = rdy_head[q])) {
1728                 TRACE(VF_PICKPROC, printf("cpu %d queue %d empty\n", cpuid, q););
1729                 continue;
1730         }
1731         assert(proc_is_runnable(rp));
1732         if (priv(rp)->s_flags & BILLABLE)               
1733                 get_cpulocal_var(bill_ptr) = rp; /* bill for system time */
1734         return rp;
1735   }
1736   return NULL;
1737 }

ここでの前提条件は今のMinixはSMPに対応していて、データ構造もpercpuの仕組みが取り入れられているということ。この辺りの話は前にカーネル/VM Advent Calendarで書いたところです。

まずはランキューを取得するところ。

1725   rdy_head = get_cpulocal_var(run_q_head);

get_cpulocal_var()はcpuごとのランキューを取得するということですが、このランキューの構造はというとkernel/cpulocals.hで以下のように定義されていて、proc構造体の配列でサイズはNR_SCHED_QUEUESとなってます。

  71 /* CPU private run queues */
  72 DECLARE_CPULOCAL(struct proc *, run_q_head[NR_SCHED_QUEUES]); /* ptrs to ready list headers */
  73 DECLARE_CPULOCAL(struct proc *, run_q_tail[NR_SCHED_QUEUES]); /* ptrs to ready list tails */
  74 DECLARE_CPULOCAL(volatile int, cpu_is_idle); /* let the others know that you are idle */

NR_SCHED_QUEUES等の定義はinclude/minix/config.hに。

  68 /* Scheduling priorities. Values must start at zero (highest
  69  * priority) and increment.
  70  */
  71 #define NR_SCHED_QUEUES   16    /* MUST equal minimum priority + 1 */
  72 #define TASK_Q             0    /* highest, used for kernel tasks */
  73 #define MAX_USER_Q         0    /* highest priority for user processes */   
  74 #define USER_Q            ((MIN_USER_Q - MAX_USER_Q) / 2 + MAX_USER_Q) /* default
  75                                                 (should correspond to nice 0) */
  76 #define MIN_USER_Q        (NR_SCHED_QUEUES - 1) /* minimum priority for user
  77                                                    processes */

ここまで読むと、ランキューのサイズは16で、配列のインデックスはスケジューリング優先度とイコールとなっているのがわかりますね。。優先度は0~15で0が最優先。優先度0のタスクにアクセスするにはrun_q_head[0]という感じ。
run_q_headはproc構造体の配列なので優先度が同じプロセスが複数ある場合はランキューはどうなるのか?という疑問が出ますよね。kernel/proc.hによると優先度が同じプロセスはproc構造体のp_nextreadyを使ってリスト構造を作っているようです。

  67   struct proc *p_nextready;     /* pointer to next ready process */
  68   struct proc *p_caller_q;      /* head of list of procs wishing to send */
  69   struct proc *p_q_link;        /* link to next proc wishing to send */
  70   endpoint_t p_getfrom_e;       /* from whom does process want to receive? */
  71   endpoint_t p_sendto_e;        /* to whom does process want to send? */

ランキューのrun_q_head[n]でアクセスできるproc構造体はその優先度の中で最優先なプロセスというところでしょうか。

カレントcpuのランキューが取得できたらランキューを優先度の高い順から調べます。

1726   for (q=0; q < NR_SCHED_QUEUES; q++) { 

チェック対象のランキューにプロセスがいなければ次の優先度へ。

1727         if(!(rp = rdy_head[q])) {
1728                 TRACE(VF_PICKPROC, printf("cpu %d queue %d empty\n", cpuid, q););
1729                 continue;
1730         }

1727行目のif文でプロセスが見つかったらこのプロセスが次に動くプロセスになります。
プロセスが見つかったけどプロセスの状態チェックを入れます。プロセスが実行状態じゃないのにここに来たらバグですね。

1731         assert(proc_is_runnable(rp));

次のチェックですが、プロセスにBILLABLEビットが立っていたらrpをbill_ptrに代入します。

1732         if (priv(rp)->s_flags & BILLABLE)               
1733                 get_cpulocal_var(bill_ptr) = rp; /* bill for system time */
1734         return rp;

bill_ptrはpercpuなproc構造体です。なのでkernel/cpulocals.hに定義があります。

  55 DECLARE_CPULOCAL(struct proc *,bill_ptr);/* process to bill for clock ticks */

1732、1733行目はcpu利用時間をプロセスに足す必要があるプロセス(普通のユーザプロセス)の場合の処理という感じですかね。
そして最後に実行対象のプロセスを返して終了です。

もし実行対象のプロセスが全くなければNULLを返しています。