switch_toの第三引数について
Rev.2を表示中。最新版はこちら。
switch_toはprev, next, prevと3つの引数で呼ばれます。第三の引数は何故?との質問で、詳解LINUXカーネルの説明では、今ひとつ要領が得ることができず、ただスタックがかわるから。ということで納得していましたが、はた。と閃めきました。ひょっとしたら外れているかもしれません。まずこのことを理解する前提に、カーネルスタックは個々のユーザプロセス内に設定してあるスタックを使っていると言う事です。switch_toでプロセスが切り替わります。すなわちcontext_switch内でswitch_to前と後ろとではスタックが異なるということです。
static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next) { ・・・・・ switch_to(prev, next, prev); ・・・・・ finish_task_switch(this_rq(), prev); }switch_toを呼び出した後、finish_task_switchを呼んでいます。それには切り替えられるプロセスprev引数をともなっています。しかし、ここでのprevはprevプロセス(カレントプロセス)上のスタックに存在しているわけで、nextプロセス上のスタックには存在しません。そうなるとfinish_task_switchでの引数prevは、切り替えられるプロセスとして参照することができません。そうならないようにするために、第三の引数としてprevを渡しているのです。
どのような実装か、私なりの解釈でいきますと、まず、 switch_toは関数でなくマクロである。そしてgccの拡張インラインアセンブラで記述してあるということを頭にいれておくことが重要です。
#define switch_to(prev, next, last) \ do { \ unsigned long ebx, ecx, edx, esi, edi; \ \ asm volatile("pushfl\n\t" /* save flags */ \ "pushl %%ebp\n\t" /* save EBP */ \ "movl %%esp,%[prev_sp]\n\t" /* save ESP */ \ "movl %[next_sp],%%esp\n\t" /* restore ESP */ \ "movl $1f,%[prev_ip]\n\t" /* save EIP */ \ "pushl %[next_ip]\n\t" /* restore EIP */ \ "jmp __switch_to\n" /* regparm call */ \ "1:\t" \ "popl %%ebp\n\t" /* restore EBP */ \ "popfl\n" /* restore flags */ \ \ /* output parameters */ \ : [prev_sp] "=m" (prev->thread.sp), \ [prev_ip] "=m" (prev->thread.ip), \ "=a" (last), \ \ /* clobbered output registers: */ \ "=b" (ebx), "=c" (ecx), "=d" (edx), \ "=S" (esi), "=D" (edi) \ \ /* input parameters: */ \ : [next_sp] "m" (next->thread.sp), \ [next_ip] "m" (next->thread.ip), \ \ /* regparm parameters for __switch_to(): */ \ [prev] "a" (prev), \ [next] "d" (next)); \ } while (呪文のそうなソースです。処理の概要は、まずAXレジスターにprevを、DXレジスターにnextを設定します。そしてprevのスタックポインターを保存し、nextのスタックポインターをSPレジスターに設定します。この瞬間、context_switch関数内のprev, nextの内容は、prevプロセスの内容でなく, nextプロセスのものに切り替わってしまいます。そのために、本マクロ終了にAXを第三引数としてのlastに代入することで、context_switchのprevをprevプロセスのものとしているわけです。
nextプロセス上のローカル変数としてのprevの位置に、AX(切り替えられるprevが設定されている。)で上書きしているわけです。なぜこんなまどろっこしいことを。思いますが、ここで先に説明した、switch_toが関数でなく、マクロでありしかも拡張インラインで記述している。ということが大きく関係してきえると理解しました。
マクロ故、AXレジスターを介してprev返す事はできません。またインラインアセンブラ故に、それぞれの入力オペレータと出力オペレーにprevが必要です。そしてスタック切り替え後のprevとして(SPからのオフセット)として、出力オペレータとしてのprevと同じオフセットを示しているlastに、AXで上書きすることで、スタックが切り替わってもprevだけは、そのスタック上では切り替わる前の値として参照できるようにしています。たぶん
p/s
switch_toを呼ぶ前に,prevをstatic変数に保存して、それをfinish_task_switchの引数としてはだめなのだろうか? と思いましたが、よく考えればswitch_toから返って次に走るのはnextですから、静的な変数でって言うのはダメですね。