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. 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
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. 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構造体のアドレス
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の初期化から
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. /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. 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. /*
* 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
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. 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. 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. 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. 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. 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)
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. 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