プロセス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
ここでは、割り込みの発生から割り込み・例外のハンドラへの制御以降についてさらに詳しく説明します。
割り込み・例外処理の詳細
- 割り込み・例外が発生したかを調べる
- IDTエントリを読み取る
- GDTエントリを読み取る
- CSレジスタのCPLと割り込み・例外ハンドラがあるセグメントのDPLを比較する
- CSレジスタのCPLとIDTエントリのDPLを比較する
- CPUがユーザモードの場合はカーネルスタックを用意する
- ユーザスタックとカーネルスタックの状態
- eflags、CSレジスタ、eipレジスタの値をカーネルスタックに退避させる
- 割り込み・例外ハンドラ(への入り口となる処理)への制御移行が完了する
- 割り込み・例外ハンドラ(への入り口となる処理)
- alltrapへジャンプする時のスタックの状態
割り込み・例外が発生したかを調べる
①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レジスタのCPLとIDTエントリの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レジスタの値 |
上位アドレス側 |