はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみにインスパイアされてxv6 OSを読んでます。
UNIX V6自体はx86CPUでは動作しないため、基本的には、UNIXv6をX86アーキテクチャで動くようにしたxv6 OSのリポジトリをForkしたkash1064/xv6-public: xv6 OSのソースコードを読んでいくことにしました。
前回はmain
関数で実行されるseginit
関数を確認しました。
今回はpicinit
関数とioapicinit
関数の挙動を追っていきます。
もくじ
picinit関数
picinit
関数はpicirq.c
で定義された関数です。
以下の通りかなり小さい関数です。
#include "types.h"
#include "x86.h"
#include "traps.h"
// I/O Addresses of the two programmable interrupt controllers
#define IO_PIC1 0x20 // Master (IRQs 0-7)
#define IO_PIC2 0xA0 // Slave (IRQs 8-15)
// Don't use the 8259A interrupt controllers. Xv6 assumes SMP hardware.
void picinit(void)
{
// mask all interrupts
outb(IO_PIC1+1, 0xFF);
outb(IO_PIC2+1, 0xFF);
}
//PAGEBREAK!
// Blank page.
outb
はx86.h
で定義された以下の関数です。
static inline void outb(ushort port, uchar data)
{
asm volatile("out %0,%1" : : "a" (data), "d" (port));
}
任意のポートにデータを送信するアセンブリを発行します。
参考:assembly - what does “outb” in AT&T asm mean? - Stack Overflow
ここでは、0x21
と0xA1
という8259 PIC
に関連したポートをマスクしています。
8259 PIC
は、割込み要求を受け入れ、CPUに送信する割込みコントローラですが、SMPをサポートするxv6OSではAPICによる割込みを実装するため、無効化する必要があります。
ここで、0x21
と0xA1
は、どちらも8259 PIC
のデータポートになります。
以下のOSDevのWikiにある通り、ローカルAPICとIOAPICによる割込みを実装する場合は、これらのデータポートに0xFF
を設定して8259 PIC
を無効化します。
参考:assembly - I/O Port Addressing - Stack Overflow
ioapicinit関数
picinit
関数の処理が完了し、PICが無効化されました。
続いて、ioapic.c
で定義されたioapicinit
関数がmain
関数から呼び出されます。
この関数では、IOAPICの初期化を行います。
void ioapicinit(void)
{
int i, id, maxintr;
ioapic = (volatile struct ioapic*)IOAPIC;
maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;
id = ioapicread(REG_ID) >> 24;
if(id != ioapicid) cprintf("ioapicinit: id isn't equal to ioapicid; not a MP\n");
// Mark all interrupts edge-triggered, active high, disabled,
// and not routed to any CPUs.
for(i = 0; i <= maxintr; i++){
ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i));
ioapicwrite(REG_TABLE+2*i+1, 0);
}
}
コードを順番に見ていきます。
メモリマップされたIOAPICレジスタ領域の参照
ioapic = (volatile struct ioapic*)IOAPIC;
の行では変数ioapic
にioapic
構造体を設定しています。
IOAPIC
は0xFEC00000
としてioapic.c
で定義されています。
// ioapic.c
#define IOAPIC 0xFEC00000 // Default physical address of IO APIC
IOAPICのアドレスはデフォルトで0xFEC00000
になることが保証されているため、このアドレスを固定値として指定しています。
参考:x86 - About the IO-APIC 82093AA - Stack Overflow
ioapic
構造体は、以下のように非常にシンプルな構造になっています。
// IO APIC MMIO structure: write reg, then read or write data.
struct ioapic {
uint reg;
uint pad[3];
uint data;
};
参考:Memory Mapped Registers for Accessing IOAPIC Registers
参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet
最初のreg
がインデックスとして使用され、data
の領域を利用して読み書きを行われます。
実際に起動時にioapic
の領域に格納される値がどうなっているのか気になったのでデバッガで確認してみました。
0xFEC00000
から20バイト分の領域を見てみましたが、この時点ではすべて0でした。
$ x/5wx 0xfec00000
0xfec00000: 0x00000000 0x00000000 0x00000000 0x00000000
0xfec00010: 0x00000000
Dataレジスタ経由でデータの読み書きを行う
続いて以下のコードを見ます。
maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;
id = ioapicread(REG_ID) >> 24;
if(id != ioapicid) cprintf("ioapicinit: id isn't equal to ioapicid; not a MP\n");
ioapicread
関数は、引数として受け取った値をioapic
のreb
に書き込み、その後data
の値を取得して返却する関数です。
static uint ioapicread(int reg)
{
ioapic->reg = reg;
return ioapic->data;
}
IOAPICでは、ioapic->reg
がインデックスレジスタとして使用され、間接的にデータの読み書きを行うことができます。
インデックスレジスタとレジスタの対応は以下の通りです。
参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet
maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;
の行では、インデックスレジスタに1を指定してバージョン情報を取得しています。
$ x/5wx 0xfec00000
0xfec00000: 0x00000001 0x00000000 0x00000000 0x00000000
0xfec00010: 0x00170020
maxintr
には0x17
が入ります。
これはバージョンレジスタの23:16bitの範囲にあるMREを取得しているようです。
ここで取得できる情報は割込み入力PINの数から1を引いた数になるみたいです。(おそらくこれが定義できる割込みの最大数に?)
参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet
次の行ではid
として0x0
を取得しました。
これはIDレジスタの27:24bitの範囲の値でAPIC IDの値になります。(1つ目が0なのでたぶん初回実行時の値が0になっている?)
参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet
この値がioapicid
と一致するかを検証しています。
ioapicid
はマルチプロセッサ編で見た通り、MPコンフィグレーションテーブルから取得したAPICの番号でした。
Redirection Tableの初期化
最後に、ioapicwrite
関数で全てのRedirection Table
を無効化しています。
// Mark all interrupts edge-triggered, active high, disabled,
// and not routed to any CPUs.
for(i = 0; i <= maxintr; i++){
ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i));
ioapicwrite(REG_TABLE+2*i+1, 0);
}
ioapicwrite
関数は以下のようにインデックスと書き込みデータを使用して書き込みを行う関数です。
static void ioapicwrite(int reg, uint data)
{
ioapic->reg = reg;
ioapic->data = data;
}
INT_DISABLED
は、0x00010000
として定義されています。
これによって、17番目のbitに1をセットすることができます。
参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet
このbitはMaskフラグになっており、セットされた場合割込みが送信されなくなります。
下位8bitに設定している値は割込みベクタです。
なんで初期化時に無効化してるんでしょうか。。
たぶん以降のコードを読めばわかると思うので引き続き進めていこうと思います。
まとめ
今回はpicinit
とioapicinit
まで読み進めました。
まだ読んでいないmain
関数で実行している関数も残り10個になり、折り返しに差し掛かってきました。
次回はconsoleinit
関数を読んでいきます。