あるコンテキストスイッチの話
DESCRIPTION
2014年7月5日に行われたOSC名古屋、及び2014年7月12日にカーネル/VM探検隊@北陸 1でのスライドです。 楽しさ求めて、もうちょっとはじけちゃえ(ぴょんぴょんと)TRANSCRIPT
あるコンテキストスイッチの話
えとみ なるあきOSC名古屋
Kernel/VM探検隊@北陸
お約束
※ コンテキストスイッチのやりかたはいくつか方法(setjmp,longjmpなど)が あると思いますが、 setjmp,longjmpなどは今回は扱いません。※ MPカーネルも扱いません...(頭が追いついていないので(´・ω・`))※ wikipediaのコンテキストスイッチの項目をあらかじめ読んでおくと、 よりいっそう楽しめる内容になっております。※ trapb命令は覚えておくと良いキーワードです。※ exception_return()でFPUをON/OFしているというのは重要です。※ ぴょんぴょんも重要キーワードです。※ 予習→実装という流れでお話は進みます!
ソースコードはおまけです!資料のCPUはalphaですがarm、 mips、powerpc全て実装が違います。この資料を読んで概念を身につけたあとに、一人でも自分の好きなCPUの実装を調べた方がいらっしゃったら嬉しいです
これまでのあらすじ
※ ある日の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のコードがバグっていたという結論がでた!
まずコンテキストスイッチとは何なのか?
コンテキストスイッチ (context switch) とは、複数のプロセスが1つのCPUを共有できるように、CPUの状態(コンテキスト)を保存したり復元したりする過程のことである。
コンテキストスイッチはマルチタスクオペレーティングシステムに不可欠な機能である。通常コンテキストスイッチは多くの計算機処理を必要とするため、オペレーティングシステムの設計においてはコンテキストスイッチを最適化することが重要である。
コンテキストスイッチでは、実行中のプロセスの状態を何らかの方法で保存し、後にそのプロセスを再開する際にその状態を復元して、正常に実行を継続できるようにしなければならない。
プロセスの状態には、そのプロセスが使用し得る全てのレジスタ(特にプログラムカウンタ)や、プロセスの実行に必要となるオペレーティングシステム固有の情報が含まれる。
wikipediaより
ぴょんぴょん
つまりOSのなかでプロセスがぴょんぴょんしている訳ですね!
mi_switch()
ご注文はどのLWPですか?
描いてみた
sh
mi_switch syslog
mi_switch mikutter(ruby)
mi_switch mlterm
shmi_switch
コンテキストスイッチ
コンテキストスイッチ
コンテキストスイッチ
コンテキストスイッチ
コンテキストスイッチするとき、実行中のプロセスの状態を「どこ」に保存しているのか?
PCB(Process control block)
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
予習として描いてみた(ポルナレフもビックリっーか…)
mikutterのプロセスmltermのプロセス
CPU実行中のmikutterから
mltermにスイッチ
不思議な力で次の実行プロセスは
mltermに決定
mikutterプロセスのPCBにCPUのレジスタとか保存
mltermプロセスのPCBの内容をCPUにロード
『The WORLD』 オレだけの時間だぜ
CPU実行中のmltermからmikutterにスイッチ
不思議な力で次の実行プロセスは
mikutterに決定
mltermプロセスのPCBにCPUのレジスタとか保存
mikutterプロセスのPCBの内容をCPUにロード
『時』は動き出す….
では実装(コンテキストスイッチをするところ)
mi_switchの実装/* * The machine independent parts of context switch. * * Returns 1 if another LWP was actually run. */intmi_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がカーネル内のどこから呼ばれるか?がミソ
mi_switchの実装/* * The machine independent parts of context switch. * * Returns 1 if another LWP was actually run. */intmi_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
ここまでのおさらい
※ OSのなかではプロセスがぴょんぴょんしている
※ 違うプロセスに移る時にプロセスの状態を保存する
※ 違うプロセスから復帰する時にプロセスの状態を復元する
※ 保存する状態はCPUのレジスタとプロセスの情報
※ 保存する場所をPCBという
lazy FPU context switchのFPUとは?
Macintosh IIciよりMC68882
FPU(Floating Point Unit、浮動小数点(演算処理)装置)とは、浮動小数点演算を専門に行う処理装置のこと。コンピュータの周辺機器のようなアーキテクチャのものもあれば、CPUと一体化したコプロセッサのようなアーキテクチャのものもある。
wikipediaより
※ FPUは浮動小数点専用のレジスタを持つ※ でも、どんなプログラムもFPUを使うとは限らない…※ コンテキストスイッチではCPUのレジスタを保存復帰する※ FPUのレジスタはサイズがでかい、かつ数が多いので保存復帰が超重い…
lazy FPU context switchとは?
\ __ /_ (m) _ ピコーン |ミ|/ `´ \ ( ゚∀゚) ノヽノ | < <
FPUを使うときだけFPUの内容をコンテキストスイッチしよう!
つまり、Lazy context switchとはプロセス切替時に FPUのレジスタを復帰させるのではなく、プロセス切替後に FPUが使われた時点で初めて復帰させることでFPUを使わない場合のコンテキストスイッチを軽くする技!なのです!
ΩΩΩ<な、なんだってー!?
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
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構造体のアドレス
予習として描いてみた
exec
setreg
fpu_state_release
PCBのFPUを保存する領域を初期化
~MDLWP_FPACTIVEでFPU無効化フラグを立てる
exception_return FPUが使えなくなる(*^-゚)vィェィ♪
では実装(プロセスが作成される所まで)
execve_runproc/sys/kern/kern_exec.cstatic intexecve_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の初期化から
setregsユーザープロセスがプログラムを実行できるようにスタックの設定、レジスタの初期化を行う
/sys/arch/alpha/alpha/machdep.c
/* * Set registers on exec. */voidsetregs(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を保存する領域 を初期化する
execve_runprocsys/kern/kern_exec.cstatic intexecve_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の無効化
/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. */voidpcu_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()を呼びます
FPUを無効化する
/* * Release the FPU. */voidfpu_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は無効ではありません!フラグをセットしただけです。
/* * 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, a0call_pal PAL_OSF1_wrfen
FPUを無効化した状態でプロセスの作成を完了する
trap()/systemcall()を終了するときにexception_return()でFPUを有効化したり無効化したりします
/sys/arch/alpha/alpha/locore.s FENに1を立てるとFPUがON
次に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を使ってみましょう!
予習として描いてみた
FPUが使えない状態でLWP作成
浮動小数点命令を発効
※ 以前にCPU(FPU)を使用していたLWPのFPUレジスタの内容を そのLWPのPCBにセーブ/リリース※ 自分用のPCBに保存してあるFPUのレジスタの内容をFPUにロード※ exception_return()でFPU有効化
trap()
では実装(FPUがロードされる所まで)
浮動小数点命令を実行すると、浮動小数点無効フォルトが発生する
/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*/voidtrap(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 voidfpu_load(void){
pcu_load(&fpu_ops);}
static inline voidfpu_save(void){
pcu_save(&fpu_ops);}
static inline voidfpu_discard(bool valid_p){
pcu_discard(&fpu_ops, valid_p);}
FPUをsave/releaseする
もし、このLWPがFPU(CPU)の実行権限を取得する「以前」に別のLWPがFPUを使っていたらFPUのレジスタをセーブ、リリース
/* * pcu_load: load/initialize the PCU state of current LWP on current CPU. */voidpcu_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
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 voidpcu_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に退避されてないのねん
FPUをsaveする/* * Save the FPU state. */
voidfpu_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
FPUをloadする
※ 現在のLWPのPCB領域に保存してある FPUレジスタの内容をFPUにロード※ CPU構造体に現在のLWPのアドレス LWP構造体に現在のCPU構造体のアドレス 何個目のFPUを使用しているか をセット
/* * pcu_load: load/initialize the PCU state of current LWP on current CPU. */voidpcu_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
FPUをloadする/* * Load the float-point context for the current lwp. */voidfpu_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)
ここまでのおさらい
※ すべてのLWPはFPUが無効の状態で作成される
※ FPUが無効の状態で浮動小数点命令を実行すると 浮動小数点無効フォルトが発生する
※ (もしコンテキストスイッチ以前のLWPがFPUを使っていたら) FPUのレジスタをセーブ、リリースする
∧ ∧ 。 (*゚ー゚) / | ⊃⊃~| | U~U
では応用編へ
予習として描いてみた
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のコンテキストスイッチが一致しない
では実装(コンテキストスイッチされるところ)
mi_switchの実装/* * The machine independent parts of context switch. * * Returns 1 if another LWP was actually run. */intmi_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がカーネル内のどこから呼ばれるか?がミソ
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(). */voidpcu_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
おしまい
で、trapb命令は?※ trapb命令を実行すると特殊なtrapが発生するが、 その処理内で無条件にfpu_load()を呼んでいた! ※ つまり、fpu_load()の二度掛けをしていた → それまでの演算結果の破棄….
※ trapb命令は結構面白い命令なのでどっかで発表したいです…
fpu_load()は容量用法を守って正しくお使いください
参考文献 & お世話になった方OSの一般的な知識BSDカーネルの設計と実装 -FreeBSD詳解-Solarisインターナル ―カーネル構造のすべてLion’s Commentary on UNIXはじめてのOSコードリーディング UNIX V6で学ぶカーネルのしくみ
AlphaについてAlpha Architecture Reference Manual Fourth EditionAlpha 21264 Microprocessor Hardware Reference Manual
lazy FPU context switchについてNetBSD Documentation: How lazy FPU context switch workshttp://www.netbsd.org/docs/kernel/lazyfpu.htmlNetBSD ドキュメンテーション: どのように 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 @tsutsuiiJun Ebihara @ebijun、oshimaya @oshimyja、Kenji Aoyama @ao_kenji、ご注文はOSvですか? @syuu1228NONAKA Kimihiro @nonakap、Hiroshi Tokuda @tokudahiroshi、 伊織ん@へら~ @ioriveur波打際のだよもんさん @daemon1995、まあぼ@cub @marbocub じとめすきー @lycoris_blog
※イカ先生 @impreza_gf8さんにはAlpha Station XP1000を譲っていただきました!※ mltermはarakiken @arakikenさんの高性能ターミナルエミュレータです! ※ mikutterはおさわり大臣@toshi_aさんの高性能ておくれtwitterクライアントです!