前面,bochs加载代码清单1进入实模式,在显示屏上打印字符串,然后进入死循环。那么如何让实模式跳转到保护模式呢?那么就不得不说实模式和保护模式的寻址机制了。
1实模式寻址机制
由于实模式下只有20根地址线可用,它的寻址能力为:
1M =220* 8bit
由于每位地址指向一个字节8bit,所以后面我们乘于8bit。一个地址由段和偏移地址决定,计算公式如下:
物理地址(PhysicalAddress)=段值(Segment)* 16 +偏移(Offset)
段乘于16表示将段左移4位,然后和偏移构成20位的地址。注意的是,在实模式中段值和偏移都为16位。
2保护模式寻址机制
保护模式有32根地址线可用,它的寻址能力为:
4G =232* 8bit
保护模式下一个地址也是由段和偏移地址构成,但是段中存储的不是地址,而是GDT(会LDT)的索引值。通过索引值找到GDT中的数据项(段描述符),段描述符中才实际存储段地址值。保护模式寻址机制如图1所示。
图1保护模式寻址机制
3什么是GDT
既然从实模式进入保护模式涉及到GDT,那么就要了解GDT的结构。GDT实际就是容器,它存储的段描述符由8个字节构成。如图2所示
图2GDT中段描述符结构
4编写GDT段描述符
;宏------------------------------------------------------------------------------------------------------
;
;描述符
;usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macroDescriptor3
dw %2& 0FFFFh ;段界限1
dw %1& 0FFFFh ;段基址1
db (%1>> 16) & 0FFh ;段基址2
dw ((%2>> 8) & 0F00h) | (%3 & 0F0FFh) ;属性1+段界限2+属性2
db (%1>> 24) & 0FFh ;段基址3
%endmacro;
宏有3个参数,在宏体中用%1、2%、3%表示,1%为32位基址,2%为少于20位的段界限,3%为少于4位的属性。
宏中对三个参数进行了拆分组合生成8字节的段描述符。
5将段描述符放入GDT
[SECTION.gdt]
;GDT
;段基址,段界限 ,属性
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_CODE32:Descriptor 0,SegCode32Len- 1, DA_C + DA_32 ;非一致代码段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ;显存首地址
;GDT结束
在gdt段中,存储了3个段描述符。分别时空描述符LABEL_GDT、32位代码段描述符、视频段描述符。
[SECTION.s32]; 32位代码段.由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax,SelectorVideo
mov gs,ax ;视频段选择子(目的)
mov edi,(80 * 11 + 79) * 2 ;屏幕第11行,第79列。
mov ah,0Ch ; 0000:黑底 1100:红字
mov al,'P'
mov [gs:edi],ax
;到此停止
jmp $
SegCode32Len equ $- LABEL_SEG_CODE32
;END of [SECTION .s32]
其中在32位代码段描述符中,SegCode32Len– 1表示32位代码段的长度(即段界限),减去1是应为从0开始计算。
;GDT选择子
SelectorCode32 equ LABEL_DESC_CODE32 -LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO -LABEL_GDT
;END of [SECTION .gdt]
这里可以看出SelectorCode32、SelectorVideo为索引值,表示在GDT中的位置。从这里也可以猜测空描述符的作用。
6设置段描述符
段寄存器存放的是GDT的索引值,但是段描述符中的32位代码段基址为0,视频段为0B8000h。很明显32为代码段的地址并不是0,显存的代码段为0B8000h。在实模式下设置32位代码段基址。
[SECTION.s16]
[BITS 16]
LABEL_BEGIN:
mov ax,cs
……
;初始化32位代码段描述符
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODE32
mov word[LABEL_DESC_CODE32 + 2], ax
shr eax,16
mov byte[LABEL_DESC_CODE32 + 4], al
mov byte[LABEL_DESC_CODE32 + 7], ah
……
这里将代码寄存器cs(存放执行代码段地址)左移4位(段*16),然后和32位代码段LABEL_SEG_CODE32在执行代码段中的地址(偏移地址)相加。得到物理地址存放到eax中。然后将eax拆分组合放到LABEL_DESC_CODE32描述符中段基址地方。
现在段描述符都初始化好了,可以加载gdtr了。
7加载gdtr
GDTR是一个长度为48bit的寄存器,内容为一个32位的基地址和一个16位的段限。其中32位的基址是指GDT在内存中的地址。
[SECTION.gdt]
……
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
……
;GDT结束
GdtLen equ $- LABEL_GDT ; GDT长度
GdtPtr dw GdtLen- 1 ; GDT界限
dd 0 ;GDT基地址;为加载GDTR作准备
……
xor eax,eax
mov ax,ds
shl eax,4
add eax,LABEL_GDT ; eax <- gdt基地址
mov dword[GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt基地址
;加载GDTR
lgdt [GdtPtr];将GDT地址赋值给gdtr
代码将数据段赋值给ax,然后左移4位,加上GDT在程序中的偏移地址。这个值为GDT的物理地址。最后将这个地址赋值给dword[GdtPtr + 2]。
完成这些之后,再进行:
关闭实模式下中断
打开A20
设置cr0的PE位
跳转进入保护模式