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

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




main.c
https://github.com/mit-pdos/xv6-public/blob/master/main.c#L30

int
main(void)
{
  ...
  tvinit();        // trap vectors
  ...

trap.c
https://github.com/mit-pdos/xv6-public/blob/master/trap.c#L17

void
tvinit(void)
{
  int i;

  for(i = 0; i < 256; i++)
    SETGATE(idt[i], 0, SEG_KCODE<<3, vectors[i], 0);
  SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER);

  initlock(&tickslock, "time");
}

tvinitでは、IDT(割り込みディスクリプタテーブル)の各エントリを初期化しています。


割り込みハンドラ(または例外ハンドラ)について
割り込みベクタ番号0〜255までの割り込みハンドラ(または例外ハンドラ)は、vectors.Sファイル内に実装されています。
vectors.S

# handlers
 .globl alltraps
 .globl vector0
  vector0:
     pushl $0
     pushl $0
     jmp alltraps
...


vectors.Sファイルについて
vectors.Sはvectors.plのperlスクリプトを実行することにより作成されます。このことはMakefileから確認することができます。
Makefile
https://github.com/mit-pdos/xv6-public/blob/master/Makefile#L143

vectors.S: vectors.pl
                ./vectors.pl > vectors.S

また、vectors.Sはxv6のカーネルプログラム(kernel)に組み込まれていることも確認できます。
Makefile
https://github.com/mit-pdos/xv6-public/blob/master/Makefile#L28

OBJS = \
	bio.o\
	console.o\
	
        ...
	
        vectors.o\
	vm.o\

https://github.com/mit-pdos/xv6-public/blob/master/Makefile#L123

kernel: $(OBJS) entry.o entryother initcode kernel.ld
	$(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) -b binary initcode entryother
	$(OBJDUMP) -S kernel > kernel.asm
	$(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym


extern uint vectors[ ] について
extern uint vectorsは、vectors.Sファイル内にあるvectorsラベル(vectors:)のアドレスを参照しています。
trap.c
https://github.com/mit-pdos/xv6-public/blob/master/trap.c#L13

extern uint vectors[];  // in vectors.S: array of 256 entry pointers

vectors.S

#vector table
.data
.globl vectors
vectors:
.long vector0
.long vector1
.long vector2

....

.long vector255

vectorsラベルのアドレスは、32bit長のデータ領域256個(.long vector0、.long vector1、.long vector2、、、.long vector255)からなるvector tableの先頭アドレスです。このvector tableの各エントリには、割り込みハンドラ(または例外ハンドラ)のアドレスが格納されています(vector tableのi番目のエントリには、割り込みベクタ番号iに対応する割り込みハンドラ(または例外ハンドラ)のアドレスが格納されています)。vector tableは、リアルモードで使用される割り込みベクタテーブルや、プロテクトモードで使用される割り込みディスクリプタテーブルではないことに注意してください。
ソースコードにおいては、extern uint vectorsを利用してvector tableのi番目のエントリをvectors[i]として参照することで、割り込みベクタ番号iに対応する割り込みハンドラ(または例外ハンドラ)のアドレス(.long vector i)を取得することができるようになっています。


処理の内容

割り込みディスクリプタを初期化する

割り込みベクタ番号0〜255に対応する割り込みディスクリプタを初期化していきます。

割り込みディスクリプタテーブルの各エントリを割り込みゲートとして設定する

for(i = 0; i < 256; i++)
    SETGATE(idt[i], 0, SEG_KCODE<<3, vectors[i], 0);

SETGATEマクロを用いて、割り込みディスクリプタテーブル(struct gatedesc idt[256])の各エントリidt[i]を、割り込みゲートとして設定しています。

1つ目の引数(gate)には、設定対象となる割り込みディスクリプタidt[i]を設定します。
割り込みディスクリプタのデータ構造は、struct gatedesc構造体として表現されています。

2つ目の引数(istrap)には、割り込みディスクリプタを、トラップゲートとして設定するか・割り込みゲートとして設定するかを指定します。
ここでは、割り込みディスクリプタを割り込みゲートとして設定するので0を指定します。

3つ目の引数(sel)には、割り込みハンドラが格納されているコードセグメントのセレクタ値を指定します。
割り込みが発生した時のCPUの現行特権レベル(CPL = 0) が 割り込み・例外ハンドラがあるセグメントの特権レベル(DPL = 3) よりも高い場合、一般保護例外が発生してしまうので、カーネルコードセグメントのセレクタ値であるSEG_KCODE<<3を指定します。
セグメントディスクリプタテーブルにおける各エントリ(セグメントディスクリプタ)のインデックス値、SEG_KCODE 1 、SEG_KDATA 2、SEG_UCODE 3、SEG_UDATA 4を3bit左シフト演算することで(8倍することで)、各エントリについてのセレクタ値を得ることができます。

4つ目の引数(off)には、割り込みハンドラの先頭アドレスを指定します。
vectors[i]の値が、割り込みベクタ番号iに対応する割り込みハンドラ(または例外ハンドラ)の先頭アドレスです。

5つ目の引数(d)には、割り込みディスクリプタのアクセスに必要なCPUの特権レベルを指定するDPL(Descripter Priviledge Level)を設定します。ここでは、割り込みディスクリプタのアクセスに必要なCPUの特権レベルを0(カーネルモード)に指定するので、DPL(Descripter Priviledge Level)を0に指定します。

割り込みディスクリプタテーブルのエントリ64だけをトラップゲートとして設定する

SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER);

SETGATEマクロを用いて、全てのエントリを割り込みゲートとして設定した割り込みディスクリプタテーブル(struct gatedesc idt[256])の内、エントリ64(T_SYSCALL)の割り込みディスクリプタをトラップゲートとして再設定します。
割り込みベクタ番号64(エントリ64(T_SYSCALL)の割り込みディスクリプタ)は、システムコールのために使用します。

1つ目の引数(gate)には、設定対象となる割り込みディスクリプタidt[T_SYSCALL]を設定します。

2つ目の引数(istrap)には、割り込みディスクリプタを、トラップゲートとして設定するか・割り込みゲートとして設定するかを指定します。
ここでは、割り込みディスクリプタをトラップゲートとして設定するので1を指定します。

3つ目の引数(sel)には、割り込みハンドラが格納されているコードセグメントのセレクタ値を指定します。
割り込みが発生した時のCPUの現行特権レベル(CPL = 0) が 割り込み・例外ハンドラがあるセグメントの特権レベル(DPL = 3) よりも高い場合、一般保護例外が発生してしまうので、カーネルコードセグメントのセレクタ値であるSEG_KCODE<<3を指定します。
セグメントディスクリプタテーブルにおける各エントリ(セグメントディスクリプタ)のインデックス値、SEG_KCODE 1 、SEG_KDATA 2、SEG_UCODE 3、SEG_UDATA 4を3bit左シフト演算することで(8倍することで)、各エントリについてのセレクタ値を得ることができます。

4つ目の引数(off)には、例外ハンドラの先頭アドレスを指定します。
vectors[T_SYSCALL]の値が、割り込みベクタ番号iに対応する例外ハンドラの先頭アドレスです。

5つ目の引数(d)には、割り込みディスクリプタのアクセスに必要なCPUの特権レベル指定するDPL(Descripter Priviledge Level)を設定します。
システムコールはユーザモードでも実行できる必要があるので、割り込みディスクリプタのアクセスに必要なCPUの特権レベルを3(ユーザモード)にするためにDPL(Descripter Priviledge Level)を3(DPL_USER)に設定します。

タイマ割り込みのカウントに関わるロックを初期化する

initlock(&tickslock, "time");

initlock関数を呼び出して、タイマ割り込みのカウントに関わるロックを初期化しています。




次回
jupiteroak.hatenablog.com