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関数を呼び出して、タイマ割り込みのカウントに関わるロックを初期化しています。