NetBSD-currentにおけるcurcpu() / curlwp()の仕組み
NetBSDではスレッドやCPUの管理の為にcurcpu()/curlwp()というマクロを定義しており、これによって現在のCPUに対するstruct cpu_infoを取得したり現在のスレッドに対するstruct lwpを取得したり出来るようになっている。
これがどのように実装されており、どのようにしてSMP対応がなされているかを調べてみた。
NetBSD-current/mips
SMP対応されていない。
以下はarch/mips/include/cpu.hからの抜粋:
struct cpu_info { struct cpu_data ci_data; /* MI per-cpu data */ struct cpu_info *ci_next; /* Next CPU in list */ cpuid_t ci_cpuid; /* Machine-level identifier */ u_long ci_cpu_freq; /* CPU frequency */ u_long ci_cycles_per_hz; /* CPU freq / hz */ u_long ci_divisor_delay; /* for delay/DELAY */ u_long ci_divisor_recip; /* unused, for obsolete microtime(9) */ struct lwp *ci_curlwp; /* currently running lwp */ struct lwp *ci_fpcurlwp; /* the current FPU owner */ int ci_want_resched; /* user preemption pending */ int ci_mtx_count; /* negative count of held mutexes */ int ci_mtx_oldspl; /* saved SPL value */ int ci_idepth; /* hardware interrupt depth */ }; #define CPU_INFO_ITERATOR int #define CPU_INFO_FOREACH(cii, ci) \ (void)(cii), ci = &cpu_info_store; ci != NULL; ci = ci->ci_next
一応CPU_INFO_FOREACHなどというマクロを定義しているが、勿論一度もつかわれていない。
/* Note: must be kept in sync with -ffixed-?? Makefile.mips. */ #define MIPS_CURLWP $23 #define MIPS_CURLWP_QUOTED "$23" #define MIPS_CURLWP_CARD 23 #define MIPS_CURLWP_FRAME(x) FRAME_S7(x) #ifndef _LOCORE extern struct cpu_info cpu_info_store; register struct lwp *mips_curlwp asm(MIPS_CURLWP_QUOTED); #define curlwp mips_curlwp #define curcpu() (curlwp->l_cpu) #define curpcb ((struct pcb *)curlwp->l_addr) #define fpcurlwp (curcpu()->ci_fpcurlwp) #define cpu_number() (0) #define cpu_proc_fork(p1, p2)
これを見ると、どうもmips_curlwpという変数を一般レジスタ$23(S7)に固定的に割り当てているようだ。
なので、curlwp()はレジスタ$23への参照で、curcpu()、curpcb()などはcurlwp()を起点としたポインタ参照で対応出来る。
cpu_number()はSMP対応してないので0。
しかし、何故curlwp()をレジスタに割り振ったのだろうか。高速化の為?
NetBSD-current/powerpc
struct cpu_info { struct cpu_data ci_data; /* MI per-cpu data */ struct device *ci_dev; /* device of corresponding cpu */ struct lwp *ci_curlwp; /* current owner of the processor */ struct pcb *ci_curpcb; struct pmap *ci_curpm; struct lwp *ci_fpulwp; struct lwp *ci_veclwp; int ci_cpuid; volatile int ci_astpending; int ci_want_resched; volatile u_long ci_lasttb; volatile int ci_tickspending; volatile int ci_cpl; volatile int ci_iactive; volatile int ci_idepth; volatile int ci_ipending; int ci_intrdepth; int ci_mtx_oldspl; int ci_mtx_count; char *ci_intstk; #define CPUSAVE_LEN 8 register_t ci_tempsave[CPUSAVE_LEN]; register_t ci_ddbsave[CPUSAVE_LEN]; register_t ci_ipkdbsave[CPUSAVE_LEN]; #define CPUSAVE_R28 0 /* where r28 gets saved */ #define CPUSAVE_R29 1 /* where r29 gets saved */ #define CPUSAVE_R30 2 /* where r30 gets saved */ #define CPUSAVE_R31 3 /* where r31 gets saved */ #define CPUSAVE_DAR 4 /* where SPR_DAR gets saved */ #define CPUSAVE_DSISR 5 /* where SPR_DSISR gets saved */ #define CPUSAVE_SRR0 6 /* where SRR0 gets saved */ #define CPUSAVE_SRR1 7 /* where SRR1 gets saved */ #define DISISAVE_LEN 4 register_t ci_disisave[DISISAVE_LEN]; struct cache_info ci_ci; void *ci_sysmon_cookie; void (*ci_idlespin)(void); uint32_t ci_khz; struct evcnt ci_ev_clock; /* clock intrs */ struct evcnt ci_ev_statclock; /* stat clock */ struct evcnt ci_ev_softclock; /* softclock intrs */ struct evcnt ci_ev_softnet; /* softnet intrs */ struct evcnt ci_ev_softserial; /* softserial intrs */ struct evcnt ci_ev_traps; /* calls to trap() */ struct evcnt ci_ev_kdsi; /* kernel DSI traps */ struct evcnt ci_ev_udsi; /* user DSI traps */ struct evcnt ci_ev_udsi_fatal; /* user DSI trap failures */ struct evcnt ci_ev_kisi; /* kernel ISI traps */ struct evcnt ci_ev_isi; /* user ISI traps */ struct evcnt ci_ev_isi_fatal; /* user ISI trap failures */ struct evcnt ci_ev_pgm; /* user PGM traps */ struct evcnt ci_ev_fpu; /* FPU traps */ struct evcnt ci_ev_fpusw; /* FPU context switch */ struct evcnt ci_ev_ali; /* Alignment traps */ struct evcnt ci_ev_ali_fatal; /* Alignment fatal trap */ struct evcnt ci_ev_scalls; /* system call traps */ struct evcnt ci_ev_vec; /* Altivec traps */ struct evcnt ci_ev_vecsw; /* Altivec context switches */ struct evcnt ci_ev_umchk; /* user MCHK events */ struct evcnt ci_ev_ipi; /* IPIs received */ }; #ifdef MULTIPROCESSOR struct cpu_hatch_data { struct device *self; struct cpu_info *ci; int running; int pir; int asr; int hid0; int sdr1; int sr[16]; int batu[4], batl[4]; int tbu, tbl; }; static __inline int cpu_number(void) { int pir; __asm ("mfspr %0,1023" : "=r"(pir)); return pir; } void cpu_boot_secondary_processors(void); #define CPU_IS_PRIMARY(ci) ((ci)->ci_cpuid == 0) #define CPU_INFO_ITERATOR int #define CPU_INFO_FOREACH(cii, ci) \ cii = 0, ci = &cpu_info[0]; cii < ncpu; cii++, ci++ #else #define cpu_number() 0 #define CPU_INFO_ITERATOR int #define CPU_INFO_FOREACH(cii, ci) \ cii = 0, ci = curcpu(); ci != NULL; ci = NULL #endif /* MULTIPROCESSOR */ extern struct cpu_info cpu_info[]; static __inline struct cpu_info * curcpu(void) { struct cpu_info *ci; __asm volatile ("mfsprg %0,0" : "=r"(ci)); return ci; } #define curlwp (curcpu()->ci_curlwp) #define curpcb (curcpu()->ci_curpcb) #define curpm (curcpu()->ci_curpm)
cpu_infoの保存先は単なる配列になっていて、リンクドリストにはなっていない。
curcpu()のインラインアセンブリは良く分からない。
curlwp()、curpcb()などはcurcpu()からのポインタ参照で取得出来る。
NetBSD-current/alpha
struct cpu_info { /* * Private members accessed in assembly with 8 bit offsets. */ struct lwp *ci_fpcurlwp; /* current owner of the FPU */ paddr_t ci_curpcb; /* PA of current HW PCB */ /* * Public members. */ struct lwp *ci_curlwp; /* current owner of the processor */ struct cpu_data ci_data; /* MI per-cpu data */ struct cctr_state ci_cc; /* cycle counter state */ struct cpu_info *ci_next; /* next cpu_info structure */ int ci_mtx_count; int ci_mtx_oldspl; /* * Private members. */ struct mchkinfo ci_mcinfo; /* machine check info */ cpuid_t ci_cpuid; /* our CPU ID */ struct cpu_softc *ci_softc; /* pointer to our device */ u_long ci_want_resched; /* preempt current process */ u_long ci_intrdepth; /* interrupt trap depth */ struct trapframe *ci_db_regs; /* registers for debuggers */ uint64_t ci_pcc_freq; /* cpu cycles/second */ #if defined(MULTIPROCESSOR) volatile u_long ci_flags; /* flags; see below */ volatile u_long ci_ipis; /* interprocessor interrupts pending */ #endif }; #define CPUF_PRIMARY 0x01 /* CPU is primary CPU */ #define CPUF_PRESENT 0x02 /* CPU is present */ #define CPUF_RUNNING 0x04 /* CPU is running */ #define CPUF_PAUSED 0x08 /* CPU is paused */ #define CPUF_FPUSAVE 0x10 /* CPU is currently in fpusave_cpu() */ extern struct cpu_info cpu_info_primary; extern struct cpu_info *cpu_info_list; #define CPU_INFO_ITERATOR int #define CPU_INFO_FOREACH(cii, ci) cii = 0, ci = cpu_info_list; \ ci != NULL; ci = ci->ci_next #if defined(MULTIPROCESSOR) extern volatile u_long cpus_running; extern volatile u_long cpus_paused; extern struct cpu_info *cpu_info[]; #define curcpu() ((struct cpu_info *)alpha_pal_rdval()) #define CPU_IS_PRIMARY(ci) ((ci)->ci_flags & CPUF_PRIMARY) void cpu_boot_secondary_processors(void); void cpu_pause_resume(unsigned long, int); void cpu_pause_resume_all(int); #else /* ! MULTIPROCESSOR */ #define curcpu() (&cpu_info_primary) #endif /* MULTIPROCESSOR */ #define curlwp curcpu()->ci_curlwp #define fpcurlwp curcpu()->ci_fpcurlwp #define curpcb curcpu()->ci_curpcb /* * definitions of cpu-dependent requirements * referenced in generic code */ #define cpu_number() alpha_pal_whami() #define cpu_proc_fork(p1, p2) /* nothing */
ついでにalphaも読んでみた。
curcpu()はSMP有効の場合alpha_pal_rdval()になる。
これはなんなのか調べてみた所、Alphaのマイクロコード的な存在であるPALを呼び出しているものでrdvalはRead System Valueだそうだ。
とすると、どこかでWrite System Valueもしておかないとイケない事になりそうだが、やはりそれも存在していた。
マスタCPUの場合はarch/alpha/alpha/machdep.cで設定される:
#if defined(MULTIPROCESSOR) /* * Set our SysValue to the address of our cpu_info structure. * Secondary processors do this in their spinup trampoline. */ alpha_pal_wrval((u_long)&cpu_info_primary); cpu_info[cpu_id] = &cpu_info_primary; #endif
セカンダリCPUの場合はarch/alpha/alpha/multiproc.sの中で、CPUを起こすときに引数として渡されてきたcpu_infoをwrvalで書き込んでいる:
/* Store our CPU info in SysValue. */ mov s0, a0 call_pal PAL_OSF1_wrval
残りのcurlwp()、curpcb()などはやはりポインタ参照で解決されている。
cpu_infoのアロケートと保存はどのようになっているのか見てみることにする。
struct cpu_info cpu_info_primary = { .ci_curlwp = &lwp0 }; struct cpu_info *cpu_info_list = &cpu_info_primary; #if defined(MULTIPROCESSOR) /* * Array of CPU info structures. Must be statically-allocated because * curproc, etc. are used early. */ struct cpu_info *cpu_info[ALPHA_MAXPROCS];
if (ma->ma_slot == hwrpb->rpb_primary_cpu_id) ci = &cpu_info_primary; else { ci = malloc(sizeof(*ci), M_DEVBUF, M_WAITOK); memset(ci, 0, sizeof(*ci)); } #if defined(MULTIPROCESSOR) cpu_info[ma->ma_slot] = ci; #endif
#if defined(MULTIPROCESSOR) void cpu_boot_secondary_processors(void) { struct cpu_info *ci; u_long i; bool did_patch = false; for (i = 0; i < ALPHA_MAXPROCS; i++) { ci = cpu_info[i]; if (ci == NULL || ci->ci_data.cpu_idlelwp == NULL) continue; if (ci->ci_flags & CPUF_PRIMARY) continue; if ((cpus_booted & (1UL << i)) == 0) continue; /* Patch MP-criticial kernel routines. */ if (did_patch == false) { alpha_patch(true); did_patch = true; } /* * Link the processor into the list, and launch it. */ ci->ci_next = cpu_info_list->ci_next; cpu_info_list->ci_next = ci; atomic_or_ulong(&ci->ci_flags, CPUF_RUNNING); atomic_or_ulong(&cpus_running, (1U << i)); } }
cpu_info[]はポインタの保存先になっており、実体は有効化時にmallocしてリンクドリストにつないでいるようだ。
恐らくMIPSでcpu_info_storeに当たるのはcpu_info_primaryだろう。
cpu_info_listには大した意味はなくて、単なる&cpu_info_primaryの別名という感じがする。
更にみていくと、arch/alpha/alpha/lock_stubs.sなどでGET_CURLWPというようなマクロを呼んでる部分がある。
これはどうなっているのだろうか?
/* * void mutex_enter(kmutex_t *mtx); */ LEAF(mutex_enter, 1) LDGP(pv) GET_CURLWP
#if defined(MULTIPROCESSOR) /* * Get various per-cpu values. A pointer to our cpu_info structure * is stored in SysValue. These macros clobber v0, t0, t8..t11. * * All return values are in v0. */ #define GET_CPUINFO call_pal PAL_OSF1_rdval #define GET_CURLWP \ call_pal PAL_OSF1_rdval ; \ addq v0, CPU_INFO_CURLWP, v0 #define GET_FPCURLWP \ call_pal PAL_OSF1_rdval ; \ addq v0, CPU_INFO_FPCURLWP, v0 #define GET_CURPCB \ call_pal PAL_OSF1_rdval ; \ addq v0, CPU_INFO_CURPCB, v0 #define GET_IDLE_PCB(reg) \ call_pal PAL_OSF1_rdval ; \ ldq reg, CPU_INFO_IDLE_PCB_PADDR(v0) #else /* if not MULTIPROCESSOR... */ IMPORT(cpu_info_primary, CPU_INFO_SIZEOF) #define GET_CPUINFO lda v0, cpu_info_primary #define GET_CURLWP lda v0, cpu_info_primary + CPU_INFO_CURLWP #define GET_FPCURLWP lda v0, cpu_info_primary + CPU_INFO_FPCURLWP #define GET_CURPCB lda v0, cpu_info_primary + CPU_INFO_CURPCB #define GET_IDLE_PCB(reg) \ lda reg, cpu_info_primary ; \ ldq reg, CPU_INFO_IDLE_PCB_PADDR(reg) #endif #endif /* _KERNEL */
と、まぁ、予想通りなのだがRead System Valueを呼び出す事で解決している。