この記事は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
savesegmentとloadsegmentで使っている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
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
32ビットコンピュータをやさしく語る はじめて読む486 アスキー書籍
- 作者: 蒲地輝尚
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/10/21
- メディア: Kindle版
- この商品を含むブログを見る