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を呼び出す事で解決している。