👋

smp boot kernel-6.0

2023/01/18に公開

smp boot kernel-6.0
マルチコアCPUの初期化処理を見ていきます。

bringup_nonboot_cpus()でCPUの数だけcpu_up()しています。
for_each_present_cpuでCPU個数文ループしています。

kernel/cpu.c
void bringup_nonboot_cpus(unsigned int setup_max_cpus)
{
        unsigned int cpu;

        for_each_present_cpu(cpu) {
                if (num_online_cpus() >= setup_max_cpus)
                        break;
                if (!cpu_online(cpu)) {
                        cpu_up(cpu, CPUHP_ONLINE);
                }
        }
}

実行する初期化コードの設定です。
setup_real_mode関数 start_eipのアドレス設定

arch/x86/realmode/init.c
static void __init setup_real_mode(void)
{
	u16 real_mode_seg;
	const u32 *rel;
	u32 count;
	unsigned char *base;
	unsigned long phys_base;
	struct trampoline_header *trampoline_header;
	size_t size = PAGE_ALIGN(real_mode_blob_end - real_mode_blob);

	base = (unsigned char *)real_mode_header;

	memcpy(base, real_mode_blob, size);
	
	trampoline_header->start = (u64) secondary_startup_64;
}

memcpy(base, real_mode_blob, size);
real_mode_headerにreal_mode_blobをコピーしています。
このreal_mode_blobの中にCPU初期化コードが入っています。

  • real_mode_blobがどのように作られているのか見ていきます。

real_mode_blobはarch/x86/realmode/rmpiggy.Sで定義されています。

arch/x86/realmode/rmpiggy.S
SYM_DATA_START(real_mode_blob)
        .incbin "arch/x86/realmode/rm/realmode.bin"
SYM_DATA_END_LABEL(real_mode_blob, SYM_L_GLOBAL, real_mode_blob_end)

real_mode_blobの中身はrealmode.binです。
.incbinでバイナリファイルを取り込んでいます。

次にrealmode.binを見ていきます。
realmode.binは以下のスクリプトで生成されます。

arch/x86/realmode/rm/.realmode.bin.cmd
make -f ./scripts/Makefile.build obj=arch/x86/realmode/rm   arch/x86/realmode/rm/realmode.bin

objcopy  -O binary arch/x86/realmode/rm/realmode.elf arch/x86/realmode/rm/realmode.bin

objcopyでファイル変換しています。

$ file arch/x86/realmode/rm/realmode.elf
arch/x86/realmode/rm/realmode.elf: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
$ file arch/x86/realmode/rm/realmode.bin
arch/x86/realmode/rm/realmode.bin: data

realmode.elfはELF実行フォーマットですが、realmode.binはdataとなっています。
realmode.elfは

arch/x86/realmode/rm/.realmode.elf.cmd
cmd_arch/x86/realmode/rm/realmode.elf := 
ld -m elf_x86_64 -z noexecstack --no-warn-rwx-segments  -m elf_i386 --emit-relocs 
-T arch/x86/realmode/rm/realmode.lds 
arch/x86/realmode/rm/header.o 
arch/x86/realmode/rm/trampoline_64.o arch/x86/realmode/rm/stack.o 
arch/x86/realmode/rm/reboot.o arch/x86/realmode/rm/wakeup_asm.o 
arch/x86/realmode/rm/wakemain.o arch/x86/realmode/rm/video-mode.o 
arch/x86/realmode/rm/copy.o arch/x86/realmode/rm/bioscall.o 
arch/x86/realmode/rm/regs.o arch/x86/realmode/rm/video-vga.o 
arch/x86/realmode/rm/video-vesa.o arch/x86/realmode/rm/video-bios.o 
-o arch/x86/realmode/rm/realmode.elf

ようやく本体が表示されました。
real_mode_elfにはこのコードが埋め込まれています。
header.oとreal_mode_headerの型が一致している必要があるわけですね。

real_mode_headerはBSS領域に確保されています。
real_mode_headerはreserve_real_modeで初期化されています。

/arch/x86/realmode/init.c
static inline void set_real_mode_mem(phys_addr_t mem)
{
	real_mode_header = (struct real_mode_header *) __va(mem);
}


void __init reserve_real_mode(void)
{
	phys_addr_t mem;
	size_t size = real_mode_size_needed();

	/* Has to be under 1M so we can execute real-mode AP code. */
	mem = memblock_phys_alloc_range(size, PAGE_SIZE, 0, 1<<20);
	if (!mem)
		pr_info("No sub-1M memory is available for the trampoline\n");
	else
		set_real_mode_mem(mem);

}

確保したメモリ領域をそのままreal_mode_headerに割り当てています。
コメントにもあるようにリアルモードなので1M以下にするようにとあります。

いままでが準備で、ここからがCPU初期化の本番になります。

/arch/x86/kernel/smpboot.c
static int do_boot_cpu(int apicid, int cpu, struct task_struct *idle,
		       int *cpu0_nmi_registered)
{
	if (apic->wakeup_secondary_cpu)
		boot_error = apic->wakeup_secondary_cpu(apicid, start_ip);
	else
		boot_error = wakeup_cpu_via_init_nmi(cpu, start_ip, apicid,
						     cpu0_nmi_registered);
}

BSPの場合にはwakeup_cpu_via_init_nmi(),
APの場合にはapic->wakeup_secondary_cpu()を実行します。

BSPとはBoot Processer(CPU0)で、APとはそれ以外のCPU(非ブートCPU)のことです。

NMIはCPU0にしか使われないので、もしCPU0がNMIによって起こされたら、NMIによって中断されたeipに戻ることになります。

なぜならcpu0をwakeupするためのnmi_handlerは何もしないからです。
NMIから戻った後の次のコードが本当に重要です。

static int
wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
	       int *cpu0_nmi_registered)
{
	int id;
	int boot_error;

	preempt_disable();

	/*
	 * Wake up AP by INIT, INIT, STARTUP sequence.
	 * INIT, INIT, STARTUPシーケンスを実行するとBIOSのブートストラップコードに
	 * ジャンプしてします。
	 * BSPを起動するのに望ましくないため、その次のNMIでCPU0をWake upします。
	 */
	if (cpu) {
		boot_error = wakeup_secondary_cpu_via_init(apicid, start_ip);
		goto out;
	}

	/*
	 * Wake up BSP by nmi.
	 *
	 * Register a NMI handler to help wake up CPU0.
	 */
	boot_error = register_nmi_handler(NMI_LOCAL,
					  wakeup_cpu0_nmi, 0, "wake_cpu0");

	if (!boot_error) {
		enable_start_cpu0 = 1;
		*cpu0_nmi_registered = 1;
		id = apic->dest_mode_logical ? cpu0_logical_apicid : apicid;
		boot_error = wakeup_secondary_cpu_via_nmi(id, start_ip);
	}

out:
	preempt_enable();

	return boot_error;
}

次にAPの起動を見ていきます。
wakeup_cpu_via_initはAPにSTARTUP IPI(SIPI)を送ります。

APがSIPIを受信すると、まずrealmodeに切り替わります。
そしてSIPIパラメータで指定されたアドレスにジャンプします。
次の物理アドレスにジャンプします。

unsigned long start_ip = real_mode_header->trampoline_start;

real_mode_headerはカーネルコンパイル時にスクリプトによって埋め込まれます。

arch/x86/realmode/rm/header.S
.section ".header", "a"

	.balign	16
GLOBAL(real_mode_header)
	.long	pa_text_start
	.long	pa_ro_end
	/* SMP trampoline */
	.long	pa_trampoline_start
	.long	pa_trampoline_status
	.long	pa_trampoline_header

APはtrampoline_startにジャンプします。

arch/x86/realmode/rm/trampoline_64.S
.text
	.code16

	.balign	PAGE_SIZE
ENTRY(trampoline_start)
	cli			# We should be safe anyway
	wbinvd
        # Setup stack
        # Enable protected mode
        # Enable paging and in turn activate Long Mode
	.balign	8
GLOBAL(trampoline_header)
	tr_start:		.space	8
}

このコードでプロテクトモードを有効にします。
そして最後に trampoline_header->tr_start(8bytes address) に格納されているアドレスにジャンプします。

void __init setup_real_mode(void)
{
	trampoline_header->start = (u64) secondary_startup_64;
}

secondary_startup_64にジャンプするわけです。
この時点で、PAEモードとPGEを有効にし、初期ブートステージ4レベルのページテーブルをセットアップします。

ブートCPUが行うように、最後にinitial_codeにジャンプします。
x86_64_start_kernel から do_boot_cpu の start_secondary に変更されています。

https://www.twblogs.net/a/5e4e44b1bd9eee101df4880e

Discussion