あるコンテキストスイッチの話

3,147 views

Published on

2014年7月5日に行われたOSC名古屋、及び2014年7月12日にカーネル/VM探検隊@北陸 1でのスライドです。
楽しさ求めて、もうちょっとはじけちゃえ(ぴょんぴょんと)

Published in: Technology
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,147
On SlideShare
0
From Embeds
0
Number of Embeds
54
Actions
Shares
0
Downloads
15
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide

あるコンテキストスイッチの話

  1. 1. あるコンテキストスイッチの話 えとみ なるあき OSC名古屋 Kernel/VM探検隊@北陸
  2. 2. お約束 ※ コンテキストスイッチのやりかたはいくつか方法(setjmp,longjmpなど)が あると思いますが、 setjmp,longjmpなどは今回は扱いません。 ※ MPカーネルも扱いません...(頭が追いついていないので(´・ω・`)) ※ wikipediaのコンテキストスイッチの項目をあらかじめ読んでおくと、 よりいっそう楽しめる内容になっております。 ※ trapb命令は覚えておくと良いキーワードです。 ※ exception_return()でFPUをON/OFしているというのは重要です。 ※ ぴょんぴょんも重要キーワードです。 ※ 予習→実装という流れでお話は進みます! ソースコードはおまけです! 資料のCPUはalphaですがarm、 mips、powerpc 全て実装が違います。 この資料を読んで概念を身につけたあとに、 一人でも自分の好きなCPUの実装を調べた方がいらっしゃったら嬉しいです
  3. 3. これまでのあらすじ ※ ある日のNetBSD/alpha currentでtopをすると Floating point exceptionでcore dumpする という事件が発生した! ※ PR-48782でgccのバグだと勘違いして主張してみた!(libmのコンパイルオプションが変更されてた) “ I think that this case is -mieee-with-inexact option bug.” ※ とりあえず変更はrevretされたので自分でも独自調査 “I think that trapb instruction incompatible with fpu_state_load().”と返答し へぼパッチを投げる “lazy FPU context switch”って何?調べてみました! ※ その後、いろいろやりとりして、topのバグでもなく、gccのバグでもなく カーネルのlazy FPU context switchのコードがバグっていたという結論がでた!
  4. 4. まずコンテキストスイッチとは何なのか? コンテキストスイッチ (context switch) とは、 複数のプロセスが1つのCPUを共有できるように、 CPUの状態(コンテキスト)を保存したり復元したりする過程のことである。 コンテキストスイッチはマルチタスクオペレーティングシステムに不可欠な機能である。 通常コンテキストスイッチは多くの計算機処理を必要とするため、 オペレーティングシステムの設計においてはコンテキストスイッチを最適化することが重要である。 コンテキストスイッチでは、実行中のプロセスの状態を何らかの方法で保存し、 後にそのプロセスを再開する際にその状態を復元して、 正常に実行を継続できるようにしなければならない。 プロセスの状態には、そのプロセスが使用し得る全てのレジスタ(特にプログラムカウンタ)や、 プロセスの実行に必要となるオペレーティングシステム固有の情報が含まれる。 wikipediaより
  5. 5. ぴょんぴょん つまりOSのなかでプロセスがぴょんぴょんしている訳ですね! mi_switch() ご注文はどのLWPですか?
  6. 6. 描いてみた sh mi_switch syslog mi_switch mikutter(ruby) mi_switch mlterm shmi_switch コンテキストスイッチ コンテキストスイッチ コンテキストスイッチ コンテキストスイッチ コンテキストスイッチするとき、 実行中のプロセスの状態を「どこ」に保存しているのか? PCB(Process control block)
  7. 7. PCB is 何? Alphaの浮動小数点レジスタは 64bit X 32本+ FPUコントロールレジスタ swpctx命令でコンテキストされる /src/sys/arch/alpha/include/alpha_cpu.h src/sys/arch/alpha/include/pcb.h src/sys/arch/alpha/include/reg.h
  8. 8. 予習として描いてみた(ポルナレフもビックリっーか…) mikutterのプロセス mltermのプロセス CPU 実行中のmikutterから mltermにスイッチ 不思議な力で 次の実行プロセスは mltermに決定 mikutterプロセスのPCBに CPUのレジスタとか保存 mltermプロセスのPCBの内容 をCPUにロード 『The WORLD』 オレだけの時間だぜ CPU 実行中のmltermから mikutterにスイッチ 不思議な力で 次の実行プロセスは mikutterに決定 mltermプロセスのPCBに CPUのレジスタとか保存 mikutterプロセスのPCBの内容 をCPUにロード 『時』は動き出す….
  9. 9. では実装 (コンテキストスイッチをするところ)
  10. 10. mi_switchの実装 /* * The machine independent parts of context switch. * * Returns 1 if another LWP was actually run. */ int mi_switch(lwp_t *l) { struct cpu_info *ci; struct schedstate_percpu *spc; struct lwp *newl; int retval, oldspl; struct bintime bt; bool returning; ………. /* Switch to the new LWP.. */ prevlwp = cpu_switchto(l, newl, returning); ci = curcpu(); /* * Switched away - we have new curlwp. * Restore VM context and IPL. */ pmap_activate(l); uvm_emap_switch(l); pcu_switchpoint(l); /sys/kern/kern_synch.c l newl lの情報(PC等)が lのPCBに保存される newlの情報(PC等)が newlのPCBからロードされる mi_switchが カーネル内のどこから 呼ばれるか?がミソ
  11. 11. mi_switchの実装 /* * The machine independent parts of context switch. * * Returns 1 if another LWP was actually run. */ int mi_switch(lwp_t *l) { struct cpu_info *ci; struct schedstate_percpu *spc; struct lwp *newl; int retval, oldspl; struct bintime bt; bool returning; ………. /* Switch to the new LWP.. */ prevlwp = cpu_switchto(l, newl, returning); ci = curcpu(); /* * Switched away - we have new curlwp. * Restore VM context and IPL. */ pmap_activate(l); uvm_emap_switch(l); pcu_switchpoint(l); 別のLWPにコンテキストスイッチする関数 /sys/kern/kern_synch.c /* * struct lwp *cpu_switchto(struct lwp *current, struct lwp *next) * Switch to the specified next LWP * Arguments: * a0 'struct lwp *' of the LWP to switch from * a1 'struct lwp *' of the LWP to switch to */ LEAF(cpu_switchto, 0) LDGP(pv) beq a0, 1f /* * do an inline savectx(), to save old context */ ldq a2, L_PCB(a0) /* NOTE: ksp is stored by the swpctx */ stq s0, PCB_CONTEXT+(0 * 8)(a2) /* store s0 - s6 */ stq s1, PCB_CONTEXT+(1 * 8)(a2) /sys/arch/alpha/alpha/locore.s
  12. 12. ここまでのおさらい      ※ OSのなかではプロセスがぴょんぴょんしている      ※ 違うプロセスに移る時にプロセスの状態を保存する      ※ 違うプロセスから復帰する時にプロセスの状態を復元する      ※ 保存する状態はCPUのレジスタとプロセスの情報      ※ 保存する場所をPCBという
  13. 13. lazy FPU context switchのFPUとは? Macintosh IIciよりMC68882 FPU(Floating Point Unit、浮動小数点(演算処理)装置)とは、 浮動小数点演算を専門に行う処理装置のこと。 コンピュータの周辺機器のようなアーキテクチャのものもあれば、 CPUと一体化したコプロセッサのようなアーキテクチャのものもある。 wikipediaより ※ FPUは浮動小数点専用のレジスタを持つ ※ でも、どんなプログラムもFPUを使うとは限らない… ※ コンテキストスイッチではCPUのレジスタを保存復帰する ※ FPUのレジスタはサイズがでかい、かつ数が多いので保存復帰が超重い…
  14. 14. lazy FPU context switchとは? \  __  / _ (m) _  ピコーン    |ミ| / `´  \   ( ゚∀゚)  ノヽノ |   < < FPUを使うときだけFPUの内容を コンテキストスイッチしよう! つまり、Lazy context switchとは プロセス切替時に FPUのレジスタを復帰させるのではなく、 プロセス切替後に FPUが使われた時点で初めて復帰させることで FPUを使わない場合のコンテキストスイッチを軽くする技!なのです! ΩΩΩ<な、なんだってー!?
  15. 15. PCUとは? Per CPU Unit (PCU) is an interface to manage synchronization of any per- CPU context (unit) tied to an LWP context. Typical use of PCU is for ”lazy-switch” synchronization of the FPU state.  NetBSD Kernel Developer's Manual PCU(9) NetBSDでLazy context switchの対象となるCPU ※ Alpha FPUは1個 ※ ARM FPUは1個 ※ MIPS FPUは2個(FPUとDSP) ※ PowerPC FPUは2個(FPUとAltiVec/SPE) ※ FPUを無効化出来るCPU ※ 無効化状態でFPUを使おうとすると浮動小数点無効フォルトが発生するCPU
  16. 16. PCUを理解するためのお約束 ※ すべてのLWPはFPUが無効の状態で作成される struct lwp { /* Scheduling and overall state. */ ………. #if PCU_UNIT_COUNT > 0 struct cpu_info * volatile l_pcu_cpu[PCU_UNIT_COUNT]; uint32_t l_pcu_valid; #endif …………. /sys/sys/lwp.h struct cpu_data { /* * The first section is likely to be touched by other CPUs - * it is cache hot. */ lwp_t *cpu_biglock_wanted; /* LWP spinning on biglock */ ……….. struct lwp * volatile cpu_pcu_curlwp[PCU_UNIT_COUNT]; ………. /sys/sys/cpu_data.h ← FPUを使用している(た) LWPのアドレス ← FPUを使用している(た)   CPU構造体のアドレス
  17. 17. 予習として描いてみた exec setreg fpu_state_release PCBのFPUを保存する領域 を初期化 ~MDLWP_FPACTIVEで FPU無効化フラグを立てる exception_return FPUが使えなくなる (*^-゚)vィェィ♪
  18. 18. では実装 (プロセスが作成される所まで)
  19. 19. execve_runproc /sys/kern/kern_exec.c static int execve_runproc(struct lwp *l, struct execve_data * restrict data, bool no_local_exec_lock, bool is_spawn) { struct exec_package * const epp = &data->ed_pack; int error = 0; struct proc *p; ………. /* * Set initial SP at the top of the stack. * * Note that on machines where stack grows up (e.g. hppa), SP points to * the end of arg/env strings. Userland guesses the address of argc * via ps_strings::ps_argvstr. */ /* Setup new registers and do misc. setup. */ (*epp->ep_esch->es_emul->e_setregs)(l, epp, (vaddr_t)newstack); if (epp->ep_esch->es_setregs) (*epp->ep_esch->es_setregs)(l, epp, (vaddr_t)newstack); ………. /* Discard all PCU state; need to start fresh */ pcu_discard_all(l); まずはPCBの初期化から
  20. 20. setregs ユーザープロセスがプログラムを 実行できるようにスタックの設定、 レジスタの初期化を行う /sys/arch/alpha/alpha/machdep.c /* * Set registers on exec. */ void setregs(register struct lwp *l, struct exec_package *pack, vaddr_t stack) { struct trapframe *tfp = l->l_md.md_tf; struct pcb *pcb; ………. pcb = lwp_getpcb(l); memset(&pcb->pcb_fp, 0, sizeof(pcb->pcb_fp)); alpha_pal_wrusp(stack); tfp->tf_regs[FRAME_PS] = ALPHA_PSL_USERSET; tfp->tf_regs[FRAME_PC] = pack->ep_entry & ~3; tfp->tf_regs[FRAME_A0] = stack; /* a0 = sp */ tfp->tf_regs[FRAME_A1] = 0; /* a1 = rtld cleanup */ tfp->tf_regs[FRAME_A2] = 0; /* a2 = rtld object */ tfp->tf_regs[FRAME_A3] = l->l_proc->p_psstrp; /* a3 = ps_strings */ tfp->tf_regs[FRAME_T12] = tfp->tf_regs[FRAME_PC]; /* a.k.a. PV */ ←PCBのFPUを保存する領域 を初期化する
  21. 21. execve_runproc sys/kern/kern_exec.c static int execve_runproc(struct lwp *l, struct execve_data * restrict data, bool no_local_exec_lock, bool is_spawn) { struct exec_package * const epp = &data->ed_pack; int error = 0; struct proc *p; ………. /* * Set initial SP at the top of the stack. * * Note that on machines where stack grows up (e.g. hppa), SP points to * the end of arg/env strings. Userland guesses the address of argc * via ps_strings::ps_argvstr. */ /* Setup new registers and do misc. setup. */ (*epp->ep_esch->es_emul->e_setregs)(l, epp, (vaddr_t)newstack); if (epp->ep_esch->es_setregs) (*epp->ep_esch->es_setregs)(l, epp, (vaddr_t)newstack); ………. /* Discard all PCU state; need to start fresh */ pcu_discard_all(l); 次はFPUの無効化
  22. 22. /sys/kern/subr_pcu.c FPUを無効化する const pcu_ops_t fpu_ops = { .pcu_id = PCU_FPU, .pcu_state_load = fpu_state_load, .pcu_state_save = fpu_state_save, .pcu_state_release = fpu_state_release, }; const pcu_ops_t * const pcu_ops_md_defs[PCU_UNIT_COUNT] = { [PCU_FPU] = &fpu_ops, }; /* * pcu_discard_all: discard PCU state of the given LWP. * * Used by exec and LWP exit. */ void pcu_discard_all(lwp_t *l) { const uint32_t pcu_valid = l->l_pcu_valid; if (__predict_true(pcu_valid == 0)) { /* PCUs are not in use. */ return; } for (u_int id = 0; id < PCU_UNIT_COUNT; id++) { if ((pcu_valid & (1U << id)) == 0) { continue; } if (__predict_true(l->l_pcu_cpu[id] == NULL)) { continue; } const pcu_ops_t * const pcu = pcu_ops_md_defs[id]; pcu_lwp_op(pcu, l, PCU_CMD_RELEASE); } l->l_pcu_valid = 0; } /src/sys/arch/alpha/alpha/machdep.c 親LWPがFPUを使っていたら、 FPUを無効化するために pcu_lwp_op()内で pcu_ops_t構造体経由で fpu_state_release()を呼びます
  23. 23. FPUを無効化する /* * Release the FPU. */ void fpu_state_release(struct lwp *l) { l->l_md.md_flags &= ~MDLWP_FPACTIVE; } /src/sys/arch/alpha/alpha/fp_complete.c #define MDLWP_FP_C 0x007ffffe /* Extended FP_C Quadword bits */ #define MDLWP_FPACTIVE __BIT(63) /* FPU is active on LWP's PCU CPU */ /src/sys/arch/alpha/include/proc.h ←フラグをセットします この時点ではまだFPUは無効ではありません! フラグをセットしただけです。
  24. 24. /* * exception_return: return from trap, exception, or syscall */ IMPORT(ssir, 8) LEAF(exception_return, 1) /* XXX should be NESTED */ br pv, 1f ………. /* GET_CPUINFO clobbers v0, t0, t8...t11. */ 3: GET_CPUINFO /* check for AST */ ldq t1, CPU_INFO_CURLWP(v0) ldl t3, L_MD_ASTPENDING(t1) /* AST pending? */ bne t3, 7f /* yes */ /* no: headed back to user space */ /* Enable the FPU based on whether MDLWP_FPACTIVE is set. */ 4: ldq t2, L_MD_FLAGS(t1) cmplt t2, zero, a0 call_pal PAL_OSF1_wrfen FPUを無効化した状態でプロセスの作成を完了する trap()/systemcall()を終了するときに exception_return()でFPUを 有効化したり無効化したりします /sys/arch/alpha/alpha/locore.s FENに1を立てるとFPUがON
  25. 25. 次にFPUを使ってみる ………. $main..ng: lda $30,-64($30) .cfi_def_cfa_offset 64 stq $26,0($30) stq $15,8($30) ………. stl $1,48($15) ldah $1,$LC0($29) !gprelhigh ldt $f10,$LC0($1) !gprellow ………. divt/sui $f12,$f11,$f10 trapb stt $f10,32($15) ………. FPUが無効化された状態で LWPが作成されました! 次はFPUを使ってみましょう!
  26. 26. 予習として描いてみた FPUが使えない状態でLWP作成 浮動小数点命令を発効 ※ 以前にCPU(FPU)を使用していたLWPのFPUレジスタの内容を そのLWPのPCBにセーブ/リリース ※ 自分用のPCBに保存してあるFPUのレジスタの内容をFPUにロード ※ exception_return()でFPU有効化 trap()
  27. 27. では実装 (FPUがロードされる所まで)
  28. 28. 浮動小数点命令を実行すると、浮動小数点無効フォルトが発生する /sys/arch/alpha/alpha/trap.c 浮動小数点命令を実行出来るように fpu_load(pcu_load)を呼びます /* * Trap is called from locore to handle most types of processor traps. * System calls are broken out for efficiency and ASTs are broken out * to make the code a bit cleaner and more representative of the * Alpha architecture. */ /*ARGSUSED*/ void trap(const u_long a0, const u_long a1, const u_long a2, const u_long entry, struct trapframe *framep) { struct lwp *l; struct proc *p; struct pcb *pcb; ………. case ALPHA_KENTRY_IF: /* * These are always fatal in kernel, and should never * happen. (Debugger entry is handled in XentIF.) ………. case ALPHA_IF_CODE_FEN: fpu_load(); goto out; static inline void fpu_load(void) { pcu_load(&fpu_ops); } static inline void fpu_save(void) { pcu_save(&fpu_ops); } static inline void fpu_discard(bool valid_p) { pcu_discard(&fpu_ops, valid_p); }
  29. 29. FPUをsave/releaseする もし、このLWPがFPU(CPU)の 実行権限を取得する「以前」に 別のLWPがFPUを使っていたら FPUのレジスタをセーブ、リリース /* * pcu_load: load/initialize the PCU state of current LWP on current CPU. */ void pcu_load(const pcu_ops_t *pcu) { lwp_t *oncpu_lwp, * const l = curlwp; const u_int id = pcu->pcu_id; struct cpu_info *ci, *curci; int s; ………. /* Save the PCU state on the current CPU, if there is any. */ if ((oncpu_lwp = curci->ci_pcu_curlwp[id]) != NULL) { pcu_do_op(pcu, oncpu_lwp, PCU_CMD_SAVE | PCU_CMD_RELEASE); } ………. /* * Finally, load the state for this LWP on this CPU. Indicate to * the load function whether PCU state was valid before this call. */ const bool valid = ((1U << id) & l->l_pcu_valid) != 0; pcu->pcu_state_load(l, valid ? PCU_VALID : 0); curci->ci_pcu_curlwp[id] = l; l->l_pcu_cpu[id] = curci; l->l_pcu_valid |= (1U << id); splx(s); } /sys/kern/subr_pcu.c
  30. 30. FPUをsave/releaseする /* * pcu_do_op: save/release PCU state on the current CPU. * * => Must be called at IPL_PCU or from the interrupt. */ static inline void pcu_do_op(const pcu_ops_t *pcu, lwp_t * const l, const int flags) { struct cpu_info * const ci = curcpu(); const u_int id = pcu->pcu_id; KASSERT(l->l_pcu_cpu[id] == ci); if (flags & PCU_CMD_SAVE) { pcu->pcu_state_save(l); } if (flags & PCU_CMD_RELEASE) { pcu->pcu_state_release(l); ci->ci_pcu_curlwp[id] = NULL; l->l_pcu_cpu[id] = NULL; } } /sys/kern/subr_pcu.c ← save/releaseをするから   割り込まれたくない?     ∧ ∧___    /(*゚ー゚) /\  /| ̄∪∪ ̄|\/    |  FPU |/      ̄ ̄ ̄ ̄   ∧ ∧  (*゚ー゚)  |つ ⊂ ∼O‐つ FPUを使いたいのにスイッチ以前のLWPの情報が残ってる…. FPUレジスタの内容を以前のLWPのPCBに保存しないと… FPUのレジスタの内容だけスイッチ時に PCBに退避されてないのねん
  31. 31. FPUをsaveする /* * Save the FPU state. */ void fpu_state_save(struct lwp *l) { struct pcb * const pcb = lwp_getpcb(l); alpha_pal_wrfen(1); savefpstate(&pcb->pcb_fp); alpha_pal_wrfen(0); } /* * savefpstate: Save a process's floating point state. * * Arguments: * a0 'struct fpstate *' to save into */ LEAF(savefpstate, 1) LDGP(pv) /* save all of the FP registers */ lda t1, FPREG_FPR_REGS(a0) /* get address of FP reg. save area */ stt $f0, (0 * 8)(t1) /* save first register, using hw name */ stt $f1, (1 * 8)(t1) /* etc. */ ………. /* * Then save the FPCR; note that the necessary 'trapb's are taken * care of on kernel entry and exit. */ mf_fpcr ft0 stt ft0, FPREG_FPR_CR(a0) /* store to FPCR save area */ RET END(savefpstate) ※alpha_pal_wrfenはfpuを有効にする関数 (引数1で有効、引数0で無効) 一時的にFPUにアクセスするので有効にします /sys/arch/alpha/alpha/fp_complete.c /sys/arch/alpha/alpha/locore.s
  32. 32. FPUをloadする ※ 現在のLWPのPCB領域に保存してある FPUレジスタの内容をFPUにロード ※ CPU構造体に現在のLWPのアドレス LWP構造体に現在のCPU構造体のアドレス 何個目のFPUを使用しているか をセット /* * pcu_load: load/initialize the PCU state of current LWP on current CPU. */ void pcu_load(const pcu_ops_t *pcu) { lwp_t *oncpu_lwp, * const l = curlwp; const u_int id = pcu->pcu_id; struct cpu_info *ci, *curci; int s; ………. /* Save the PCU state on the current CPU, if there is any. */ if ((oncpu_lwp = curci->ci_pcu_curlwp[id]) != NULL) { pcu_do_op(pcu, oncpu_lwp, PCU_CMD_SAVE | PCU_CMD_RELEASE); } ………. /* * Finally, load the state for this LWP on this CPU. Indicate to * the load function whether PCU state was valid before this call. */ const bool valid = ((1U << id) & l->l_pcu_valid) != 0; pcu->pcu_state_load(l, valid ? PCU_VALID : 0); curci->ci_pcu_curlwp[id] = l; l->l_pcu_cpu[id] = curci; l->l_pcu_valid |= (1U << id); splx(s); } /sys/kern/subr_pcu.c
  33. 33. FPUをloadする /* * Load the float-point context for the current lwp. */ void fpu_state_load(struct lwp *l, u_int flags) { struct pcb * const pcb = lwp_getpcb(l); ………. if ((flags & PCU_VALID) == 0) { atomic_inc_ulong(&fpevent_use.ev_count); } else { atomic_inc_ulong(&fpevent_reuse.ev_count); } alpha_pal_wrfen(1); restorefpstate(&pcb->pcb_fp); alpha_pal_wrfen(0); l->l_md.md_flags |= MDLWP_FPACTIVE; } ※alpha_pal_wrfenはfpuを有効にする関数 (引数1で有効、引数0で無効) 一時的にFPUにアクセスするので有効にします /sys/arch/alpha/alpha/fp_complete.c /sys/arch/alpha/alpha/locore.s /* * restorefpstate: Restore a process's floating point state. * * Arguments: * a0 'struct fpstate *' to restore from */ LEAF(restorefpstate, 1) LDGP(pv) /* * Restore the FPCR; note that the necessary 'trapb's are taken care of * on kernel entry and exit. */ ldt ft0, FPREG_FPR_CR(a0) /* load from FPCR save area */ mt_fpcr ft0 /* Restore all of the FP registers. */ lda t1, FPREG_FPR_REGS(a0)/* get address of FP reg. save area */ ldt $f0, (0 * 8)(t1) /* restore first reg., using hw name */ ldt $f1, (1 * 8)(t1) /* etc. */ ………. ldt $f28, (28 * 8)(t1) ldt $f29, (29 * 8)(t1) ldt $f30, (30 * 8)(t1) RET END(restorefpstate)
  34. 34. ここまでのおさらい    ※ すべてのLWPはFPUが無効の状態で作成される    ※ FPUが無効の状態で浮動小数点命令を実行すると 浮動小数点無効フォルトが発生する    ※ (もしコンテキストスイッチ以前のLWPがFPUを使っていたら) FPUのレジスタをセーブ、リリースする ∧ ∧  。 (*゚ー゚) /   | ⊃⊃ ∼|   |   U~U では応用編へ
  35. 35. 予習として描いてみた FPUが使えない状態でLWP作成 浮動小数点命令を発効 ※ 以前にCPU(FPU)を使用していたLWPのFPUレジスタの内容を そのLWPのPCBにセーブ/リリース ※ 自分用のPCBに保存してあるFPUのレジスタの内容をFPUにロード ※ exception_return()でFPU有効化 mi_switch syslog mi_switch top 浮動小数点命令を発効 浮動小数点命令 普通の命令 trap() コンテキストスイッチ pcu_switchpoint() でFPU無効化マーク コンテキストスイッチ pcu_switchpoint() でFPU無効化マーク trap() ※ 以前にCPU(FPU)を使用していたLWPのFPUレジスタの内容を そのLWPのPCBにセーブ/リリース このタイミングでFPUを保存 CPUのコンテキストスイッチと FPUのコンテキストスイッチが一致しない
  36. 36. では実装 (コンテキストスイッチされるところ)
  37. 37. mi_switchの実装 /* * The machine independent parts of context switch. * * Returns 1 if another LWP was actually run. */ int mi_switch(lwp_t *l) { struct cpu_info *ci; struct schedstate_percpu *spc; struct lwp *newl; int retval, oldspl; struct bintime bt; bool returning; ………. /* Switch to the new LWP.. */ prevlwp = cpu_switchto(l, newl, returning); ci = curcpu(); /* * Switched away - we have new curlwp. * Restore VM context and IPL. */ pmap_activate(l); uvm_emap_switch(l); pcu_switchpoint(l); /sys/kern/kern_synch.c l newl lの情報(PC等)が lのPCBに保存される newlの情報(PC等)が newlのPCBからロードされる mi_switchが カーネル内のどこから 呼ばれるか?がミソ
  38. 38. pcu_switchpoint /* * pcu_switchpoint: release PCU state if the LWP is being run on another CPU. * This routine is called on each context switch by by mi_switch(). */ void pcu_switchpoint(lwp_t *l) { const uint32_t pcu_valid = l->l_pcu_valid; int s; KASSERTMSG(l == curlwp, "l %p != curlwp %p", l, curlwp); if (__predict_true(pcu_valid == 0)) { /* PCUs are not in use. */ return; } s = splpcu(); for (u_int id = 0; id < PCU_UNIT_COUNT; id++) { if ((pcu_valid & (1U << id)) == 0) { continue; } struct cpu_info * const pcu_ci = l->l_pcu_cpu[id]; if (pcu_ci == NULL || pcu_ci == l->l_cpu) { continue; } const pcu_ops_t * const pcu = pcu_ops_md_defs[id]; pcu->pcu_state_release(l); } splx(s); } コンテキストスイッチ以前のLWPが FPUを使っていたら FPU無効化フラグを立てる trap()/systemcall()(exception_return)が呼 ばれてもFPUは有効にならない /sys/kern/subr_pcu.c
  39. 39. おしまい
  40. 40. で、trapb命令は? ※ trapb命令を実行すると特殊なtrapが発生するが、 その処理内で無条件にfpu_load()を呼んでいた! ※ つまり、fpu_load()の二度掛けをしていた   → それまでの演算結果の破棄…. ※ trapb命令は結構面白い命令なのでどっかで発表したいです… fpu_load()は容量用法を守って正しくお使いください
  41. 41. 参考文献 & お世話になった方 OSの一般的な知識 BSDカーネルの設計と実装 -FreeBSD詳解- Solarisインターナル ―カーネル構造のすべて Lion’s Commentary on UNIX はじめてのOSコードリーディング UNIX V6で学ぶカーネルのしくみ Alphaについて Alpha Architecture Reference Manual Fourth Edition Alpha 21264 Microprocessor Hardware Reference Manual lazy FPU context switchについて NetBSD Documentation: How lazy FPU context switch works http://www.netbsd.org/docs/kernel/lazyfpu.html NetBSD ドキュメンテーション: どのように lazy FPU コンテキストスイッチは動作するのか http://www.jp.netbsd.org/ja/docs/kernel/lazyfpu.html 夜でもアッサム http://assam-at-night.blogspot.jp/2006/06/lazy-fpu-context-switchnetbsd.html お世話になった方&ファボって元気づけてくれた方 Izumi Tsutsui ‫‏‬@tsutsuii Jun Ebihara ‫‏‬@ebijun、oshimaya ‫‏‬@oshimyja、Kenji Aoyama ‫‏‬@ao_kenji、ご注文はOSvですか? @syuu1228 NONAKA Kimihiro ‫‏‬@nonakap、Hiroshi Tokuda ‫‏‬@tokudahiroshi、 伊織ん@へら ‫‏‬@ioriveur 波打際のだよもんさん ‫‏‬@daemon1995、まあぼ@cub ‫‏‬@marbocub じとめすきー @lycoris_blog ※イカ先生 @impreza_gf8さんにはAlpha Station XP1000を譲っていただきました! ※ mltermはarakiken @arakikenさんの高性能ターミナルエミュレータです! ※ mikutterはおさわり大臣@toshi_aさんの高性能ておくれtwitterクライアントです!

×