smp boot kernel-6.0
smp boot kernel-6.0
マルチコアCPUの初期化処理を見ていきます。
bringup_nonboot_cpus()でCPUの数だけcpu_up()しています。
for_each_present_cpuでCPU個数文ループしています。
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のアドレス設定
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で定義されています。
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は以下のスクリプトで生成されます。
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は
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で初期化されています。
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初期化の本番になります。
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はカーネルコンパイル時にスクリプトによって埋め込まれます。
.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にジャンプします。
.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 に変更されています。
Discussion