# Start the first CPU: switch to 32-bit protected mode, jump into C. # The BIOS loads this code from the first sector of the hard disk into # memory at physical address 0x7c00 and starts executing in real mode # with %cs=0 %ip=7c00.
# Zero data segment registers DS, ES, and SS. xorw %ax,%ax # Set %ax to zero movw %ax,%ds # -> Data Segment movw %ax,%es # -> Extra Segment movw %ax,%ss # -> Stack Segment
# 硬件相关,主线是OS的启动,该部分不深究也没影响,其实就是一个固定步骤。 # Physical address line A20 is tied to zero so that the first PCs # with 2 MB would run software that assumed 1 MB. Undo that. seta20.1: inb $0x64,%al # Wait for not busy testb $0x2,%al jnz seta20.1
movb $0xd1,%al # 0xd1 -> port 0x64 outb %al,$0x64
seta20.2: inb $0x64,%al # Wait for not busy testb $0x2,%al jnz seta20.2
movb $0xdf,%al # 0xdf -> port 0x60 outb %al,$0x60
# Switch from real to protected mode. Use a bootstrap GDT that makes # virtual addresses map directly to physical addresses so that the # effective memory map doesn't change during the transition. lgdt gdtdesc movl %cr0, %eax orl $CR0_PE, %eax movl %eax, %cr0 ########################### 以下正式进入32位保护模式 //PAGEBREAK! # Complete the transition to 32-bit protected mode by using a long jmp # to reload %cs and %eip. The segment descriptors are set up with no # translation, so that the mapping is still the identity mapping. ljmp $(SEG_KCODE<<3), $start32
.code32 # Tell assembler to generate 32-bit code now. start32: # Set up the protected-mode data segment registers movw $(SEG_KDATA<<3), %ax # Our data segment selector movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %ss # -> SS: Stack Segment movw $0, %ax # Zero segments not ready for use movw %ax, %fs # -> FS movw %ax, %gs # -> GS
# Set up the stack pointer and call into C. movl $start, %esp # 这里将esp栈设置到了start,由于栈向低地址处增长,所以刚好和bootasm文件的代码背道而驰。 call bootmain
# If bootmain returns (it shouldn't), trigger a Bochs # breakpoint if running under Bochs, then loop. movw $0x8a00, %ax # 0x8a00 -> port 0x8a00 movw %ax, %dx outw %ax, %dx movw $0x8ae0, %ax # 0x8ae0 -> port 0x8a00 outw %ax, %dx spin: jmp spin
# Bootstrap GDT .p2align2 # force 4 byte alignment gdt: SEG_NULLASM # null seg SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg gdtdesc: .word (gdtdesc - gdt - 1) # sizeof(gdt) - 1 .long gdt # address gdt
# 开启分页 # Turn on paging. movl %cr0, %eax orl $(CR0_PG|CR0_WP), %eax movl %eax, %cr0 ########################### 以下正式进入分页模式,地址皆是虚拟地址 # 再一次修改esp指针,将esp移到内核代码范围中 # Set up the stack pointer. movl $(stack + KSTACKSIZE), %esp # 真正进入内核main函数,开始各种初始化。 mov $main, %eax jmp *%eax .comm stack, KSTACKSIZE
该部分代码负责进入main函数前的初始化,主要工作如下:
打开4M big page分页开关,让cpu支持4M大页。entrypgdir会将内核区域与映射为物理地址低4M的大页,我们后面会详细进行讨论,entrypgdir的生命周期非常短,在main函数中初始化过程中,会另外产生一个粒度更小的页表kpgdir(4K为一页),该页表会一直作为xv6的内核页表。