这里紧跟上一篇谈到的0x7C00开始执行,当然此时是在实模式下面,执行最初先禁止中断,因为现在堆栈都没有就算有中断也不能执行,所以第一步禁止中断,一直到设置好堆栈才打开中断。这一部分代码很简单,利用ax将段寄存器全部清零,然后调用切换到保护模式下面去,但是由于CS一直都在使用当中,所以就不需要初始化CS。不过要注意的是实际上进入switch_to_prot的时候,并没有进入保护模式,而是在 返回的时候才是执行保护模式下面的32位代码。
- 1. .code16
- 2. EXTERN(_RealEntryPoint)
- 3. cli
- 4. xor ax,ax
- 5. mov ds,ax
- 6. mov es,ax
- 7. mov fs,ax
- 8. mov gs,ax
- 9. mov ss,ax
- 10. mov sp,word ptr ds:stack16
- 11. sti
- 12. call switch_to_prot
- 13. .code32
- 14. xor eax,eax
- 15. mov dword ptr [_FrldrBootPartition],eax
- 16. mov byte ptr [_FrldrBootDrive],dl
- 17. mov byte ptr [_FrldrBootPartition],dh
- 18. call _EnableA20
- 19. xor eax,eax
- 20. mov edi,offset __bss_start__
- 21. mov ecx,offset __bss_end__ + 3
- 22. sub ecx,edi
- 23. shr ecx,2
- 24. rep stosd
- 25. push eax
- 26. call _BootMain
- 27. call switch_to_real
- 28. .code16
- 29. int HEX(19)
- 30. stop:
- 31. jmp stop
- 32. nop
- 33. nop
这一段重新初始化段寄存器,同样的落下CS不管。之所以要有这一段,是因为可能中间发生中断从而导致改变了段寄存器的值。从最后的pop指令可以看 出,实际上是为了和调用switch_to_prot函数之前的段寄存器保持一致,记住ds:[code32ret]这个地址,这里适用于返回到 _RealEntryPoint后面的32位代码部分。转向保护模式的过程只是转向实模式过程的逆过程。
首先介绍下什么叫做A20门,因为在最初的8086系统上面可以访问的数据是1M,通过数据段左移四位,加上便宜的得到 最终的数据,所以可以访问的数据是1M+64K-16B,但是由于整个系统只有20位地址线,所以超过1M的部分不能访问,系统会自动在超过1M的时候进 行回卷,也就是说访问1M地址的时候,实际上是访问0号地址,1M+1B的时候访问的是1号地址,依次类推。然而到了80286的时候可以访问的地址是 16M,所以这时就不能回卷了。然而需要兼容的话,这种回卷又必须存在——不然怎么仅仅只访问1M地址。所以,就提出在键盘控制器上面用一个寄存器位表明 是否需要回卷,当这个位被打开的时候,表明可以访问超过1M的地址。当这一位禁止的时候,表明只能访问1M内存。
- 1. EXTERN(switch_to_prot)
- 2. .code16
- 3. cli
- 4. xor ax,ax
- 10. pop word ptr ds:[code32ret]
- 11. mov word ptr ds:[stack16],sp
- 12. lgdt gdtptr
- 13. lidt i386idtptr
- 14. mov eax,cr0
- 15. or eax,CR0_PE_SET
- 16. mov cr0,eax
- 17. jmp far ptr PMODE_CS:inpmode
- 18. .code32
- 19. inpmode:
- 20. mov ax,PMODE_DS
- 21. mov ds,ax
- 22. mov es,ax
- 23. mov fs,ax
- 24. mov gs,ax
- 25. mov ss,ax
- 26. mov esp,dword ptr [stack32]
- 27. push dword ptr [code32ret]
- 28. ret
相关内容可以查询8042用户手册。在是能8042之后,就转向将BSS部分清空。
- 1. .code16
- 2. empty_8042:
- 3. .word 0x00eb,0x00eb // 这里是jmp $+2的指令码,因为指令码长度刚好是2,所以这里是对时间的一种消耗——也就是常说的等待操作
- 4. in al,HEX(64) //从地址64里面读取数据,这里的64位地址和内存地址不在同一个空间内,这里是8042的命令寄存器
- 5. cmp al,HEX(ff) // 判断8042是否空闲
- 6. jz empty_8042_ret // 如果控制器空闲,就跳出循环,否则继续等待
- 7. test al,2
- 8. jnz empty_8042
- 9. empty_8042_ret:
- 10. ret
- 11. EXTERN(_EnableA20)
- 12. .code32
- 13. pusha
- 14. call switch_to_real
- 15. .code16
- 16. call empty_8042
- 17. mov al,HEX(D1) // 发送消息,表明希望写输出缓冲寄存器
- 18. out HEX(64),al
- 19. call empty_8042 //等待设备就绪
- 20. mov al,HEX(DF) // 往寄存器当中写数据
- 21. out HEX(60),al
- 22. call empty_8042
- 23. call switch_to_prot
- 24. .code32
- 25. popa
- 26. ret
- 1. xor eax,eax //首先将eax清空
- 2. mov edi,offset __bss_start__ //首地址放到edi当中
- 3. mov ecx,offset __bss_end__ + 3 //末尾地址+3进行对齐操作
- 4. sub ecx,edi //得到整个BSS段的大小
- 5. shr ecx,2 //一次处理四位,所以这里右移四位除以四
- rep stosd //循环直到ECX等于0
最后函数的控制转入到bootmain,这是一个实实在在的C函数,函数原型为VOID BootMain(LPSTR CmdLine),这里传递进去的参数为0。并且这个函数不会有返回值(假设有返回值的话,整个系统该重启或者陷入死循环)。