NetBSD-5.0BETA/i386のSMP実装#4
どこから2コア目以降が起動するのか分かった(と思う)。
NetBSD-5.0BETA/i386のSMP実装#1 - かーねる・う゛いえむにっきでちょっと触れたmpbios_scan()の中で、lapic_boot_init()を呼んでいる箇所がある:
/* * looks like we've got a MP system. start setting up * infrastructure.. * XXX is this the right place?? */ #if NACPI > 0 if (mpacpi_ncpu == 0) { #endif lapic_base = LAPIC_BASE; if (mp_cth != NULL) lapic_base = (paddr_t)mp_cth->apic_address; #if NLAPIC > 0 lapic_boot_init(lapic_base); #endif #if NACPI > 0 } #endif
lapic_boot_init()はlapic_map()を呼ぶ:
/* * Initialize fixed idt vectors for use by local apic. */ void lapic_boot_init(paddr_t lapic_base) { lapic_map(lapic_base);
lapic_map()の最後で、MULTIPROCESSORならcpu_init_first()を呼んでいる:
#ifdef MULTIPROCESSOR cpu_init_first(); /* catch up to changed cpu_number() */ #endif
長ったらしいがもう少し続く。cpu_init_first()の頭でcpu_copy_trampoline()が呼ばれている:
/* * Runs once per boot once multiprocessor goo has been detected and * the local APIC on the boot processor has been mapped. * * Called from lapic_boot_init() (from mpbios_scan()). */ void cpu_init_first(void) { cpu_info_primary.ci_cpuid = lapic_cpu_number(); cpu_copy_trampoline();
このcpu_copy_trampoline()は2コア目以降が有効になった時のエントリアドレスに起動用コードを配置(コピー)している。
static void cpu_copy_trampoline(void) { /* * Copy boot code. */ extern u_char cpu_spinup_trampoline[]; extern u_char cpu_spinup_trampoline_end[]; vaddr_t mp_trampoline_vaddr; mp_trampoline_vaddr = uvm_km_alloc(kernel_map, PAGE_SIZE, 0, UVM_KMF_VAONLY); pmap_kenter_pa(mp_trampoline_vaddr, mp_trampoline_paddr, VM_PROT_READ | VM_PROT_WRITE); pmap_update(pmap_kernel()); memcpy((void *)mp_trampoline_vaddr, cpu_spinup_trampoline, cpu_spinup_trampoline_end - cpu_spinup_trampoline); pmap_kremove(mp_trampoline_vaddr, PAGE_SIZE); pmap_update(pmap_kernel()); uvm_km_free(kernel_map, mp_trampoline_vaddr, PAGE_SIZE, UVM_KMF_VAONLY); }
コピーの作業が終わった後でSMPが有効化され、各CPUはcpu_spinup_trampolineから起動する。
この関数の実体はarch/i386/i386/mptramp.Sにある:
.text .align 4,0x0 .code16 _C_LABEL(cpu_spinup_trampoline): cli xorw %ax,%ax movw %ax, %ds movw %ax, %es movw %ax, %ss data32 addr32 lgdt (gdt_desc) # load flat descriptor table movl %cr0, %eax # get cr0 orl $0x1, %eax # enable protected mode movl %eax, %cr0 # doit ljmpl $0x8, $mp_startup
起動用gdtをロード、cr0をクリアし32bitプロテクトモードへ切り替え、ljmplでコードセグメントをロード。
_TRMP_LABEL(mp_startup) .code32 movl $0x10, %eax # data segment movw %ax, %ds movw %ax, %ss movw %ax, %es movw %ax, %fs movw %ax, %gs movl $ (MP_TRAMPOLINE+PAGE_SIZE-16),%esp # bootstrap stack end, # with scratch space.. #ifdef MPDEBUG leal RELOC(cpu_trace),%edi #endif HALT(0x1) /* First, reset the PSL. */ pushl $PSL_MBO popfl movl RELOC(pmap_largepages),%eax orl %eax,%eax jz 1f movl %cr4,%eax orl $CR4_PSE,%eax movl %eax,%cr4 1: movl RELOC(mp_pdirpa),%ecx HALTT(0x5,%ecx) /* Load base of page directory and enable mapping. */ movl %ecx,%cr3 # load ptd addr into mmu movl %cr0,%eax # get control word # enable paging & NPX emulation orl $(CR0_PE|CR0_PG|CR0_NE|CR0_TS|CR0_EM|CR0_MP|CR0_WP),%eax movl %eax,%cr0 # and page NOW! #ifdef MPDEBUG leal _C_LABEL(cpu_trace),%edi #endif HALT(0x6) /* * ok, we are now running with paging enabled and sharing page tables * with cpu0. figure out which processor we really are, what stack we * should be on, etc. */ /* Don't touch lapic until BP has done init sequence. */ 1: movl _C_LABEL(cpu_starting),%ecx pause testl %ecx, %ecx jz 1b HALTT(0x7, %ecx) /* %ecx points at our cpu_info structure. */ movw $((MAXGDTSIZ*8) - 1), 6(%esp) # prepare segment descriptor movl CPU_INFO_GDT(%ecx), %eax # for our real gdt movl %eax, 8(%esp) lgdt 6(%esp) HALT(0x8) jmp 1f nop 1: HALT(0x12) movl $GSEL(GDATA_SEL, SEL_KPL),%eax #switch to new segment movl %eax,%ds movl %eax,%es movl %eax,%ss HALT(0x13) pushl $GSEL(GCODE_SEL, SEL_KPL) pushl $mp_cont HALT(0x14) lret
データセグメントをロード、どこかからスタックへのアドレスを算出してスタックポインタに設定、ページングを有効化、メインCPUとの待ち合わせを行ってこのCPUに用意されたGDTをロード、再度セグメントを切り替えmp_cont()のアドレスをスタックに積み、mp_cont()に向かってロングリターン。
mp_cont: HALT(0x15) movl CPU_INFO_IDLELWP(%ecx),%esi movl L_ADDR(%esi),%esi /* %esi now points at our PCB. */ HALTT(0x19, %esi) movl PCB_ESP(%esi),%esp movl PCB_EBP(%esi),%ebp HALT(0x20) /* Switch address space. */ movl PCB_CR3(%esi),%eax HALTT(0x22, %eax) movl %eax,%cr3 HALT(0x25) /* load segment registers. */ movl $GSEL(GCPU_SEL, SEL_KPL),%eax HALTT(0x26,%eax) movl %eax,%fs xorl %eax,%eax HALTT(0x27,%eax) movl %eax,%gs movl PCB_CR0(%esi),%eax HALTT(0x28,%eax) movl %eax,%cr0 HALTT(0x30,%ecx) pushl %ecx call _C_LABEL(cpu_hatch) HALT(0x33) jmp _C_LABEL(idle_loop) .data _C_LABEL(mp_pdirpa): .long 0 #ifdef MPDEBUG .global _C_LABEL(cpu_trace) _C_LABEL(cpu_trace): .long 0x40 .long 0xff .long 0xff #endif
CPU_INFO_IDLELWP(%ecx)がどうなってるのかよく分からないが、ともかくidle状態のlwpを取得して、その中にあるPCBからレジスタを復帰(初期化という方が正確か)、最後はreturnするのではなくcpu_hatchを呼びidle_loopへジャンプしている。
/* * The CPU ends up here when its ready to run * This is called from code in mptramp.s; at this point, we are running * in the idle pcb/idle stack of the new CPU. When this function returns, * this processor will enter the idle loop and start looking for work. */ void cpu_hatch(void *v) { struct cpu_info *ci = (struct cpu_info *)v; int s, i; #ifdef __x86_64__ cpu_init_msrs(ci, true); #endif cpu_probe(ci); ci->ci_data.cpu_cc_freq = cpu_info_primary.ci_data.cpu_cc_freq; /* cpu_get_tsc_freq(ci); */ KDASSERT((ci->ci_flags & CPUF_PRESENT) == 0); /* * Synchronize time stamp counters. Invalidate cache and do twice * to try and minimize possible cache effects. Note that interrupts * are off at this point. */ wbinvd(); atomic_or_32(&ci->ci_flags, CPUF_PRESENT); tsc_sync_ap(ci); tsc_sync_ap(ci); /* * Wait to be brought online. Use 'monitor/mwait' if available, * in order to make the TSC drift as much as possible. so that * we can detect it later. If not available, try 'pause'. * We'd like to use 'hlt', but we have interrupts off. */ while ((ci->ci_flags & CPUF_GO) == 0) { if ((ci->ci_feature2_flags & CPUID2_MONITOR) != 0) { x86_monitor(&ci->ci_flags, 0, 0); if ((ci->ci_flags & CPUF_GO) != 0) { continue; } x86_mwait(0, 0); } else { for (i = 10000; i != 0; i--) { x86_pause(); } } } /* Because the text may have been patched in x86_patch(). */ wbinvd(); x86_flush(); KASSERT((ci->ci_flags & CPUF_RUNNING) == 0); lcr3(pmap_kernel()->pm_pdirpa); curlwp->l_addr->u_pcb.pcb_cr3 = pmap_kernel()->pm_pdirpa; lcr0(ci->ci_data.cpu_idlelwp->l_addr->u_pcb.pcb_cr0); cpu_init_idt(); gdt_init_cpu(ci); lapic_enable(); lapic_set_lvt(); lapic_initclocks(); #ifdef i386 npxinit(ci); #else fpuinit(ci); #endif lldt(GSYSSEL(GLDT_SEL, SEL_KPL)); ltr(ci->ci_tss_sel); cpu_init(ci); cpu_get_tsc_freq(ci); s = splhigh(); #ifdef i386 lapic_tpr = 0; #else lcr8(0); #endif x86_enable_intr(); splx(s); x86_errata(); aprint_debug_dev(ci->ci_dev, "running\n"); }
cpu_hatch()では、CPUF_PRESENTフラグをci->ci_flagsに設定してtscを同期するなどcpu_boot_secondary()との待ち合わせ作業を行っている。
また、詳細はよく分からないがやりのこしの初期化作業を色々と行っている。
おそらくサブCPUがここを通過してメインCPUがcpu_boot_secondary_processors()を通過するとプロセスがサブCPUに割り当て可能になるのではないか?