minixsmp

minixsmpの実装勉強はmp.cに入っていく。

まず最初は、enable_cpu()。

PUBLIC void enable_cpu(int cpu, int echo) {
/* Mark a CPU as usable, enables it, and open interrupts.
   If echo is nonzero, print a message of successfully enabling */

   if (cpu_available[cpu]==CPU_ENABLED) return;

   /* A CPU only can only enable its own APIC */
   if (cpu==this_cpu) {
      enable_apic_ints();
      cpu_available[cpu]=CPU_ENABLED;
      lock_pick_proc();
      if (echo) printk("CPU%d enabled\n",cpu);
   }
   else {
      interrupt_cpu(cpu,MP_CM_ENABLE,echo);
   }
}

関数宣言についてるPUBLICはminixのコーディング作法に則っているから。
minix/const.hでこんな風に定義。ようは外部から呼ばれる関数はPUBLICをつけて、staticな関数はPRIVATEをつける。

#define PUBLIC                  /* PUBLIC is the opposite of PRIVATE */
#define FORWARD       static    /* some compilers require this to be 'static'*/

この関数の最初の処理は引数で渡されたcpuがenableかどうかチェックして、すでにenableなら何もせずにreturnする。

次の処理は引数のcpuが今動いているcpuかどうかチェックする。

if (cpu==this_cpu) {

this_cpuはxmp.hで定義している。

/* If MP is not enabled, cpu# is 0, else it's neccessary ask the APIC */
#if (ENABLE_MP == 1) 
#define this_cpu         f_this_cpu()
#else
#define this_cpu         0
#endif

ここで、this_cpuはf_this_cpu()の置き換えてることがわかり、さらにこれの本体はxmp.sにある_f_this_cpu()になっている。

_f_this_cpu()はカレントのcpu番号を返す。

! Returns APIC id of curret cpu (0..n-1)
_f_this_cpu:
        o16     push    ds
                push    edx
                THIS_CPU_SHORT(eax)
                pop     edx
        o16     pop     ds
                ret

push、popはお約束的な処理なので、この処理の重要箇所はTHIS_CPU_SHORTにある。
THIS_CPU_SHORTは関数っぽいけど実はマクロでxmp.hにいる。

/* This secuence provides in <reg> current cpu number from 0 to n-1
   Registers <reg>, EDX, DS are afected 
   SHORT version does not restores DS_SELECTOR in DS 
   <reg> can be EAX, EBX, ECX */

#define THIS_CPU_SHORT(reg)                             ;\
                mov     edx,    (_local_apic_base)      ;\
                add     edx,    0x20                    ;\
                mov     reg,    FLAT_DS_SELECTOR        ;\
                mov     ds,     reg                     ;\
                mov     reg,    (edx)                   ;\
                and     reg,    0x0F000000              ;\
                shr     reg,    6*4

_local_apic_baseはきっとmp.cにあるこれのこと。

u32_t local_apic_base=0x0FEE00000;

APICで使用するメモリに関する仕様から、APICのデフォルトベースアドレス0FEC0_0000hと0FEE0_0000hとなってるので、_local_apic_baseの値は0x0FEE00000になっているはず。

インテルのMPの仕様書はここに。
http://www.intel.com/design/archives/processors/pro/docs/242016.htm

これでやっと、カレントのcpu番号を得ることができたので、if文で分岐判定ができる。
cpuがカレントのcpuと同じだった時からみていくと、

   /* A CPU only can only enable its own APIC */
   if (cpu==this_cpu) {
      enable_apic_ints();
      cpu_available[cpu]=CPU_ENABLED;
      lock_pick_proc();
      if (echo) printk("CPU%d enabled\n",cpu);
   }

最初の仕事はenable_apic_ints()のコール。名前から何かを有効にしてるんだろうとは想像がつく。
intsは何の略かというとinterruptsの略。
実装はこれだけど、やってることはメモリから値を読んだり、書き込んだりしているだけ。
LOCAL_APIC_XXXXの実装はxmp.sにいる。

void enable_apic_ints(void) {
/* Enable current APIC and open to interrputs from PIC */
   u32_t reg;

   reg = LOCAL_APIC_READ(LOCAL_APIC_SPIV);
   reg |= (1<<ENABLE_APIC_SHIFT);               /* Enable APIC */
   LOCAL_APIC_WRITE(LOCAL_APIC_SPIV, reg); 

   reg = LOCAL_APIC_READ(LOCAL_APIC_LVTL0);
   reg &= ~(7<<LVT_DM_SHIFT);                   /* clear delivery mode */
   reg &= ~(1<<LVT_MASKED_SHIFT);               /* unmask LINTIN0 */
   reg |= (LVT_DM_EXTINT<<LVT_DM_SHIFT);        /* ExtINT at LINTINT0 */
   LOCAL_APIC_WRITE(LOCAL_APIC_LVTL0, reg);

   reg = LOCAL_APIC_READ(LOCAL_APIC_LVTL1);
   reg &= ~(7<<LVT_DM_SHIFT);                   /* clear delivery mode */
   reg &= ~(1<<LVT_MASKED_SHIFT);               /* ummask LINTIN1 */
   reg |= (LVT_DM_NMI<<LVT_DM_SHIFT);           /* NMI at LINTINT1 */
   LOCAL_APIC_WRITE(LOCAL_APIC_LVTL1, reg);

}

#今のところはmp実装の概要を掴みたいので、各ビット演算で使ってる値とか気にしない。
割り込みを有効にしたら、cpuの状態をCPU_ENABLEDにする。

次に呼ばれるlock_pick_proc()はその名の通りロックをする。
何に対してロックをするかとういとproc.cにあるpick_proc()の処理を安全に行うため。

PUBLIC void lock_pick_proc()
{
/* Safe gateway to pick_proc() for tasks. */

  mp_switching_lock();
  pick_proc();
  mp_switching_unlock();
}

pick_proc()はminixカーネルが次に動かすプロセスを決めるための処理。pick_proc()もmpに対応するために変更が入っているけど今はそこまで追わない。
#pick_proc()はminix2とminix3で実装が変わっている。

次に、cpuがカレントのcpuじゃなかった場合は、interrupt_cpu()をコールするだけ。
これもmp.c内にいる。

void interrupt_cpu(int cpu, int command, int param) {
/* Send a message to a CPU consisting in a command and an optional parameter */

   mp_command[cpu]=command;
   mp_param[cpu]=param;
   forward_vector_to_cpu(VECTOR(16),cpu);
}

mp_command、mp_paramはmp.cの上のほうで定義。

int mp_command[MP_NR_CPUS];     /* command for cpu to execute in IPI */
int mp_param[MP_NR_CPUS];       /* paremeter of IPI command */

IPIって何?
intelのドキュメントによるとinterpurocessor interruptsの略っぽい。他のcpuとかプロセスに割り込めるかどうかってことを許可したりするらしい。

そして、forward_vector_to_cpu()なんだけど、これもメモリ読んだり、書いたりが主なので深追いはしない。

void forward_vector_to_cpu(u8_t vector, int cpu) {
/* Send a interrupt vector to a CPU */
   u32_t icr_h,icr_l;

   while (DELIVERY_STATUS); 
   /* Wait for local APIC to complete previous IPI */
   
   /* prepare to send IPI */
   icr_h = LOCAL_APIC_READ(LOCAL_APIC_ICR_HIGH);
   icr_l = LOCAL_APIC_READ(LOCAL_APIC_ICR_LOW);
 
   icr_l &=~0x000CDFFF;  /* clear non-reserved fields */
   icr_h &= 0x00FFFFFF;  /* clear non-reserved fields */

   /* Next inetructions does nothig because the values are all 0's,
      so, skip it for speed */
 /*icr_l |= (DELIVERY_FIXED<<DELIVERY_SHIFT);*/ /* stablish FIXED (000) */
 /*icr_l &= ~(1 << TRIGGER_SHIFT);*/            /* trigger = edge */
 /*icr_l |= (PHYSICAL_DEST << DEST_MODE_SHIFT);*/       /* destination = physical */
 /*icr_l |= (DEST_FIELD << DEST_SHORT_SHIFT);*/ /* destination by field */

   icr_l |= vector;                             /* vector field */
   icr_h |= (cpu << DEST_FIELD_SHIFT);          /* cpu to interrupt */

   /* Clear previous error???? We dont hand it, so skip for speed */
   /*apic_error_status();*/

   /* send the IPI */
   LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h);
   LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW,  icr_l);
}

ここまでで、mp環境で各cpuを有効にすることができているはず。