All Articles

xv6OSを真面目に読みこんでカーネルを完全に理解する -IOAPIC 編-

はじめての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.

outbx86.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

ここでは、0x210xA1という8259 PICに関連したポートをマスクしています。

8259 PICは、割込み要求を受け入れ、CPUに送信する割込みコントローラですが、SMPをサポートするxv6OSではAPICによる割込みを実装するため、無効化する必要があります。

ここで、0x210xA1は、どちらも8259 PICのデータポートになります。

以下のOSDevのWikiにある通り、ローカルAPICとIOAPICによる割込みを実装する場合は、これらのデータポートに0xFFを設定して8259 PICを無効化します。

参考:8259 PIC - OSDev Wiki

参考: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;の行では変数ioapicioapic構造体を設定しています。

IOAPIC0xFEC00000として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

2022/02/image-6.png

参考画像: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関数は、引数として受け取った値をioapicrebに書き込み、その後dataの値を取得して返却する関数です。

static uint ioapicread(int reg)
{
  ioapic->reg = reg;
  return ioapic->data;
}

IOAPICでは、ioapic->regがインデックスレジスタとして使用され、間接的にデータの読み書きを行うことができます。

インデックスレジスタとレジスタの対応は以下の通りです。

2022/02/image-5.png

参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet

参考:I/O APICについて - 睡分不足

maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;の行では、インデックスレジスタに1を指定してバージョン情報を取得しています。

$ x/5wx 0xfec00000
0xfec00000:	0x00000001	0x00000000	0x00000000	0x00000000
0xfec00010:	0x00170020

maxintrには0x17が入ります。

これはバージョンレジスタの23:16bitの範囲にあるMREを取得しているようです。

ここで取得できる情報は割込み入力PINの数から1を引いた数になるみたいです。(おそらくこれが定義できる割込みの最大数に?)

2022/02/image-7.png

参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet

次の行ではidとして0x0を取得しました。

これはIDレジスタの27:24bitの範囲の値でAPIC IDの値になります。(1つ目が0なのでたぶん初回実行時の値が0になっている?)

2022/02/image-8.png

参考画像: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をセットすることができます。

2022/02/image-9.png

参考画像:Intel 9 Series Chipset Platform Controller Hub Datasheet

このbitはMaskフラグになっており、セットされた場合割込みが送信されなくなります。

下位8bitに設定している値は割込みベクタです。

なんで初期化時に無効化してるんでしょうか。。

たぶん以降のコードを読めばわかると思うので引き続き進めていこうと思います。

まとめ

今回はpicinitioapicinitまで読み進めました。

まだ読んでいないmain関数で実行している関数も残り10個になり、折り返しに差し掛かってきました。

次回はconsoleinit関数を読んでいきます。

参考書籍