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ヘッダーを取得する
- マルチプロセッサ対応の有無を示すフラグをセットする
- 現在動作しているプロセッサのLocal APICレジスタ群のベースアドレスを取得する
- Base Configuration Tableを走査して各プロセッサが持つLocal APIC IDとI/O APICが持つI/O APIC IDを取得する
- マルチプロセッサ対応していない場合はメッセージを出力する
- IMCRについて
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関数を呼び出してメッセージを出力します。
現在動作しているプロセッサの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. }
わからないので割愛します。