ユーザプロセスからカーネルモードへの遷移
x86は4段階の動作レベルを有していて、カーネルは0、ユーザプロセスは3で動作することで、ユーザプロセスによるカーネルプロセスの進入をプロテクしています。プロテクトモードではセグメントがディスクリプターテーブルのインデックスを表すもので、動作レベルはセグメント(セレクタ)で指定されるディスクリプタに、その動作レベルを設定することで、各セグメントでの動作をレベルが決定されるようになっています。そしてCSセグメントに関しては、実際の動作時のレベルとなります。ということはCSセグメントに動作レベルが0で設定してあるディスクリプターのインデックスを設定すれば(セグメント間ジャンプ)、動作レベルが遷移するのではと・・・。いうわけにはいきません。イメージはこんな感じなのですが、しかし、そうなると動作レベル3のプロセスが動作レベル0の領域参照を自由に許してしまうことになります。X86では下位動作レベルから上位動作レベルのセグメント間jmpを許していないのです。
システムコールは動作レベル3のユーザプロセスから動作レベル0のカーネルへ、動作レベルを遷移させています。ではどのようにして遷移させているのでしょうか?
x86では下位の動作レベルから上位の動作レベルの遷移の手段の1つとして、割り込みよる方法があります。INT命令のソフト割り込み(トラップ)によって、ユーザプロセスからカーネルへと遷移させることができるのです。そしてシステムコールint 0x80はまさにこれを使ってカーネルモードへと遷移させているわけです。
割り込み処理は、グローバルディスクリプターのように、idtrレジスターにメモリー上に定義された割り込みディスクリプターの集まりのテーブルの先頭アドレスが設定し、割り込みが発生するたびに、その割り込み番号をインデックスとしてこの割り込みディスクリプタの情報に従って動作するようになっています。割り込みディスクリプタにはジャンプするセレクタ値およびオフセットが記述されており、呼び出し元が動作レベル3でも動作レベル0へとセレクタ値へジャンプすることが可能となっています。言うまでも無く、この割り込みディスクリプターテーブルを作成するのはカーネルの仕事となるわけです。
start_kernel関数からtrap_init関数が呼ばれ、そこで割り込みディスクリプターテーブルが作成されます。その中でシステムコールに関しては、 set_system_gate(SYSCALL_VECTOR, &system_call)として設定されています。SYSCALL_VECTORは0x80で、system_callはカーネルのシステムコールを処理する入り口となります。
set_system_gate関数は_set_gate関数を呼び出しています。_set_gate関数は、割り込みディスクリプターテーブルに、引数で指定された値をディスクリプタのフォーマットに整形して、割り込みディスクリプタテーブルに設定するだけです。システムコール処理では、本関数を_set_gate(0x80, GATE_TRAP, &system_call, 0x3, 0, __KERNEL_CS)で呼ぶことになります。この引数の意味するところは、割り込みディスクリプターテーブルの0x80番目にゲートトラップとして、CSセレクタ値として__KERNEL_CSを、オフセットはsystem_callとする設定となります。4番目の引数は特権レベルを表しています。この場合動作モードが3以上なら、この割り込みゲート呼び出し可能と言う意味になります。言い換えればユーザプロセスが利用できるようにするには、_set_gatek関数の3番目の引数を3で呼び出す必要があるわけです。
割り込みゲートの動作レベルは、このゲートを呼び出せる動作モードを表しており、ジャンプ先の動作レベルとは別ものです。割り込みゲートの動作レベルが3であっても、動作レベルが0のセグメントへジャンプすることは可能です。
システムコールは動作レベル3のユーザプロセスから動作レベル0のカーネルへ、動作レベルを遷移させています。ではどのようにして遷移させているのでしょうか?
x86では下位の動作レベルから上位の動作レベルの遷移の手段の1つとして、割り込みよる方法があります。INT命令のソフト割り込み(トラップ)によって、ユーザプロセスからカーネルへと遷移させることができるのです。そしてシステムコールint 0x80はまさにこれを使ってカーネルモードへと遷移させているわけです。
割り込み処理は、グローバルディスクリプターのように、idtrレジスターにメモリー上に定義された割り込みディスクリプターの集まりのテーブルの先頭アドレスが設定し、割り込みが発生するたびに、その割り込み番号をインデックスとしてこの割り込みディスクリプタの情報に従って動作するようになっています。割り込みディスクリプタにはジャンプするセレクタ値およびオフセットが記述されており、呼び出し元が動作レベル3でも動作レベル0へとセレクタ値へジャンプすることが可能となっています。言うまでも無く、この割り込みディスクリプターテーブルを作成するのはカーネルの仕事となるわけです。
start_kernel関数からtrap_init関数が呼ばれ、そこで割り込みディスクリプターテーブルが作成されます。その中でシステムコールに関しては、 set_system_gate(SYSCALL_VECTOR, &system_call)として設定されています。SYSCALL_VECTORは0x80で、system_callはカーネルのシステムコールを処理する入り口となります。
set_system_gate関数は_set_gate関数を呼び出しています。_set_gate関数は、割り込みディスクリプターテーブルに、引数で指定された値をディスクリプタのフォーマットに整形して、割り込みディスクリプタテーブルに設定するだけです。システムコール処理では、本関数を_set_gate(0x80, GATE_TRAP, &system_call, 0x3, 0, __KERNEL_CS)で呼ぶことになります。この引数の意味するところは、割り込みディスクリプターテーブルの0x80番目にゲートトラップとして、CSセレクタ値として__KERNEL_CSを、オフセットはsystem_callとする設定となります。4番目の引数は特権レベルを表しています。この場合動作モードが3以上なら、この割り込みゲート呼び出し可能と言う意味になります。言い換えればユーザプロセスが利用できるようにするには、_set_gatek関数の3番目の引数を3で呼び出す必要があるわけです。
static inline void set_system_gate(unsigned int n, void *addr) { BUG_ON((unsigned)n > 0xFF); #ifdef CONFIG_X86_32 _set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS); #else _set_gate(n, GATE_INTERRUPT, addr, 0x3, 0, __KERNEL_CS); #endif }補足
割り込みゲートの動作レベルは、このゲートを呼び出せる動作モードを表しており、ジャンプ先の動作レベルとは別ものです。割り込みゲートの動作レベルが3であっても、動作レベルが0のセグメントへジャンプすることは可能です。