ReactOS操作系统启动代码分析(I386架构)(2)

这里紧跟上一篇谈到的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  
5.	    mov ds, ax  
6.	    mov es, ax  
7.	    mov fs, ax  
8.	    mov gs, ax  
9.	    mov ss, 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。并且这个函数不会有返回值(假设有返回值的话,整个系统该重启或者陷入死循环)。

你可能感兴趣的:(windows)