プロセス1実行編④(システムコール) vectors.S (Xv6を読む~OSコードリーディング~)

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




vectors.S

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

...

.long vector64

...

.long vector255
# generated by vectors.pl - do not edit
# handlers
.globl alltraps

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

.globl vector1
vector1:
pushl $0
pushl $1
jmp alltraps

.globl vector2
vector2:
pushl $0
pushl $2
jmp alltraps

...

.globl vector64
vector64:
pushl $0
pushl $64
jmp alltraps

...

.globl vector255
vector255:
pushl $01
pushl $255
jmp alltraps

int T_SYSCALL(int 0x40)命令を実行するとソフトウェア割り込みが生じ、IDT(割り込みディスクリプタテーブル)にあるインデックス0x40(64)のエントリに設定した割り込み・例外ハンドラ(実際にはハンドラへの入り口となる処理)の先頭アドレスへ制御が移ります。
tvinit関数の処理で見たように、IDT(割り込みディスクリプタテーブル)にあるインデックス0x40(64)のエントリに設定した割り込み・例外ハンドラ(実際にはハンドラへの入り口となる処理)の先頭アドレスはvectors[T_SYSCALL](vector64ラベルが付けらたアドレス)なので、割り込み・例外ハンドラ(実際にはハンドラへの入り口となる処理)は下記の処理の部分となります。

.globl vector64
vector64:
pushl $0
pushl $64
jmp alltraps

ここでは、割り込みの発生から割り込み・例外のハンドラへの制御以降についてさらに詳しく説明します。


割り込み・例外処理の詳細

割り込み・例外が発生したかを調べる

①CPUが1つの命令を実行完了します。この時CSレジスタとeipレジスタが次に実行すべき命令を指定しています。
②CSレジスタとeipレジスタが指定している命令を実行する前に、CPUの制御装置が①の命令を実行している間に割り込み・例外が発生したかを調べます。

IDTエントリを読み取る

①割り込み・例外が発生していたら、割り込みベクタ番号iを取得します。
②IDTR(割り込みディスクリプタテーブルレジスタ)からIDT(割り込みディスクリプタテーブル)の先頭アドレスを取得します。
IDTの先頭アドレス と 割り込みベクタ番号i によって指定されるIDTエントリを読み取ります。

GDTエントリを読み取る

①GDTR(グローバルディスクリプタテーブルレジスタ)からGDT(グローバルディスクリプタテーブル)の先頭アドレスを取得します。
②GDTの先頭アドレス と 読み取ったIDTエントリから得られるセレクタ値 を使って、割り込み・例外ハンドラがあるセグメントに関連しているGDTエントリを読み取ります。

CSレジスタCPLと割り込み・例外ハンドラがあるセグメントのDPLを比較する

割り込みが発生した時のCPUの現行特権レベル(CPL)と割り込み・例外ハンドラがあるセグメントのディスクリプタ特権レベル(DPL)を比較します。
割り込みが発生した時のCPUの現行特権レベル(CPL = 0) > 割り込み・例外ハンドラがあるセグメントの特権レベル(DPL = 3)となる場合は、一般保護例外を発生させます。カーネルモードでの実行中に発生した割り込み・例外をユーザー空間で実装されたハンドラが処理できないようにしています。

CSレジスタCPLIDTエントリのDPLを比較する

ソフトウェア割り込みの場合は、割り込みが発生した時のCPUの現行特権レベル(CPL)とIDTエントリのディスクリプタ特権レベル(DPL)を比較します。
割り込みが発生した時のCPUの現行特権レベル(CPL = 3) < IDTエントリのディスクリプタ特権レベル(DPL = 0)となる場合は、一般保護例外を発生させます。ユーザモードでIDTエントリにアクセスできないようにしています。
しかし、今回のようなint T_SYSCALL(int 0x40)命令によるソフトウェア割り込み(システムコール)では、ユーザモードでもIDTエントリにアクセスできるようにする必要があるので、IDTエントリ64のディスクリプタ特権レベルにはユーザモードでも利用できる3(DPL = 0)が設定されています。

CPUがユーザモードの場合はカーネルスタックを用意する

CPUがユーザモードの場合→割り込みが発生した時のCPUの現行特権レベル(CPL = 3) < 割り込み・例外ハンドラがあるセグメントのディスクリプタ特権レベル(DPL = 0)となる場合は、次のような手順でカーネルスタックを利用できるようにします。

①プロセスが現在使用しているssレジスタの値とespレジスタの値を内部レジスタに保存します。
②TSSセグメントからカーネルスタックのアドレスを指定するセグメントベース(taskstate構造体のss0メンバの値)とオフセット値(taskstate構造体のesp0メンバの値)を読み取り、それぞれをssレジスタとespレジスタにロードします。
③①で保存したssレジスタの値とespレジスタの値をカーネルスタックに退避させます。

TSSセグメントの設定は、scheduler関数で呼び出したswitchuvm関数で行なっています。
②の処理が終わった時点で、使用するスタックが、ユーザスタック(ユーザーモードで使用するスタック)からカーネルスタック(カーネルモードで使用するスタック)に切り替わっています。

ユーザスタックとカーネルスタックの状態

この時点で、ユーザスタック(ユーザーモードで使用するスタック)とカーネルスタック(カーネルモードで使用するスタック)の状態は以下のようになっています。

ユーザスタックの状態

下位アドレス側
0 形式的に用意したリターンアドレス
init システムコールの第一引数の値を指定するアドレス
argv システムコールの第二引数の値を指定するアドレス
スタックのボトム(上位アドレス側)

カーネルスタックの状態

下位アドレス側
esp ユーザモードで使っていたespレジスタの値
ss ユーザモードで使っていたssレジスタの値
上位アドレス側

eflags、CSレジスタ、eipレジスタの値をカーネルスタックに退避させる

カーネルスタックにeflags、CSレジスタ、eipレジスタの値を退避させます。

ユーザスタックの状態

下位アドレス側
0 形式的に用意したリターンアドレス
init システムコールの第一引数の値を指定するアドレス
argv システムコールの第二引数の値を指定するアドレス
スタックのボトム(上位アドレス側)

カーネルスタックの状態

下位アドレス側
eip ユーザモードで使っていたeipレジスタの値
cs ユーザモードで使っていたcsレジスタの値
eflags ユーザモードで使っていたeflagsレジスタの値
esp ユーザモードで使っていたespレジスタの値
ss ユーザモードで使っていたssレジスタの値
上位アドレス側

割り込み・例外ハンドラ(への入り口となる処理)への制御移行が完了する

IDTエントリから取得したセレクタ値(tvinit関数内のSETGATEマクロの第三引数(sel)で指定した値)とオフセット値(tvinit関数内のSETGATEマクロの第四引数(off)で指定した値)をそれぞれcsレジスタとeipレジスタにセットします。
この処理が終わった時点で、割り込み・例外ハンドラ(への入り口となる処理)の先頭アドレスへ制御が移ります。

割り込み・例外ハンドラ(への入り口となる処理)

.globl vector64
vector64:
pushl $0
pushl $64
jmp alltraps


.globl vector64
.globl(globlディレクティブ)を使って、vector64ラベルを外部のファイルから参照できるようにします


vector64:
pishl命令が配置されているアドレスにvector64ラベル(シンボル名、名前)を付けます。


pushl $0
push命令によって、ハードウェアエラーコードをスタックに退避させる挙動を再現しています。
割り込み・例外発生時に制御回路がハードウェアエラーコードをスタックに退避されない場合は、ハードウェアエラーコードに相当する値を0としてスタックに退避します。


pushl $64
割り込みベクタ番号の値64をスタックに退避させます。


jmp alltraps
alltrapsサブルーチンのアドレスへジャンプします。

alltrapへジャンプする時のスタックの状態

ユーザースタックの状態

下位アドレス側
0 形式的に用意したリターンアドレス
init システムコールの第一引数の値を指定するアドレス
argv システムコールの第二引数の値を指定するアドレス
スタックのボトム(上位アドレス側)

カーネルスタックの状態

下位アドレス側
64 割り込みベクタ番号の値
0 ハードウェアエラーコードの代わりの値
eip ユーザモードで使っていたeipレジスタの値
cs ユーザモードで使っていたcsレジスタの値
eflags ユーザモードで使っていたeflagsレジスタの値
esp ユーザモードで使っていたespレジスタの値
ss ユーザモードで使っていたssレジスタの値
上位アドレス側


次回
jupiteroak.hatenablog.com