Xen源代码分析(一)——head.shttp://www.cnblogs.com/bulllbat/archive/2012/11/15/2770979.html 启动汇编部分代码是xen 的引导启动程序,位于./xen/arch/x86/boot目录下。代码描述了从xen加载到调用第一个C函数“__start_xen”之间的初始化 系统环境过程。主要涉及的文件流程为head.S->trampoline.S->x86_32.s,其中head.s为冲GRUB进入 XEN的入口文件,首先看看head.s部分都做了什么(只看32位体系)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 /* 只能由 grub 来引导,head.S 是从GRUB进入XEN 的入口文件; 开始执行的第一个汇编文件,包括初始化页表,解析早期命令行参数等工作 */ #include <xen/config.h> #include <xen/multiboot.h> #include <public/xen.h> #include <asm/asm_defns.h> #include <asm/desc.h> #include <asm/page.h> #include <asm/msr.h> .text .code32 /*当xen运行时,cpu已经处于保护模式了,和LINUX内核的处理方式一致,虚拟地址等于物理地址加上固定值*/ /*在xen\include\asm-x86\x86_32\page.h中有__XEN_VIRT_START的定义*/ #define sym_phys(sym) ((sym) - __XEN_VIRT_START) /** *xen 编译时的映像布局由xen\arch\x86\xen.lds.S 控制: ... #ifdef __x86_64__ #define FORMAT "elf64-x86-64" #else #define FORMAT "elf32-i386" #endif ENTRY(start) #endif OUTPUT_FORMAT(FORMAT, FORMAT, FORMAT) #ifdef __x86_64__ OUTPUT_ARCH(i386:x86-64) #else OUTPUT_ARCH(i386) #endif PHDRS { text PT_LOAD ; } SECTIONS { . = __XEN_VIRT_START + 0x100000; _start = .; .text : { _stext = .; //Text and read-only data *(.text) *(.text.cold) *(.text.unlikely) *(.fixup) *(.gnu.warning) _etext = .; //End of text section } :text = 0x9090 ... **/ /*根据INTEL手册GDT第一项无用,故而从0x08开始*/ /*ring0,code,32-bit mode*/ #define BOOT_CS32 0x0008 /*ring0,code,64-bit mode*/ #define BOOT_CS64 0x0010 /*ring0,data*/ #define BOOT_DS 0x0018 /*real-mode code*/ #define BOOT_PSEUDORM_CS 0x0020 /*5 real-mode data*/ #define BOOT_PSEUDORM_DS 0x0028 ENTRY(start) jmp __start .align 4 /*** MULTIBOOT HEADER ****/ #define MULTIBOOT_HEADER_FLAGS (MULTIBOOT_HEADER_MODS_ALIGNED | \ MULTIBOOT_HEADER_WANT_MEMORY) /* Magic number indicating a Multiboot header. */ . long MULTIBOOT_HEADER_MAGIC /* Flags to bootloader (see Multiboot spec). */ . long MULTIBOOT_HEADER_FLAGS /* Checksum: must be the negated sum of the first two fields. */ . long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) /** * 上面的定义是给grub看的,表明支持multiboot, 详细内容见multiboot协议 **/ .section .init.text, "ax" /*.asciz is just like .ascii, but each string is followed by a zero byte.*/ .Lbad_cpu_msg: .asciz "ERR: Not a 64-bit CPU!" .Lbad_ldr_msg: .asciz "ERR: Not a Multiboot bootloader!" bad_cpu: /*打印bad cpu错误*/ mov $(sym_phys(.Lbad_cpu_msg)),%esi # Error message jmp print_err not_multiboot: /*打印非多启动错误*/ mov $(sym_phys(.Lbad_ldr_msg)),%esi # Error message print_err: /*这里的打印用的是最基本的往显卡缓存写入数据的方式*/ mov $0xB8000,%edi # VGA framebuffer 1: mov (%esi),%bl test %bl,%bl # Terminate on '\0' sentinel 2: je 2b mov $0x3f8+5,%dx # UART Line Status Register 3: in %dx,%al test $0x20,%al # Test THR Empty flag je 3b mov $0x3f8+0,%dx # UART Transmit Holding Register mov %bl,%al out %al,%dx # Send a character over the serial line movsb # Write a character to the VGA framebuffer mov $7,%al stosb # Write an attribute to the VGA framebuffer jmp 1b gdt_boot_descr: /*GDT定义,传统模式下的全局描述符表寄存器(GDTR)长48位,由16位的界限和32位的基地址构成。由于段描述符总是8字节长,故界限的值应为8N-1。 Trampoline_gdt共定义了6个描述符项,界限是6*8-1。*/ .word 6*8-1 . long sym_phys(trampoline_gdt) __start: cld cli /* Initialise GDT and basic data segments. */ lgdt %cs:sym_phys(gdt_boot_descr) mov $BOOT_DS,%ecx mov %ecx,%ds mov %ecx,%es mov %ecx,%ss /* 验证并存储多重启动信息,详见“多重启动规范”。 当boot loader引导32位操作系统的时候,机器必须有如下的状态: EAX: 必须包含魔数0X2BADB002,这个值告诉操作系统目前它是由兼容的Multiboot 的boot loader引导的。 EBX: 必须包含boot loader提供的多重引导信息结构的32位物理地址。 CS: 必须是32位的读/执行的代码段,偏移是0以及界限是 0XFFFFFFFF。具体值没有定义。 SS: 必须是32位的读/执行数据段,偏移是0以及界限是 0XFFFFFFFF。具体值没有定义。 A20 GATE : 必须enable。 CR0: 31位(PG)必须清除,第0位(PE)必须设置。其他位没有定义。 EFLAGS: 第17(VM)位必须清除,第9位(IF)必须清除,其他位没有定义。 */ /* Check for Multiboot bootloader */ cmp $0x2BADB002,%eax jne not_multiboot /* Set up trampoline segment 64k below EBDA */ movzwl 0x40e,%eax /* EBDA segment */ cmp $0xa000,%eax /* sanity check (high) */ jae 0f cmp $0x4000,%eax /* sanity check (low) */ jae 1f 0: movzwl 0x413,%eax /* use base memory size on failure */ shl $10-4,%eax 1: sub $0x1000,%eax /* From arch/x86/smpboot.c: start_eip had better be page-aligned! */ xor %al, %al shl $4, %eax mov %eax,sym_phys(trampoline_phys) /* Save the Multiboot info struct (after relocation) for later use. */ mov $sym_phys(cpu0_stack)+1024,%esp push %ebx call reloc mov %eax,sym_phys(multiboot_ptr) /* Initialize BSS (no nasty surprises!) */ /*初始化BSS段,存放程序中未初始化的全局变量。 BSS段在xen\arch\x86\x86_32\xen.lds.S中定义*/ mov $sym_phys(__bss_start),%edi mov $sym_phys(_end),%ecx sub %edi,%ecx xor %eax,%eax rep stosb /* 查询并保存CPU拓展信息。 CPUID指令可提供关于处理器的实现及其能力的完整信息,任意特权级的软件都可以使用它。 EAX寄存器用于决定CPUID生成什么信息 EAX = 0x80000000,返回信息: EAX: Maximum Input Value for Extended Function CPUID Information. PIV之后的CPU,均大于0x80000000 EBX: Reserved ECX: Reserved EDX: Reserved EAX = 0x80000001,返回信息: EAX: Extended Processor Signature and Feature Bits. EBX: Reserved ECX: Bit 0: LAHF/SAHF available in 64-bit mode Bits 31-1 Reserved EDX: Bits 10-0: Reserved Bit 11: SYSCALL/SYSRET available (when in 64-bit mode) Bits 19-12: Reserved = 0 Bit 20: Execute Disable Bit available Bits 28-21: Reserved = 0 Bit 29: Intel? 64 Architecture available if 1 Bits 31-30: Reserved = 0 cpuid_ext_features在xen\arch\x86\boot\trampoline.S中定义。boot_cpu_data在\xen\include\asm-x86 \ processor.h中定义,是cpuinfo_x86的实例。 CPUINFO86_ext_features
在xen\arch\x86\x86_32 \ asm-offsets.c中定义:OFFSET(CPUINFO86_ext_features,
struct cpuinfo_x86, x86_capability[1]); OFFSET解释如下: #define offsetof (s, m) (size_t)&(((s*)0)->m) m为结构体s中的一项,返回m距结构体起始地址的偏移量。ANSI C中常数0允许转换成任何类型的指针,但转换后指针为NULL。例中&(((s*)0)->m)这一步,并不访问m元素,只是获取m的地址,编译时不生成访问m的代码。 #define DEFINE(_sym,_val) __asm__ __volatile__ (“\n->” #_sym “%0” #_val:: “i”(_val)) #是注释符号;%0是占位符,这里指代“i”(_val)。 #define OFFSET(_sym, _str, _mem) DEFINE(_sym, offsetof(_str, _mem)) 这条宏是将_str结构体的_mem项的偏移量赋值给_sym。 */ /* Interrogate CPU extended features via CPUID. */ mov $0x80000000,%eax cpuid xor %edx,%edx cmp $0x80000000,%eax # any function > 0x80000000? jbe 1f mov $0x80000001,%eax cpuid 1: mov %edx,sym_phys(cpuid_ext_features) mov %edx,sym_phys(boot_cpu_data)+CPUINFO86_ext_features #if defined(__x86_64__) /* Check for availability of long mode. */ bt $29,%edx jnc bad_cpu /* Initialise L2 identity-map and xen page table entries (16MB). */ mov $sym_phys(l2_identmap),%edi mov $sym_phys(l2_xenmap),%esi mov $sym_phys(l2_bootmap),%edx mov $0x1e3,%eax /* PRESENT+RW+A+D+2MB+GLOBAL */ mov $8,%ecx 1: mov %eax,(%edi) add $8,%edi mov %eax,(%esi) add $8,%esi mov %eax,(%edx) add $8,%edx add $(1<<L2_PAGETABLE_SHIFT),%eax loop 1b /* Initialise L3 identity-map page directory entries. */ mov $sym_phys(l3_identmap),%edi mov $(sym_phys(l2_identmap)+7),%eax mov $4,%ecx 1: mov %eax,(%edi) add $8,%edi add $PAGE_SIZE,%eax loop 1b /* Initialise L3 xen-map page directory entry. */ mov $(sym_phys(l2_xenmap)+7),%eax mov %eax,sym_phys(l3_xenmap) + l3_table_offset(XEN_VIRT_START)*8 /* Initialise L3 boot-map page directory entry. */ mov $(sym_phys(l2_bootmap)+7),%eax mov %eax,sym_phys(l3_bootmap) + 0*8 /* Hook identity-map, xen-map, and boot-map L3 tables into PML4. */ mov $(sym_phys(l3_bootmap)+7),%eax mov %eax,sym_phys(idle_pg_table) + 0*8 mov $(sym_phys(l3_identmap)+7),%eax mov %eax,sym_phys(idle_pg_table) + l4_table_offset(DIRECTMAP_VIRT_START)*8 mov $(sym_phys(l3_xenmap)+7),%eax mov %eax,sym_phys(idle_pg_table) + l4_table_offset(XEN_VIRT_START)*8 #else /*32位下2M页面大小,开PAE方式映射,在这里我们也看出32位内核为XEN需要开启PAE,初始化页表, 将线性空间的0-12M和__PAGE_OFFSET-__PAGE_OFFSET+12M都映射到物理地址的0-12M;而将线性空间的12M-16M映射到物理地址的12M-16M(注意,这时并没有启用分页机制): */ /* Initialize low and high mappings of memory with 2MB pages */ mov $sym_phys(idle_pg_table_l2),%edi mov $0xe3,%eax /* PRESENT+RW+A+D+2MB */ 1: mov %eax,__PAGE_OFFSET>>18(%edi) /* high mapping */ stosl /* low mapping */ add $4,%edi add $(1<<L2_PAGETABLE_SHIFT),%eax cmp $DIRECTMAP_PHYS_END+0xe3,%eax jne 1b 1: stosl /* low mappings cover up to 16MB */ add $4,%edi add $(1<<L2_PAGETABLE_SHIFT),%eax cmp $(16<<20)+0xe3,%eax jne 1b #endif /* Initialize 4kB mappings of first 2MB or 4MB of memory. */ mov $sym_phys(l1_identmap),%edi mov $0x263,%eax /* PRESENT+RW+A+D+SMALL_PAGES */ #if defined(__x86_64__) or $0x100,%eax /* GLOBAL */ #endif xor %ecx,%ecx 1: stosl add $4,%edi add $PAGE_SIZE,%eax inc %ecx /* VGA hole (0xa0000-0xc0000) should be mapped UC. */ cmp $0xa0,%ecx jne 2f or $0x10,%eax /* +PCD */ 2: cmp $0xc0,%ecx jne 2f and $~0x10,%eax /* -PCD */ 2: cmp $L1_PAGETABLE_ENTRIES,%ecx jne 1b sub $(PAGE_SIZE-0x63),%edi #if defined(__x86_64__) mov %edi,sym_phys(l2_identmap) mov %edi,sym_phys(l2_xenmap) mov %edi,sym_phys(l2_bootmap) #else mov %edi,sym_phys(idle_pg_table_l2) mov %edi,sym_phys(idle_pg_table_l2) + (__PAGE_OFFSET>>18) #endif /* Apply relocations to bootstrap trampoline. */ mov sym_phys(trampoline_phys),%edx mov $sym_phys(__trampoline_rel_start),%edi mov %edx,sym_phys(trampoline_phys) 1: mov (%edi),%eax add %edx,(%edi,%eax) add $4,%edi cmp $sym_phys(__trampoline_rel_stop),%edi jb 1b /* Patch in the trampoline segment. */ shr $4,%edx mov $sym_phys(__trampoline_seg_start),%edi 1: mov (%edi),%eax mov %dx,(%edi,%eax) add $4,%edi cmp $sym_phys(__trampoline_seg_stop),%edi jb 1b call cmdline_parse_early /* Switch to low-memory stack. */ mov sym_phys(trampoline_phys),%edi lea 0x10000(%edi),%esp lea trampoline_boot_cpu_entry-trampoline_start(%edi),%eax pushl $BOOT_CS32 push %eax /* Copy bootstrap trampoline to low memory, below 1MB. */ mov $sym_phys(trampoline_start),%esi mov $trampoline_end - trampoline_start,%ecx rep movsb /* Jump into the relocated trampoline. */ /*由上面的push代码段和IP后在这里执行ret相当于两个pop指令,直接跳转到trampoline.s中*/ lret #include "cmdline.S" reloc: #include "reloc.S" .align 16 .globl trampoline_start, trampoline_end /*第二阶段初始化,实模式*/ trampoline_start: #include "trampoline.S" trampoline_end: .text /*第三阶段初始化*/ __high_start: #ifdef __x86_64__ #include "x86_64.S" #else #include "x86_32.S" #endif Xen源代码分析(二)——trampoline.s汇编文件trampoline.s,为启动汇编程序第二阶段,主要工作为进入实模式,读取内存,磁盘,视频信息然后再次进入保护模式装入新的GDT(gdt_table),英文注释了很大部分,很容易理解。下面的代码注释中,从标号0开始运行,然后是标号1。 .code16/* NB. bootsym() is only usable in real mode, or via BOOT_PSEUDORM_DS. */ #undef bootsym /*bootsym(s)定义的是s的相对位置*/ #define bootsym(s) ((s)-trampoline_start) #define bootsym_rel(sym, off, opnd...) \ bootsym(sym),##opnd; \ 111:; \ .pushsection .trampoline_rel, "a"; \ .long 111b - (off) - .; \ .popsection #define bootsym_segrel(sym, off) \ $0,$bootsym(sym); \ 111:; \ .pushsection .trampoline_seg, "a"; \ .long 111b - (off) - .; \ .popsection .globl trampoline_realmode_entry trampoline_realmode_entry: mov %cs,%ax mov %ax,%ds movb $0xA5,bootsym(trampoline_cpu_started) cld cli lidt bootsym(idt_48) lgdt bootsym(gdt_48) mov $1,%bl # EBX != 0 indicates we are an AP xor %ax, %ax inc %ax lmsw %ax # CR0.PE = 1 (enter protected mode) ljmpl $BOOT_CS32,$bootsym_rel(trampoline_protmode_entry,6) idt_48: .word 0, 0, 0 # base = limit = 0 gdt_48: .word 6*8-1 .long bootsym_rel(trampoline_gdt,4) trampoline_gdt: /* 0x0000: unused */ .quad 0x0000000000000000 /* 0x0008: ring 0 code, 32-bit mode */ .quad 0x00cf9a000000ffff /* 0x0010: ring 0 code, 64-bit mode */ .quad 0x00af9a000000ffff /* 0x0018: ring 0 data */ .quad 0x00cf92000000ffff /* 0x0020: real-mode code @ BOOT_TRAMPOLINE */ .long 0x0000ffff .long 0x00009a00 /* 0x0028: real-mode data @ BOOT_TRAMPOLINE */ .long 0x0000ffff .long 0x00009200 .pushsection .trampoline_rel, "a" .long trampoline_gdt + BOOT_PSEUDORM_CS + 2 - . .long trampoline_gdt + BOOT_PSEUDORM_DS + 2 - . .popsection .globl cpuid_ext_features cpuid_ext_features: .long 0 .globl trampoline_xen_phys_start trampoline_xen_phys_start: .long 0 .globl trampoline_cpu_started trampoline_cpu_started: .byte 0 .code32 /*1: 从实模式跳转到这里运行,也就是正式进入保护模式*/ trampoline_protmode_entry: /* Set up a few descriptors: on entry only CS is guaranteed good. */ mov $BOOT_DS,%eax mov %eax,%ds mov %eax,%es /* Set up FPU. */ fninit /* Initialise CR4. */ mov $X86_CR4_PAE,%ecx mov %ecx,%cr4 /* Load pagetable base register. */ mov $sym_phys(idle_pg_table),%eax add bootsym_rel(trampoline_xen_phys_start,4,%eax) mov %eax,%cr3 /* Set up EFER (Extended Feature Enable Register). */ mov bootsym_rel(cpuid_ext_features,4,%edi) test $0x20100800,%edi /* SYSCALL/SYSRET, No Execute, Long Mode? */ jz .Lskip_efer movl $MSR_EFER,%ecx rdmsr #if CONFIG_PAGING_LEVELS == 4 btsl $_EFER_LME,%eax /* Long Mode */ btsl $_EFER_SCE,%eax /* SYSCALL/SYSRET */ #endif btl $20,%edi /* No Execute? */ jnc 1f btsl $_EFER_NX,%eax /* No Execute */ 1: wrmsr .Lskip_efer: mov $0x80050033,%eax /* hi-to-lo: PG,AM,WP,NE,ET,MP,PE */ mov %eax,%cr0 jmp 1f 1: #if defined(__x86_64__) /* Now in compatibility mode. Long-jump into 64-bit mode. */ ljmp $BOOT_CS64,$bootsym_rel(start64,6) .code64 start64: /* Jump to high mappings. */ mov high_start(%rip),%rax jmpq *%rax high_start: .quad __high_start #else /* !defined(__x86_64__) */ /* Install relocated selectors. */ lgdt gdt_descr/*正式装载初始化后的GDT,这里装入的GDT为汇编期间最终的GDT,在x86_32.s中定义*/ mov $(__HYPERVISOR_DS),%eax mov %eax,%ds mov %eax,%es mov %eax,%fs mov %eax,%gs mov %eax,%ss /*长跳转到x86_32.s的入口__high_start,该变量在head.s中定义,为x86_32.s的入口,x86_32.s 在head.s中以包含的方式调用*/ ljmp $(__HYPERVISOR_CS),$__high_start #endif .code32 /*0: 从head.s的ret指令跳转后首先到达这里开始执行,32位代码,此时还处在保护模式下*/ trampoline_boot_cpu_entry: cmpb $0,bootsym_rel(skip_realmode,5) jnz .Lskip_realmode /* Load pseudo-real-mode segments. */ mov $BOOT_PSEUDORM_DS,%eax mov %eax,%ds mov %eax,%es mov %eax,%fs mov %eax,%gs mov %eax,%ss /* Switch to pseudo-rm CS, enter real mode, and flush insn queue. */ mov %cr0,%eax dec %eax /*通过下面两个长跳转切换到实模式*/ ljmp $BOOT_PSEUDORM_CS,$bootsym(1f) .code16 1: mov %eax,%cr0 # CR0.PE = 0 (leave protected mode) /* Load proper real-mode values into %cs, %ds, %es and %ss. */ ljmp bootsym_segrel(1f,2) 1: mov %cs,%ax mov %ax,%ds mov %ax,%es mov %ax,%ss /* Initialise stack pointer and IDT, and enable irqs. */ xor %sp,%sp lidt bootsym(rm_idt)/*加载IDT*/ sti #if defined(__x86_64__) /* * Declare that our target operating mode is long mode. * Initialise 32-bit registers since some buggy BIOSes depend on it. */ movl $0xec00,%eax # declare target operating mode movl $0x0002,%ebx # long mode int $0x15 #endif /* * Do real-mode work: * 1. Get memory map. * 2. Get Enhanced Disk Drive (EDD) information. * 3. Set video mode. */ /*获得内存信息,该函数于mem.s中调用,内存信息存放于e820map变量中, 类似Linux内核的处理方式,调用0x15号中断*/ call get_memory_map /*调用于edd.s中*/ call get_edd /*调用于video.s中*/ call video /* Disable irqs before returning to protected mode. */ cli /* Reset GDT and IDT. Some BIOSes clobber GDTR. */ lidt bootsym(idt_48) lgdt bootsym(gdt_48) /* Enter protected mode, and flush insn queue. */ xor %ax,%ax inc %ax lmsw %ax # CR0.PE = 1 (enter protected mode) /* Load proper protected-mode values into all segment registers. */ /*跳转到32位代码,为进入保护模式做准备*/ ljmpl $BOOT_CS32,$bootsym_rel(1f,6) .code32 1: mov $BOOT_DS,%eax mov %eax,%ds mov %eax,%es mov %eax,%fs mov %eax,%gs mov %eax,%ss .Lskip_realmode: /* EBX == 0 indicates we are the BP (Boot Processor). */ xor %ebx,%ebx /* Jump to the common bootstrap entry point. */ jmp trampoline_protmode_entry/*跳转到保护模式入口*/ skip_realmode: .byte 0 rm_idt: .word 256*4-1, 0, 0 /*这三部分的内容不再这里详细写了*/ #include "mem.S" #include "edd.S" #include "video.S" #include "wakeup.S" ----------------------------------------------------------------------------------- Xen源代码分析(三)——x86_32.sX86_32.s文件,启动汇编程序的最后阶段,主要工作为装入堆栈指针, Xen会在栈顶分配一个cpu_info结构,这个结构包含很多重要的成员:1)客户系统的切换上下文2)当前运行的vcpu指针3)物理处理器编号. 1,IDT的处理,整个idt_table的向量入口都初始化ignore_int,这个中断处理函数打印"Unknown interrupt(cr2=XXXXXXXX)"信息后系统进入循环 2,如果是BSP,跳转到__start_xen否则,跳转到start_secondary .code32 |
|