这里紧跟上一篇谈到的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位代码部分。转向保护模式的过程只是转向实模式过程的逆过程。
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首先介绍下什么叫做A20门,因为在最初的8086系统上面可以访问的数据是1M,通过数据段左移四位,加上便宜的得到 最终的数据,所以可以访问的数据是1M+64K-16B,但是由于整个系统只有20位地址线,所以超过1M的部分不能访问,系统会自动在超过1M的时候进 行回卷,也就是说访问1M地址的时候,实际上是访问0号地址,1M+1B的时候访问的是1号地址,依次类推。然而到了80286的时候可以访问的地址是 16M,所以这时就不能回卷了。然而需要兼容的话,这种回卷又必须存在——不然怎么仅仅只访问1M地址。所以,就提出在键盘控制器上面用一个寄存器位表明 是否需要回卷,当这个位被打开的时候,表明可以访问超过1M的地址。当这一位禁止的时候,表明只能访问1M内存。
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相关内容可以查询8042用户手册。在是能8042之后,就转向将BSS部分清空。
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。并且这个函数不会有返回值(假设有返回值的话,整个系统该重启或者陷入死循环)。