Linuxのプロセス切り替え処理

この記事はLinux Advent Calendar 2014 1日目の記事です。

switch_toはプロセス切り替え時の処理ですね。ここを読んでみます。基本的にはアセンブラで__switch_toがcの関数となってます。

まずはプロトタイプから。引数は3個でprev,、next、 lastの3個。型はすべてstruct task_structです。prevは切り替え前のプロセスで現在実行中のプロセス、nextは次に動かすプロセスです。lastは__switch_to()の戻り値が入ります。

103 /* Save restore flags to clear handle leaking NT */
104 #define switch_to(prev, next, last) \

SAVE_CONTEXTは予想通りマクロです。

105         asm volatile(SAVE_CONTEXT                                         \

実施しているのは↓でrbpをスタックにpush、rsiの値をrbpにコピー。

 82 #define SAVE_CONTEXT    "pushf ; pushq %%rbp ; movq %%rsi,%%rbp\n\t"

カレントプロセスのrspを保存して、rspに次のプロセスのスタックポインタを設定。

106              "movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */       \
107              "movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */    \

ここで使っているthreadrspは119行目のここの部分です。

119                [threadrsp] "i" (offsetof(struct task_struct, thread.sp)), \

__switch_to()を呼び出し。ここは後ほど。

108              "call __switch_to\n\t"                                       \

__percpu_argはマクロでイマイチよくわからない部分はあるんだけど

109              "movq "__percpu_arg([current_task])",%%rsi\n\t"              \

current_taskはarch/x86/kernel/cpu/common.cで定義されているcpuごとにもつ現在のプロセスを指す変数。

1140 DEFINE_PER_CPU(struct task_struct *, current_task) ____cacheline_aligned =
1141         &init_task;
1142 EXPORT_PER_CPU_SYMBOL(current_task);

__percpu_argマクロはこんな感じ

67 #define __percpu_arg(x)         __percpu_prefix "%P" #x

__percpu_prefixマクロはこれ。

48 #define __percpu_prefix         "%%"__stringify(__percpu_seg)":"

__stringifyマクロはこう。

  9 #define __stringify_1(x...)     #x
 10 #define __stringify(x...)       __stringify_1(x)

で、結局のところ%Pを付けたいだけでこれがどういうことかというと、stackoverflowによるとP __percpu_arg() to force gcc to dereference the pointer value passed in via the "p" input constraint.とあるのでポインタのデリファレンスをしたいということでしょう。

これはスタックが壊れていないかのチェックに使うもの。

111 
110              __switch_canary                                              \

CONFIG_CC_STACKPROTECTORが定義されている場合に有効(多分各種ディストリビューションでは有効かも)な部分だけど、ここはスタックが壊れていないかのチェックなので飛ばしましょう。

thread_infoは122行目の名前がついたところなはず。

111              "movq %P[thread_info](%%rsi),%%r8\n\t"                       \

メンバ変数のstackのoffsetを取得。処理的にrsiはstruct task_struct *なんだけど、これはprev or nextどっちだ?

122                [thread_info] "i" (offsetof(struct task_struct, stack)),   \

次の行ではraxをrdiに設定。ここでraxは__switch_to()の返り値なのでprevのアドレスがrdiに設定されるはず。ということはrsiはnextかな。

112              "movq %%rax,%%rdi\n\t"                                       \

tif_forkはstruct thread_infoのflagsがTIF_FORKかを確認していて、これはforkから戻った場合なのかをチェックしている。

113              "testl  %[_tif_fork],%P[ti_flags](%%r8)\n\t"                 \

fork()から戻ってきた場合はarch/x86/kernel/entry_64.Sにあるret_from_forkラベルにジャンプする。

114              "jnz   ret_from_fork\n\t"                                    \

RESTORE_CONTEXTは最初にやったSAVE_CONTEXTと逆の操作ですね。

115              RESTORE_CONTEXT                                              \

rsiを元に戻して、レジスタ各種をpop。

83 #define RESTORE_CONTEXT "movq %%rbp,%%rsi ; popq %%rbp ; popf\t"

此処から先はほとんど処理というわけではないので見るのはここまでで。マクロのswitch_canary_oparam、switch_canary_iparamもスタックのチェックなので。

116              : "=a" (last)                                                \
117                __switch_canary_oparam                                     \
118              : [next] "S" (next), [prev] "D" (prev),                      \
119                [threadrsp] "i" (offsetof(struct task_struct, thread.sp)), \
120                [ti_flags] "i" (offsetof(struct thread_info, flags)),      \
121                [_tif_fork] "i" (_TIF_FORK),                               \
122                [thread_info] "i" (offsetof(struct task_struct, stack)),   \
123                [current_task] "m" (current_task)                          \
124                __switch_canary_iparam                                     \
125              : "memory", "cc" __EXTRA_CLOBBER)

次は__switch_to()を。最初のほうはまあ飛ばしましょう。この関数はコメントが多くて良いですね( ´∀`)bグッ!

277 __visible __notrace_funcgraph struct task_struct *
278 __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
279 {
280         struct thread_struct *prev = &prev_p->thread;
281         struct thread_struct *next = &next_p->thread;
282         int cpu = smp_processor_id();
283         struct tss_struct *tss = &per_cpu(init_tss, cpu);
284         unsigned fsindex, gsindex;
285         fpu_switch_t fpu;
286 

switch_fpu_prepare()はfpuを切り替える場合の前処理。fpuを保存するのは切り替え前のプロセスがfpuを使っていた場合もしくはfpuを使用し、過去5連続したコンテキスト·スイッチがある場合。

287         fpu = switch_fpu_prepare(prev_p, next_p, cpu);
288 

ここはtssのesp0がthread_structのsp0にセットされる。arch/x86/include/asm/processor.hのnative_load_sp0()が呼ばれていると思う。

289         /*
290          * Reload esp0, LDT and the page table pointer:
291          */
292         load_sp0(tss, next);
293 

ちなみにload_sp0はこんな関数。

 22 static inline void load_sp0(struct tss_struct *tss,
 23                              struct thread_struct *thread)
 24 {
 25         PVOP_VCALL2(pv_cpu_ops.load_sp0, tss, thread);
 26 }

savesegment・ loadsegmentマクロを使ってレジスタの入れ替えを実施。

294         /*
295          * Switch DS and ES.
296          * This won't pick up thread selector changes, but I guess that is ok.
297          */
298         savesegment(es, prev->es);
299         if (unlikely(next->es | prev->es))
300                 loadsegment(es, next->es);
301 
302         savesegment(ds, prev->ds);
303         if (unlikely(next->ds | prev->ds))
304                 loadsegment(ds, next->ds);
305 

savesegmentloadsegmentで使っているes・dsはぱっと見た感じ変数なんだけど単なるレジスタ名です。

245 #define savesegment(seg, value)                         \
246         asm("mov %%" #seg ",%0":"=r" (value) : : "memory")
306 

fs・gsレジスタを保存。

307         /* We must save %fs and %gs before load_TLS() because
308          * %fs and %gs may be cleared by load_TLS().
309          *
310          * (e.g. xen_load_tls())
311          */
312         savesegment(fs, fsindex);
313         savesegment(gs, gsindex);
314

thread local storageを設定。

315         load_TLS(next, cpu);
316

このarch_end_context_switch()は paravirt_end_context_switch()のことなのかstruct pv_cpu_ops pv_cpu_opsで初期化している.end_context_switch = paravirt_nop,なのかと思うけど、struct pv_cpu_opsで設定しているのはparavirt_nopなのでx86_64の場合は何も無い気がする。paravirt_end_context_switchを使うのはXenだろーな。

317         /*
318          * Leave lazy mode, flushing any hypercalls made here.
319          * This must be done before restoring TLS segments so
320          * the GDT and LDT are properly updated, and must be
321          * done before math_state_restore, so the TS bit is up
322          * to date.
323          */
324         arch_end_context_switch(next_p);
325

next->fsindexをfsレジスタに設定してprevプロセスのfsは0に設定する。

326         /*
327          * Switch FS and GS.
328          *
329          * Segment register != 0 always requires a reload.  Also
330          * reload when it has changed.  When prev process used 64bit
331          * base always reload to avoid an information leak.
332          */
333         if (unlikely(fsindex | next->fsindex | prev->fs)) {
334                 loadsegment(fs, next->fsindex);
335                 /*
336                  * Check if the user used a selector != 0; if yes
337                  *  clear 64bit base, since overloaded base is always
338                  *  mapped to the Null selector
339                  */
340                 if (fsindex)
341                         prev->fs = 0;
342         }

64bitのfsgsレジスタを使う場合はMSRにデータを書き込む。(参考:wiki.osdev.org

343         /* when next process has a 64bit base use it */
344         if (next->fs)
345                 wrmsrl(MSR_FS_BASE, next->fs);
346         prev->fsindex = fsindex;
347 

348         if (unlikely(gsindex | next->gsindex | prev->gs)) {
349                 load_gs_index(next->gsindex);
350                 if (gsindex)
351                         prev->gs = 0;
352         }
353         if (next->gs)
354                 wrmsrl(MSR_KERNEL_GS_BASE, next->gs);
355         prev->gsindex = gsindex;
356

fpuの切り替え最後の処理で、基本的にはfpu切り替えのステータスが変わるだけど。

357         switch_fpu_finish(next_p, fpu);
358 

rspとかcurrent_taskの切り替え。

359         /*
360          * Switch the PDA and FPU contexts.
361          */
362         prev->usersp = this_cpu_read(old_rsp);
363         this_cpu_write(old_rsp, next->usersp);
364         this_cpu_write(current_task, next_p);
365

プリエンプトの回数をコピー。

366         /*
367          * If it were not for PREEMPT_ACTIVE we could guarantee that the
368          * preempt_count of all tasks was equal here and this would not be
369          * needed.
370          */
371         task_thread_info(prev_p)->saved_preempt_count = this_cpu_read(__preempt_count);
372         this_cpu_write(__preempt_count, task_thread_info(next_p)->saved_preempt_count);
373 

カーネルの切り替え。

374         this_cpu_write(kernel_stack,
375                   (unsigned long)task_stack_page(next_p) +
376                   THREAD_SIZE - KERNEL_STACK_OFFSET);
377

デバッグレジスタ、ioビットマップの再読み込み・

378         /*
379          * Now maybe reload the debug registers and handle I/O bitmaps
380          */
381         if (unlikely(task_thread_info(next_p)->flags & _TIF_WORK_CTXSW_NEXT ||
382                      task_thread_info(prev_p)->flags & _TIF_WORK_CTXSW_PREV))
383                 __switch_to_xtra(prev_p, next_p, tss);
384

プロセス切り替え前のプロセスを返す。

385         return prev_p;
386 }
387