首先,从计算机硬件启动开始说起,开始启动的时候,CPU产生一个RESET,设定一些处理器,从ROM的0Xfffffff0开始执行,进行POST(上电自检),初始化硬件设备,紧接着搜索操作系统来启动,在这个过程中试图访问磁盘的第一个扇区也称为引导扇区(512个字节),只要能找到一个有效的磁盘,那就将这个扇区拷贝到内存0x00007c00开始的位置,跳转,加载这个扇区的代码,也就是bootloader。
在这个阶段,我们需要做的最主要的有3部,为载入内核的代码做好准备。首先bootloader开始的时候需要关闭中断,设置代码从地址递增的方向执行,对8042键盘进行读写操作,置位A20地址线(为了兼容8086遗留下来的原因),使得在保护模式的时候,21位地址不会一直为0.接着我们需要加载GDTR(GDT的大小和基地址),初始化全局描述符表。最后通过置位Cr0寄存器的PE位开启保护模式,然后通过段选择子跳转到内核代码开始的位置设置DS,ES,FS,GS,SS,以及BIOS的数据栈位0-0x7c00(ebp:0,esp:0x7c00).
关中断,初始化寄存器,然后使能了A20。
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al # 对0x64写入0xd1
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 对0x60写入0xdf使能了A20
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
.p2align 2 # force 4 byte alignment
gdt: #标识符 表示地址
SEG_NULLASM # null seg 第一段永远为0 不用的
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
gdtdesc: # GDTR寄存器 用lgdt加载
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
通过SEG_ASM设置全局描述符,这里只是简单的为内核的代码加载初始化GDT而已。