NetBSD-5.0BETA/i386のSMP実装#1

NetBSD-5.0BETA/i386のSMP実装がどうなっているのかについて調べてみる。

ディレクトリ構成

sys/arch/i386i386依存のコードで、sys/arch/x86amd64/i386共通コードになっている模様。

SMPサポート時のアナウンス

http://kerneltrap.org/node/443を読むと、コンフィグレーションが以下のように書き換わると書いてある:

Multiprocessor kernels need:

cpu*            at mainbus?
ioapic*         at mainbus? apid ?
options         MULTIPROCESSOR
options         COM_MPLOCK

..and if you want debugging and/or lots of output:
 
options         MPDEBUG
options         MPVERBOSE

なので、まずはここからみていく。

バイスコンフィグレーション

sys/arch/i386/conf/std.i386をみると、以下のような項目がある:

options		MULTIPROCESSOR		# multiprocessor supprot
options 	MPBIOS			# configure CPUs and APICs using MPBIOS

mainbus0 at root
cpu* at mainbus?
ioapic* at mainbus?

これが上述のコンフィグレーションと思われる。

ここからコードを辿っていく。おそらく、

  • cpuデバイスが複数になる場合
  • ioapicデバイスが存在する場合
  • MULTIPROCESSORフラグが有効な場合
  • MPBIOSフラグが有効な場合

などのコードを探していけばよい。

sys/arch/i386/i386/mainbus.c

mainbus_attach()の中でcpuの検出を行う関数を呼び出している。
mpacpiを使う場合とmpbiosを使う場合があるようだ。
ここではmpbios(少々古いハードウェアの場合)の方を見ていこうと思う。

#ifdef MPBIOS
	mpbios_present = mpbios_probe(self);
#endif

最初にmpbios_probe()を呼び出し、

	if (!mpacpi_active) {
#ifdef MPBIOS
		if (mpbios_present)
			mpbios_scan(self, &numcpus, &numioapics);
		else
#endif

次にmpbios_scan()を呼び、

#if defined(MPBIOS) && defined(MPBIOS_SCANPCI)
		if (mpbios_scanned != 0)
			mpbios_scan_pci(self, &mba.mba_pba, pcibusprint);
		else
#endif
		config_found_ia(self, "pcibus", &mba.mba_pba, pcibusprint);

最後にmpbios_scan_pci()を呼び出している。

sys/arch/x86/x86/mpbios.c

mpbios_probe()は、mp floating pointer structureとmp configuration table headerを読み出して適切な値が読めているかチェックしている。

	scan_loc = 0;

	if (ebda && ebda < IOM_BEGIN ) {
		mp_fps = mpbios_search(self, ebda, 1024, &mp_fp_map);
		if (mp_fps != NULL)
			goto found;
	}

	scan_loc = 1;

	if (memtop && memtop <= IOM_BEGIN ) {
		mp_fps = mpbios_search(self, memtop - 1024, 1024, &mp_fp_map);
		if (mp_fps != NULL)
			goto found;
	}

	scan_loc = 2;

	mp_fps = mpbios_search(self, BIOS_BASE, BIOS_COUNT, &mp_fp_map);
	if (mp_fps != NULL)
		goto found;

	/* nothing found */
	return 0;

mp floating pointer structureの取得

	cthpa = mp_fps->pap;

	mp_cth = mpbios_map (cthpa, sizeof (*mp_cth), &mp_cfg_table_map);
	cthlen = mp_cth->base_len;
	mpbios_unmap(&mp_cfg_table_map);

	mp_cth = mpbios_map (cthpa, cthlen, &mp_cfg_table_map);

mp configuration table headerの取得

mpbios_scan()では、mpbios_probe()で取得したfloating pointer structureとconfiguration table headerに設定されている値を元にデバイス(CPU以外にもバスや割り込み、APICなどの初期化があるらしい)の初期化を行っている。
CPUが一つ検出される毎にmpbios_cpu()が呼ばれる。

		while ((count--) && (position < end)) {
			switch (type = *position) {
			case MPS_MCT_CPU:
#if NACPI > 0
				/* ACPI has done this for us */
				if (mpacpi_ncpu)
					break;
#endif
				mpbios_cpu(position, self);
				break;

CPU毎にmpbios_cpu()の呼び出しを行っているところ

mpbios_cpu()ではstruct cpu_attach_argsにパラメータを入れてcpnfig_found_sm_loc()を呼び出しているので、恐らくこれでcpuデバイスとして検出されるはず。
という事は、次に呼ばれるのは恐らくcpu_attach()になる。

static void
mpbios_cpu(const uint8_t *ent, struct device *self)
{
	const struct mpbios_proc *entry = (const struct mpbios_proc *)ent;
	struct cpu_attach_args caa;
	int locs[CPUBUSCF_NLOCS];

	/* XXX move this into the CPU attachment goo. */
	/* check for usability */
	if (!(entry->cpu_flags & PROCENTRY_FLAG_EN))
		return;

	mpbios_ncpu++;

	/* check for BSP flag */
	if (entry->cpu_flags & PROCENTRY_FLAG_BP)
		caa.cpu_role = CPU_ROLE_BP;
	else
		caa.cpu_role = CPU_ROLE_AP;

	caa.cpu_number = entry->apic_id;
	caa.cpu_func = &mp_cpu_funcs;
	locs[CPUBUSCF_APID] = caa.cpu_number;

	config_found_sm_loc(self, "cpubus", locs, &caa, mp_cpuprint,
			    config_stdsubmatch);
}
sys/arch/x86/x86/cpu.c

cpu_attach()では、struct cpu_infoを初期化、cpu_vm_init()でuvm_page_recolor()を実行(?)、mi_cpu_attach()を呼んでスケジューラにCPUを認識させidlelwpをcpu_infoに割り当て、TSSの設定、割り込みの初期化、ページングの設定を行ってcpu_start_secondary()を呼んでいる。
この関数はプライマリCPUと共通なので、プライマリCPUとセカンダリ以降は区別されるようになっている。

sys/kern/kern_cpu.c

mi_cpu_attach()では、前述した通りスケジューラにCPUを認識させidlelwpをcpu_infoに割り当てている。

	sched_cpuattach(ci);

sys/kern/kern_runq.cに存在
run queueをアロケート・初期化している。

	error = create_idle_lwp(ci);
	if (error != 0) {
		/* XXX revert sched_cpuattach */
		return error;
	}

	if (ci == curcpu())
		ci->ci_data.cpu_onproc = curlwp;
	else
		ci->ci_data.cpu_onproc = ci->ci_data.cpu_idlelwp;

CPUがプライマリでなかったら実行中プロセスとしてidle_lwpを登録

	percpu_init_cpu(ci);

	softint_init(ci);

softintの初期化

	callout_init_cpu(ci);

calloutの初期化

	xc_init_cpu(ci);
	pool_cache_cpu_init(ci);
	selsysinit(ci);

	cache_cpu_init(ci);

多分キャッシュまわり?

	TAILQ_INIT(&ci->ci_data.cpu_biodone);
	ncpu++;
	ncpuonline++;

	return 0;
}

キューを初期化して、CPUの数をカウントして終わり。