OS起動編③ mpinit() (Xv6を読む~OSコードリーディング~)

前回
jupiteroak.hatenablog.com
トップページ
jupiteroak.hatenablog.com




main.c(一部抜粋)
https://github.com/mit-pdos/xv6-public/blob/master/main.c#L22

int
main(void)
{
  ...
  mpinit();        // detect other processors
  ...

mp.c
https://github.com/mit-pdos/xv6-public/blob/master/mp.c#L91

void
mpinit(void)
{
  uchar *p, *e;
  int ismp;
  struct mp *mp;
  struct mpconf *conf;
  struct mpproc *proc;
  struct mpioapic *ioapic;

  if((conf = mpconfig(&mp)) == 0)
    panic("Expect to run on an SMP");
  ismp = 1;
  lapic = (uint*)conf->lapicaddr;
  for(p=(uchar*)(conf+1), e=(uchar*)conf+conf->length; p<e; ){
    switch(*p){
    case MPPROC:
      proc = (struct mpproc*)p;
      if(ncpu < NCPU) {
        cpus[ncpu].apicid = proc->apicid;  // apicid may differ from ncpu
        ncpu++;
      }
      p += sizeof(struct mpproc);
      continue;
    case MPIOAPIC:
      ioapic = (struct mpioapic*)p;
      ioapicid = ioapic->apicno;
      p += sizeof(struct mpioapic);
      continue;
    case MPBUS:
    case MPIOINTR:
    case MPLINTR:
      p += 8;
      continue;
    default:
      ismp = 0;
      break;
    }
  }
  if(!ismp)
    panic("Didn't find a suitable machine");

  if(mp->imcrp){
    // Bochs doesn't support IMCR, so this doesn't run on Bochs.
    // But it would on real hardware.
    outb(0x22, 0x70);   // Select IMCR
    outb(0x23, inb(0x23) | 1);  // Mask external interrupts.
  }
}

mpinit関数では、MP configuration tableというデータ構造から、システム上にある各プロセッサを識別するために使用するLocal APIC ID と システム上にあるI/O APICを識別するために使用するI/O APIC IDを取得しています。


処理の内容

MP configuration tableヘッダーを取得する

if((conf = mpconfig(&mp)) == 0)
    panic("Expect to run on an SMP");

mpconfig関数を呼び出して、MP configuration tableヘッダーを取得します。
(conf = mpconfig(&mp)) == 0 が偽となる場合 → mpconfig関数の戻り値がMP configuration tableヘッダーの先頭アドレスとなる場合 → MP configuration tableヘッダーの取得に成功した場合は、mpconf構造体(MP configuration tableヘッダーを表現した構造体)へのポインタconfにMP configuration tableヘッダーの先頭アドレスが格納されます。
またこの時、引数&mpが指定するmp構造体(MP Floating Pointer Structureを表現した構造体)へのポインタmpに、MP Floating Pointer Structureの先頭アドレスが格納されます。
(conf = mpconfig(&mp)) == 0 が真となる場合 → mpconfig関数の戻り値が0となる場合 → MP configuration tableヘッダーの取得に失敗した場合は、panic関数を呼び出してメッセージを出力します。

マルチプロセッサ対応の有無を示すフラグをセットする

ismp = 1;

マルチプロセッサ対応の有無を示すフラグに1を設定しておきます。

現在動作しているプロセッサのLocal APICレジスタ群のベースアドレスを取得する

lapic = (uint*)conf->lapicaddr;

現在動作しているプロセッサに内蔵されているLocal APICレジスタのベースアドレスの値を変数lapic(https://github.com/mit-pdos/xv6-public/blob/master/defs.h#L78)に格納します。
Local APICレジスタのベースアドレスの値は、conf->lapicaddr(MP configuration tableヘッダーのADDRESS OF LOCAL APICフィールドの値)に設定されています。

Base Configuration Tableを走査して各プロセッサが持つLocal APIC IDとI/O APICが持つI/O APIC IDを取得する

for(p=(uchar*)(conf+1), e=(uchar*)conf+conf->length; p<e; ){
    
   ...

MP configuration tableヘッダーの直後に位置するBase Configuration Tableの先頭(conf+1)から、Base Configuration Tableの終端(conf+conf->length-1)まで、1つずつエントリを走査していき、Processor Entry(システム上に存在するプロセッサごとに用意されている)からLocal APIC IDを、I/O APIC Entry(システム上に存在するI/O APICごとに用意されている)からI/O APIC IDを取得します。


p=(uchar*)(conf+1)
conf+1は、Configuration Tableヘッダーの先頭アドレス(conf)をConfiguration Tableヘッダーのサイズ分インクリメントしたアドレス値です。よって、conf+1はMP configuration tableヘッダーの直後に位置するBase Configuration Tableの先頭アドレスとなります。


e=(uchar*)conf+conf->length
conf->lengthは、Configuration TableヘッダーのBASE TABLE LENGTHフィールドの値で、base sectionのサイズ(MP configuration tableヘッダーとBase Configuration Tableの合計サイズ)をバイト単位で示したものです。よって、conf+conf->lengthは、Base Configuration Tableの終端アドレス+1となります(conf+conf->length-1は、Base Configuration Tableの終端アドレスとなります)。

Base Configuration TableにおけるエントリがProcessor Entryである場合

switch(*p){
    case MPPROC:
      proc = (struct mpproc*)p;
      if(ncpu < NCPU) {
        cpus[ncpu].apicid = proc->apicid;  // apicid may differ from ncpu
        ncpu++;
      }
      p += sizeof(struct mpproc);
      continue;
    ...

Base Configuration Tableのエントリの先頭1バイトの値(EntryTypeの値)を調べることで、そのエントリがProcessor Entryであるかを識別することができます。そのために、for文の初期設定式でBase Configuration Tableのエントリの先頭アドレスconf+1をuchar型(1バイトサイズ)へのポインタに格納し(p=(uchar*)(conf+1))、switch文の制御式でBase Configuration Tableのエントリの先頭1バイトの値*p(EntryTypeの値)を評価しています。
*pの値がMPPROC(0x00)である場合 → Base Configuration TableにおけるエントリがProcessor Entryである場合は、case MPPROC 以下の処理に入ります。


proc = (struct mpproc*)p;
Processor Entryの先頭アドレスをmpproc構造体(Processor Entryを表現した構造体)へのポインタprocに格納します。


if(ncpu < NCPU) {
    cpus[ncpu].apicid = proc->apicid;
     ncpu++;
}
Processor Entryを取得した順番で、Local APIC IDの値(Processor EntryのLOCAL APIC IDフィールドproc->apicidの値)をcpu構造体のapicidメンバに保存し、cpu構造体配列のインデックス値であるncpuをインクリメントします。
NCPU(8)で指定された個数以下のcpu構造体(プロセッサ)について、この処理を行います。
cpu構造体はプロセッサに関する情報を管理するためのデータ構造で、システム上にあるプロセッサ1つに対してcpu構造体が1つ用意されています。
Local APIC IDはシステム上にあるプロセッサを識別するために使用されます。


p += sizeof(struct mpproc);
        continue; 
Base Configuration TableのエントリはEntryTypeの値の昇順で並んでいるので、次のループでもProcessor Entryが取得できることを期待して、mpproc構造体(Processor Entryを表現した構造体)のサイズ分、アドレス値をインクリメントし、ループを継続します

Base Configuration TableにおけるエントリがI/O APIC Entryである場合

 switch(*p){
    ...
    case MPIOAPIC:
      ioapic = (struct mpioapic*)p;
      ioapicid = ioapic->apicno;
      p += sizeof(struct mpioapic);
      continue;
   ...

Base Configuration Tableのエントリの先頭1バイトの値(EntryTypeの値)を調べることで、そのエントリがI/O APIC Entryであるかを識別することができます。そのために、for文の初期設定式でBase Configuration Tableにおけるエントリの先頭アドレスconf+1をuchar型(1バイトサイズ)へのポインタに格納し(p=(uchar*)(conf+1))、switch文の制御式でBase Configuration Tableにおけるエントリの先頭1バイトの値*p(EntryTypeの値)を評価しています。
*pの値がMPIOAPIC(0x02)である場合→Base Configuration TableにおけるエントリがI/O APIC Entryである場合は、case MPIOAPIC以下の処理に入ります。


ioapic = (struct mpioapic*)p;
I/O APIC Entryの先頭アドレスをmpioapic構造体(I/O APIC Entryを表現した構造体)へのポインタioapic に格納します。


ioapicid = ioapic->apicno;
I/O APICのIDの値(I/O APIC EntryのI/O APIC IDフィールドの値 ioapic->apicno)を変数ioapicidに保存しておきます。


p += sizeof(struct mpioapic);
        continue;
Base Configuration Tableのエントリは、EntryType値の昇順で並んでいるので、次のループでも、I/O APIC Entryが取得できることを期待して、mpioapic構造体(I/O APIC Entryを表現した構造体)のサイズ分、アドレス値をインクリメントし、ループを継続します。

Base Configuration TableにおけるエントリがBus Entry、I/O Interrupt Assignment Entry、Local Interrupt Assignment Entryのいずれかである場合

    switch(*p){
    ...
    case MPBUS:
    case MPIOINTR:
    case MPLINTR:
      p += 8;
      continue;
    ...

*pの値が、MPBUS(0x01)、MPIOINTR(0x03)、MPLINTR(0x04)、のいずれれかである場合→Base Configuration TableにおけるエントリがBus Entry、I/O Interrupt Assignment Entry、Local Interrupt Assignment Entryのいずれかである場合は、アドレス値pを8バイト分(Bus Entry、I/O Interrupt Assignment Entry、Local Interrupt Assignment Entryのサイズは、全て8バイト)インクリメントして、ループを継続します。

エントリが存在しない場合

 switch(*p){
     ...
    default:
      ismp = 0;
      break;
    }

エントリが存在しない場合は、マルチプロセッサ対応していないと見なしてismpに0を設定し、ループから脱出します。

マルチプロセッサ対応していない場合はメッセージを出力する

if(!ismp)
    panic("Didn't find a suitable machine");

!ismpが真となる場合→ismpに0が設定されている場合→マルチプロセッサ対応していない場合は、panic関数を呼び出してメッセージを出力します。
 

IMCRについて

if(mp->imcrp){

    outb(0x22, 0x70); // Select IMCR 
    outb(0x23, inb(0x23) | 1); // Mask external interrupts.
} 

わからないので割愛します。




次回
jupiteroak.hatenablog.com