源代码在arch文件夹的boot.s汇编文件中.
首先看一个宏,这个宏定义在x86comm.h头文件中,
#define EXTERN(x) .global x; x:
这句代码在汇编中展开就变味一条伪指令,指示x是一个全局变量,然后定义x的开始标号。
EXTERN(_ChainLoadBiosBootSectorCode)
.code32
call switch_to_real
.code16
movb (_FrldrBootDrive),%dl
cli
movw $0x0000,%bx
movw %bx,%ds
movw %bx,%es
movw %bx,%fs
movw %bx,%gs
movw %bx,%ss
movw $0x7C00,%sp
ljmpl $0x0000,$0x7C00
首先,整个处理器模式切换到实模式下面去(不知道是不是BIOS进行了模式转换),其中.code32标志32位模式,模式转换之后,将所有的段寄存器归零,当然CS是不能变的,然后跳转到CS为0,而偏移为0x7C00的地址去。这个地址是BIOS读入启动代码的存放地址。
#define HEX(y) 0x##y
#define NULL_DESC HEX(00)
#define PMODE_CS HEX(08)
#define PMODE_DS HEX(10)
#define RMODE_CS HEX(18)
#define RMODE_DS HEX(20)
在X86common.h文件中定义了四个16进制数用于表示不同模式下的代码段和数据段,每个数据的结构分析如下:
3-15 |
2位 |
0-1位 |
其中高13为表示在GDTR或者LDTR当中的索引,而第3位为0的时候表明选择子应该在GDTR当中去索引,如果为1则到LDTR当中索引。最后0-1位表明特权级别。微软只使用两个级别0级为系统级别,级别为3表示用户级别。
则保护模式的系统级别的代码段是GDTR当中的第一个选项,而保护模式下的系统级别的数据段是第二个选项,实模式下的系统代码段和数据段分别为3和4项。由于GDTR的选项零不能使用,所以就由NULL_DESC特殊指定出来。
gdtptr:
.word HEX(27) /* Limit */
.long gdt /* Base Address */
gdt:
/* NULL Descriptor */
.word HEX(0000)
.word HEX(0000)
.word HEX(0000)
.word HEX(0000)
/* 32-bit flat CS */
.word HEX(FFFF)
.word HEX(0000)
.word HEX(9A00)
.word HEX(00CF)
/* 32-bit flat DS */
.word HEX(FFFF)
.word HEX(0000)
.word HEX(9200)
.word HEX(00CF)
/* 16-bit real mode CS */
.word HEX(FFFF)
.word HEX(0000)
.word HEX(9E00)
.word HEX(0000)
/* 16-bit real mode DS */
.word HEX(FFFF)
.word HEX(0000)
.word HEX(9200)
.word HEX(0000)
GDTR组成
32位线性基地址 |
16位表长度 |
整个表项的描述如同上图,从这里可以看出实际上实模式下的段描述符只是内存限制和保护模式的限制不相同而已。
EXTERN(switch_to_real)
.code32
mov ax, PMODE_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
pop dword ptr [code16ret]
mov dword ptr [stack32], esp
ljmp RMODE_CS, switch_to_real16
switch_to_real16:
.code16
mov ax, RMODE_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and eax, CR0_PE_CLR
mov cr0, eax
jmp far ptr 0:inrmode
inrmode:
mov ax, cs
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
xor esp, esp
mov sp, word ptr ds:[stack16]
push word ptr ds:[code16ret]
lidt rmode_idtptr
sti
ret
代码首先设置保护模式下的段寄存器,因为将保护模式下的数据给提取出来。然后进行长跳转用于跳转到实模式下面的switch_to_real16。通过上面的分析可以知道,由于整个系统实模式和保护模式的基地址都是0,所以这里执行的就是下面的switch_to_real16,如果用普通的跳转指令jmp是不能在不同的段之间跳转的。进入switch_to_real16之后,所谓一朝天子一朝臣,又要重新开始设置段寄存器。然后读取控制寄存器cr0的数据,由于CR0的最低位是控制保护模式开关的关键,所以这里把这一位给清掉了。其中:
#define CR0_PE_SET HEX(00000001)
#define CR0_PE_CLR HEX(FFFFFFFE)
rmode_idtptr:
.wordHEX(3ff) /* Limit */
.long0 /* Base Address */
执行到这里之后,基本上就进入真正的系统实模式启动过程了。
EXTERN(_SoftReboot)
.code32
call switch_to_real
.code16
movw $0x40,%ax
movw %ax,%ds
movw $0x72,%si
movw $0x1234,(%si)
ljmpl $0xFFFF,$0x0000
上面是一段热重启的代码,首先向40:72处设置标志1234,用于区分是否是热启动还是冷启动。然后直接跳转到重启中断向量0x0ffff地址进行执行。4072地址应该是BIOS自身的一个内存地址(在网上还没找到这个地址的用处,但是由于寄存器掉电会丢失,所以这里应该只能是BIOS自身的端口地址)。