ユーザモードからカーネルモード時のスタック切替
ユーザモードからカーネルモードに切り替わる時、スタックが切り替わります。ではどこでどのように切り替わっているのでしょうか? システムコールではint 80hを実行すると、system_callへとジャンプしレジスターをスタックに退避させています。実はこの時点ですでにカーネルスタックに切り替わっているのです。
X86ではタスク切り替え機構として、タスク状態セグメントと言うTSSがあります。このアーキテクチャーではプロセス毎にTSSを用意して、それをディスクリプタテーブルに登録し、そこにセグメント間JMPを行うことで、タスク切り替えを行うようになっています。TSSディスクリプタにJMPすると、そこに設定されているCS:IP、SS:SPが自動的に設定され、このTSSをtrレジスターに設定します。また同時にその時のレジスター群をtrレジスタのTSSに退避するのです。これはプロセス切り替わる毎のレジスタ群の退避と復元を1命令で行ってくれるというものです。
しかし、Linuxではプロセス切り替えにこのTSSを利用していません。(シグナルの処理とかその返り値等で、退避したレジスター群を操作する必要があり、1命令づつ処理したほうがいいからではと思いますが・・・)とにかく、linuxでは、プロセス切り替えはスタックを切り替えることで行っており、従ってレジスター群の退避/復元は一つ一つプログラムしています。
しかし、システムコールのように特権レベル変更を伴う操作、tssには特権レベル変更に伴うスタックとして、sp0,sp1,sp3が用意されていて、それぞれの特権レベル以降により、そのスタックが設定されようになっています。システムコールも例外ではありません。すなわちユーザモードから特権レベル0のカーネルモードに切り替わる時、tss->sp0のスタックがCPUの機構として設定されるわけです。
そうなると、次の実行するユーザプロセスのカーネルモードにおけるスタックポイントが、tss->sp0に設定される必要があります。
プロセスを切り替える時、switch_toマクロがコールされ、そこでスタックの切り替えの処理が行われるわけですが、そこから__switch_toへとジャンプします。__switch_toマクロからload_sp0関数がコールされます。tss->sp0に次に動作するプロセスのスタックポインタをセットするためです。そしてnative_load_sp0関数で、tss->x86_tss.sp0 = thread->sp0としています。(#ifdef CONFIG_X86_32での処理はよくわかりません。)
X86ではタスク切り替え機構として、タスク状態セグメントと言うTSSがあります。このアーキテクチャーではプロセス毎にTSSを用意して、それをディスクリプタテーブルに登録し、そこにセグメント間JMPを行うことで、タスク切り替えを行うようになっています。TSSディスクリプタにJMPすると、そこに設定されているCS:IP、SS:SPが自動的に設定され、このTSSをtrレジスターに設定します。また同時にその時のレジスター群をtrレジスタのTSSに退避するのです。これはプロセス切り替わる毎のレジスタ群の退避と復元を1命令で行ってくれるというものです。
しかし、Linuxではプロセス切り替えにこのTSSを利用していません。(シグナルの処理とかその返り値等で、退避したレジスター群を操作する必要があり、1命令づつ処理したほうがいいからではと思いますが・・・)とにかく、linuxでは、プロセス切り替えはスタックを切り替えることで行っており、従ってレジスター群の退避/復元は一つ一つプログラムしています。
しかし、システムコールのように特権レベル変更を伴う操作、tssには特権レベル変更に伴うスタックとして、sp0,sp1,sp3が用意されていて、それぞれの特権レベル以降により、そのスタックが設定されようになっています。システムコールも例外ではありません。すなわちユーザモードから特権レベル0のカーネルモードに切り替わる時、tss->sp0のスタックがCPUの機構として設定されるわけです。
そうなると、次の実行するユーザプロセスのカーネルモードにおけるスタックポイントが、tss->sp0に設定される必要があります。
プロセスを切り替える時、switch_toマクロがコールされ、そこでスタックの切り替えの処理が行われるわけですが、そこから__switch_toへとジャンプします。__switch_toマクロからload_sp0関数がコールされます。tss->sp0に次に動作するプロセスのスタックポインタをセットするためです。そしてnative_load_sp0関数で、tss->x86_tss.sp0 = thread->sp0としています。(#ifdef CONFIG_X86_32での処理はよくわかりません。)
static inline void load_sp0(struct tss_struct *tss, struct thread_struct *thread) { native_load_sp0(tss, thread); } static inline void native_load_sp0(struct tss_struct *tss, struct thread_struct *thread) { tss->x86_tss.sp0 = thread->sp0; #ifdef CONFIG_X86_32 /* Only happens when SEP is enabled, no need to test "SEP"arately: */ if (unlikely(tss->x86_tss.ss1 != thread->sysenter_cs)) { tss->x86_tss.ss1 = thread->sysenter_cs; wrmsr(MSR_IA32_SYSENTER_CS, thread->sysenter_cs, 0); } #endif }