06.实模式进入保护模式
简介
上一节我们实现了从内核加载器中加载其它扇区代码并执行,但始终工作在实模式状态下。内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1MB,最大分段64KB。
保护模式与实模式相比地址转换方式差异较大:
实模式下的地址转换方式,假设我们在ES中存入0x1000,DI中存入0xFFFF,那么ES:DI=0x1000*0x10+0xFFFF=0x1FFFF。
保护模式下ES:DI=全局描述符表中第0x200项描述符(一个描述符8个字节)给出的段基址+0xFFFF
1、描述符
保护模式下引入描述符来描述各种数据段,所有的描述符均为8个字节(0-7),由第5个字节说明描述符的类型,类型不同,描述符的结构也有所不同。若干个描述符集中在一起组成描述符表,而描述符表本身也是一种数据段,也使用描述符进行描述。“地址转换”由描述符表来完成,描述符表是一张地址转换函数表。
2、选择子
选择子是一个2字节的数,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全局描述符表)还是LDT(局部描述符表)进行,最高13位给出了所需的描述符在描述符表中的地址。(注:13位正好足够寻址8K项)有了以上三个概念之后可以进一步工作了,现在程序的运行与实模式下完全一样!各段寄存器仍然给出一个“段值”,只是这个“假段值”到真正的段地址的转换不再是“左移4位”,而是利用描述符表来完成。
3、80x86系列中引入了两个新寄存器GDTR和LDTR,其中GDTR用于表示GDT在内存中的段地址和段限(就是表的最大偏移上限),因此GDTR是一个48位的寄存器,其中32位表示段地址,16位表示段限。
目标
实现实模式到保护模式的切换需要使用汇编语言实现,boot.s文件修改如下
;能用于操作内存的寄存器只能是bx、bp、si、di
;0x7c00--0x7dff 这512字节用于启动区
;对内存的访问都必须指定段寄存器,没有显示指定时将使用ds作为段寄存器
org 0x7c00
LOAD_ADDR EQU 0x9000 ;内核加载偏移地址
mov ax,0
mov ss,ax
mov ds,ax
mov es,ax
mov bx,LOAD_ADDR
mov ch,1 ;柱面号
mov dh,0 ;磁头号
mov cl,2 ;扇区号
mov ah,0x02 ;0x02表示读盘操作
mov al,1 ;表示连续读取扇区数
mov dl,0 ;驱动器号,早期有多个软驱,一般只有一个写死0
int 0x13 ;调用BIOS实现磁盘读取
jc error ;读盘操作失败,flag标志寄存器cf 标志位被置1
jmp bx ;跳转到加载的内存地址开始执行代码
fin:
hlt
jmp fin
putloop:
mov al,[si]
inc si
cmp al,0
je fin
mov ah,0x0e ;中断调用参数
mov bx,15 ;字符颜色
int 0x10 ;中断调用号
jmp putloop
error:
mov si,errMsg
jmp putloop
errMsg:
db 'error'
db 0
kernel.s文件如下
;全局描述符结构 8字节
; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0
; byte6低四位和 byte1 byte0 表示段偏移上限
; byte7 byte4 byte3 byte2 表示段基址
;定义全局描述符数据结构
;3 表示有3个参数分别用 %1、%2、%3引用参数
;%1:段基址 %2:段偏移上限 %3:段属性
%macro GDescriptor 3
dw %2 & 0xffff
dw %1 & 0xffff
db (%1>>16) & 0xff
dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff)
db (%1>>24) & 0xff
%endmacro
DA_32 EQU 0x4000 ; 32 位段
DA_CODE EQU 0x98 ; 只执行代码段属性值
DA_RW EQU 0x92 ; 可读写数据段属性值
org 0x9000
jmp entry
[SECTION .gdt]
;定义全局描述符 段基址 段偏移上限 段属性
LABEL_GDT: GDescriptor 0, 0, 0
LABEL_DESC_CODE: GDescriptor 0, SegCodeLen-1, DA_CODE+DA_32
LABEL_DESC_VIDEO: GDescriptor 0xb8000, 0xffff, DA_RW
;gdt 表大小
GdtLen equ $-LABEL_GDT
;gdt表偏移上限和基地址
GdtPtr dw GdtLen-1
dd 0
;cpu开机进入实模式时使用的段寄存器 cs,ds,es,ss 和偏移地址组成内存地址,内存地址=段寄存器 * 16 + 偏移地址
;保护模式中段寄存器保存的是gdt 描述表中各个描述符的偏移,也叫选择子
SelectorCode32 EQU LABEL_DESC_CODE-LABEL_GDT
SelectorVideo EQU LABEL_DESC_VIDEO-LABEL_GDT
[SECTION .s16]
[BITS 16]
entry:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0x100
;设置LABEL_DESC_CODE描述符段基址
mov eax,0
mov ax,cs
shl eax,4
add eax,SEG_CODE32
mov word [LABEL_DESC_CODE+2],ax
shr eax,16
mov [LABEL_DESC_CODE+4],al
mov [LABEL_DESC_CODE+7],ah
mov eax,0
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr+2],eax
;设置GDTR 寄存器
lgdt [GdtPtr]
cli ;关闭可可屏蔽中断,如键盘中断
in al,0x92
or al,0x02
out 0x92,al
mov eax,cr0
or eax,1
mov cr0,eax
jmp dword SelectorCode32:0
[SECTION .s32]
[BITS 32]
SEG_CODE32:
mov ax,SelectorVideo
;gs 寄存器是80386新增的辅助段寄存器
mov gs,ax
;在屏幕中间显示字符串,屏幕为每行80个字符,共25行。低字节为ascii字符编码,高字节为字符显示属性
;可参考 《汇编语言》 王爽,屏幕显示相关章节
;在屏幕11行20列开始显示字符
mov ax,(80*11+20)
mov ecx,2
mul ecx
mov edi,eax
mov ah,00000010b
mov si,msg
putloop:
mov al,[esi]
cmp al,0
je fin
mov [gs:edi],ax
add edi,2
inc esi
jmp putloop
fin:
hlt
jmp fin
msg:
db 'protected mode',0
SEG_CODE32_END: nop
;32为模式代码长度
SegCodeLen EQU SEG_CODE32_END-SEG_CODE32
使用nasm 分别编译boot.s 、kernel.s 生成boot.bat和kernel.bat
使用C语言软盘功能模块制作虚拟软盘,main.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "floppy.h"
int main(int argc,char **argv){
FILE *fp = initFloppy("floppy.img");
if(fp == NULL) {
printf("初始化磁盘失败");
exit(0);
}
FILE *src = fopen("boot.bat", "r");
if(src == NULL) {
printf("文件打开失败");
exit(0);
}
char buf[512];
memset(buf, 0, 512);
fread(buf, 512, 1, src);
buf[510] = 0x55;
buf[511] = 0xaa;
writeFloppy(0, 0, 1, fp, buf);
fclose(src);
memset(buf, 0, 512);
src = fopen("kernel.bat", "r");
if(src == NULL) {
printf("文件打开失败");
exit(0);
}
fread(buf, 512, 1, src);
writeFloppy(1, 0, 2, fp, buf);
fclose(src);
fclose(fp);
return 1;
}
使用C语言文件操作boot.bat、kernel.bat,生成floppy.img 虚拟软盘文件,VirtualBox虚拟机加载该软盘文件效果如下:
我们成功从实模式进入保护模式,并在保护模式下实现字符显示!
补充
在内存地址空间中,B8000H~BFFFFH共32KB的空间,为8025彩色字符模式的显示缓冲区,向这个地址空间写数据,写入的内容将立即出现在显示器上.
在8025彩色字符模式下,显示器可以显示25行,每行80个字符,每个字符可以有256种属性(背景色,闪烁,高亮等组合信息).
这样一个字符在显示缓冲区中就要占两个字节,分别存放字符的ASCII码和属性,80*25模式下,一屛内容在显示缓冲区中占4000个字节.
显示缓冲区分为8页,每页4KB,显示器可以任意显示任意一页的内容,一般情况下,显示第0页的内容,也就是B8000~B8F9FH中的4000个字节.
颜色属性字节格式:
7 6 5 4 3 2 1 0
BL R G B I R G B
闪烁 背景(rgb) 高亮 前景(rgb)