minix2.0.0.のsmp実装の勉強

miniz2.0.0でsmpを実装した人がいるので、その人の資料を参考に読み進める。
#minix3.1.0に適用できそうなのか知りたいのでこちらの実装も合わせて見ていく。
もともとminixはmp対応してないので、APICとかの仕様も調べないといけない。

minix2.0.0との変更点はminix/com.h、minix/config.h、minix/const.h以外はすべてkernel/だけで済んでいる。
diffstatだとこんな感じに。

diffstat ../minixsmp/smp.diff 
 include/minix/com.h    |    4 
 include/minix/config.h |   27 -
 include/minix/const.h  |    3 
 src/kernel/Makefile    |   35 +
 src/kernel/README      |  148 +++++
 src/kernel/clock.c     |   33 -
 src/kernel/dmp.c       |   26 
 src/kernel/dp8390.c    |    2 
 src/kernel/driver.c    |    5 
 src/kernel/drvlib.c    |    4 
 src/kernel/exception.c |    6 
 src/kernel/glo.h       |    6 
 src/kernel/keyboard.c  |   22 
 src/kernel/main.c      |   21 
 src/kernel/mcd.c       | 1283 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/kernel/mp.c        |  955 ++++++++++++++++++++++++++++++++++++
 src/kernel/mp.h        |   69 ++
 src/kernel/mpinfo.c    |  181 ++++++
 src/kernel/mptable.h   |  169 ++++++
 src/kernel/mpx386.s    |  141 +++++
 src/kernel/proc.c      |  260 +++++++--
 src/kernel/proc.h      |   15 
 src/kernel/protect.c   |   24 
 src/kernel/protect.h   |   10 
 src/kernel/proto.h     |   10 
 src/kernel/system.c    |    2 
 src/kernel/table.c     |   14 
 src/kernel/tty.c       |    7 
 src/kernel/xmp.h       |  116 ++++
 src/kernel/xmp.s       |  362 +++++++++++++
 30 files changed, 3853 insertions(+), 107 deletions(-)

ヘッダファイルから変更点を見ていく。まずはcom.hから。

diff -Naur orig.usr/include/minix/com.h usr/include/minix/com.h
--- orig.usr/include/minix/com.h	2009-09-18 00:17:33.145769722 +0900
+++ usr/include/minix/com.h	2002-06-10 02:57:52.000000000 +0900
@@ -85,9 +85,9 @@
 #define WINCHESTER	(SYN_ALRM_TASK - ENABLE_WINI)
 				/* winchester (hard) disk class */
 
-#define SYN_ALRM_TASK     -8	/* task to send CLOCK_INT messages */
+#define SYN_ALRM_TASK     (-7 - MP_NR_CPUS)	/* task to send CLOCK_INT messages */
 
-#define IDLE              -7	/* task to run when there's nothing to run */
+#define IDLE(CPU)	  (-7 - (CPU))	/* task to run when there's nothing to run */
 
 #define PRINTER           -6	/* printer I/O class */

このファイルは2個の変更点があるけど、SYN_ALRM_TASKはminix3ではなくなってる。IDLEはsmp実装では引数を受けとるように変更されている。
もともとのmininxはユニプロセッサだからIDLE中のCPUは1個しかないけど、mp対応だとcpuによってタスクの状態が違うことがあるのでど
のcpuかを特定できるようにするためにはcpu番号を受け取る必要があると思う。

次はminix/config.h。こちらはいくつか差分があるのでちょっとずつみる。

diff -Naur orig.usr/include/minix/config.h usr/include/minix/config.h
--- orig.usr/include/minix/config.h	2009-09-18 00:17:39.045851244 +0900
+++ usr/include/minix/config.h	2002-08-29 20:03:19.000000000 +0900
@@ -66,6 +66,15 @@
 #define ALLOW_GAP_MESSAGES 1	/* proc.c - allow messages in the gap between
 				 * the end of bss and lowest stack address */
 
+/* Enable or disable multiprocessor suport (only for intel architecture), 
+   and configure number of processor (1 cpu implicit if MP not enabled) */
+#define ENABLE_MP	1	/* 1 enable, 0 disable */
+#define MP_NR_CPUS	2	/* ignored if ENABLE_MP is 0 */
+/* MP version, only for printing */
+#define MP_RELEASE	"1"
+#define MP_VERSION	"0"

あらたに4個のdefineが追加されてるけど、ここは見たままの通り。
cpuの数は静的に決めてるので、4cpuマシンならMP_NR_CPUSの値は4にする。

+
+/* Multiprocessor verifications */
+#if (CHIP != INTEL) && (ENABLE_MP == 1)
+error "ENABLE_MP: multiprocessor only avaliable for intel architecture"
+#endif
+
+#if (ENABLE_MP != 1) /* only 1 cpu if multiprocessor not enabled */
+#undef  MP_NR_CPUS
+#define MP_NR_CPUS 		1
+#endif

ここは単純にアーキテクチャintelじゃないとmp対応しないとか、mp対応しない場合のcpu数を定義してるだけ。

次はminix/const.h

diff -Naur orig.usr/include/minix/const.h usr/include/minix/const.h
--- orig.usr/include/minix/const.h	2009-09-18 00:17:42.573779963 +0900
+++ usr/include/minix/const.h	2002-06-10 02:25:28.000000000 +0900
@@ -52,7 +52,8 @@
 
 /* Number of tasks. */
 #define NR_TASKS	(9 + ENABLE_WINI + ENABLE_SCSI + ENABLE_CDROM \
-			+ ENABLE_NETWORKING + 2 * ENABLE_AUDIO)
+			+ ENABLE_NETWORKING + 2 * ENABLE_AUDIO + \
+			+ MP_NR_CPUS -1 )

const.hではNR_TASKSでcpu分のタスク数を追加する。minix3だとNR_TASKSはcom.hに移動している。

ここまでで、ヘッダファイルの変更点は見終わったので、後はkernel/以下のファイルを見ていく。
変更されたファイルは色々あるけど、main.cがminixカーネルのスタート地点なので、ここから見ていく。

diff -Naur orig.usr/src/kernel/main.c usr/src/kernel/main.c
--- orig.usr/src/kernel/main.c	2009-09-18 00:15:03.185886175 +0900
+++ usr/src/kernel/main.c	2002-09-21 02:56:05.000000000 +0900
@@ -14,7 +14,7 @@
 #include <minix/callnr.h>
 #include <minix/com.h>
 #include "proc.h"
-
+#include "mp.h"
 
 /*===========================================================================*
  *                                   main                                    *
@@ -33,6 +33,7 @@
   reg_t ktsb;			/* kernel task stack base */
   struct memory *memp;
   struct tasktab *ttp;
+  int cpu;
 
   /* Initialize the interrupt controller. */
   intr_init(1);
@@ -40,14 +41,19 @@
   /* Interpret memory sizes. */
   mem_init();
 
+
   /* Clear the process table.
    * Set up mappings for proc_addr() and proc_number() macros.
    */
   for (rp = BEG_PROC_ADDR, t = -NR_TASKS; rp < END_PROC_ADDR; ++rp, ++t) {
 	rp->p_flags = P_SLOT_FREE;
 	rp->p_nr = t;		/* proc number from ptr */
+	rp->p_currentcpu=NONE_CPU;
         (pproc_addr + NR_TASKS)[t] = rp;        /* proc ptr from number */
   }

int型のcpu変数はBSP(bootstrap processor)とかAP(application processors)のcpu番号を表すのにつかう。
rp->p_currentcpu=NONE_CPU; ←はプロセスに対してはcpu未割り当てという状態にしている。

NONE_CPUはproc.hで定義していて、値は-1。
proc.h:#define NONE_CPU -1 /* no cpu assigned to a process */

+  FOR_EACH_CPU(cpu)
+     proc_ptr[cpu] = bill_ptr[cpu] = proc_addr_IDLE[cpu] = proc_addr(IDLE(cpu));
+     /* it has to point somewhere */

単なる初期化。

   /* Set up proc table entries for tasks and servers.  The stacks of the
    * kernel tasks are initialized to an array in data space.  The stacks
@@ -116,9 +122,20 @@
   }
 
   proc[NR_TASKS+INIT_PROC_NR].p_pid = 1;/* INIT of course has pid 1 */
-  bill_ptr = proc_addr(IDLE);		/* it has to point somewhere */
+
+  FOR_EACH_CPU(cpu) cpu_available[cpu]=CPU_DISABLED;
+  enable_cpu(this_cpu, WITHOUT_ECHO);
+  /* For the moment only BSP is available */
+
   lock_pick_proc();

FOR_EACH_CPUはマクロで、mp.hで定義していてループを抽象化。

#define FOR_EACH_CPU(cpu)		for(cpu=0; cpu<MP_NR_CPUS; ++cpu)

ここの処理で出てくるcpu_availableはmp.hで以下のように定義しているcpuの配列。
配列のサイズはminix/config.hで決めたcpu数。
mp.h:extern int cpu_available[MP_NR_CPUS]; /* is this cpu running now? */
このループでenable_cpu()を使用して、MP_NR_CPUS数分のcpuを有効にする。
enable_cpu()の実装はmp.c。

+#if (ENABLE_MP == 1) 
+  /* Multiprocessor initialization */
+  lock_mp_kernel(); /* Make other CPUs to stop before enter the kernel */
+  mp_start();
+#endif
+
+
   /* Now go to the assembly code to start running the current process. */
   restart();
 }

main.cのmain()の最後はmpを有効にしているなら、mp_start()をコールしてMPのコンフィギュレーションを読んで、cpuが動くようにする。
mp_start()もmp.cに実装がある。