【我所認知的BIOS】—>實模式&保護模式切换实例

【我所認知的BIOS—>實模式&保護模式切换实例

LightSeed

2009-6-23

上一章從基礎概念上我談了談我的理解。素不知“紙上得來終覺淺,絕知此事要躬行”呀!不多說二話,我們來詳細剖析實模式和保護模式的相互切換。(我儘量解釋清楚每一句話,以下舉的例子是楊季文老師書里一個最簡單的例子)

Let us go!

1、這個例子中code做的事

下麵我們看到的這個code做了這樣的操作:

在實模式進入保護模式

在保護模式里把高端內存的value copy到低端內存中來(buffer中)

返回到實模式

在實模式下,顯示buffer中的內存數值。

對此需要說明的一點是這個例子是一個很簡單很簡單地實模式和保護模式的切換,中間很多東西都沒有考慮,不過拿來作為我們學習保護模式的入門我想到是再好不過了。

2、原程式加詳細註釋

;-------------这段代码copy form<80X86汇编语言程序设计教程>--------

;-------------只是一个最简单的实模式与保护模式的相互切换----------

;-------------切忌,本程序编译连接后生成的exe文件在纯DOS--------

;-------------才能够执行LGDT这个命令,才能顺利进入保护模式--------

;-------------宏定义区域开始--------------------------------------

;16位偏移的段间直接转移指令的宏定义

JUMP macro selector,offsetv

db 0eah ;操作码 jmp

dw offsetv;16位偏移

dw selector ;段值(real mode)或者选择子(protect mode)

endm

;字符显示宏指令的定义

ECHOCH macro ascii

mov ah, 2 ;选功能号

mov dl, ascii ;填将要显示的ASCII码给DL

int 21h ;调用DOS中断来显示ASCII

endm

;-------------宏定义区域结束-------------------------------------

;-------------结构体定义区域开始---------------------------------

;存储段描述符结构类型的定义

DESCRIPTOR struc

Limitl dw 0 ;段界限(0~15)

Basel dw 0 ;段基地址(0~15)

Basem db 0 ;段基地址(16~23)

Attributes dw 0 ;段属性

Baseh db 0 ;段基地址(24~31)

DESCRIPTOR ENDS

;伪描述符结果类型的定义

PDESC struc

Limit dw 0 ;16界限

Base dd 0 ;基地址

PDESC ENDS

;-------------结构体定义区域结束---------------------------------

;常量定义

ATDW = 92H ;存在的可读写数据段属性值

ATCE = 98H ;存在的只执行代码段属性值

.386P

;--------------实模式下数据段定义开始----------------------------

dseg segment use16

align 16 ;16位段

GDT label byte ;全局描述符表GDT标志

DUMMY DESCRIPTOR <> ;空描述符【鉴于仅仅说明实模式与保护模式切换,为什么为空如果有兴趣我们再探讨】

CODE DESCRIPTOR <0FFFFH,,,ATCE,>;代码段描述符

CDDE_SEL = CODE - GDT ;代码段描述的选择子

DATAS DESCRIPTOR <0FFFFH,8eacH,20H,ATDW,0>;源数据段描述符

DATAS_SEL = DATAS - GDT ;源数据段描述符的选择子

DATAD DESCRIPTOR <0FFFFH,,,ATDW,>;目标数据段描述符

DATAD_SEL = DATAD - GDT ;目标数据段描述符的选择子

GDTLEN = $ - GDT ;全局描述符表长度

;

VGDTR PDESC <GDTLEN-1,>;①伪描述符

;

BUFFERLEN = 256 ;缓冲区字节长度

BUFFER DB BUFFERLEN DUP(0);缓冲区

dseg ends

;---------------实模式下数据段定义结束---------------------------

;---------------实模式下代码段定义开始---------------------------

cseg segment use16 ;16位段

assume cs:cseg,ds:dseg ;声明代码段和数据段

start:

mov ax,dseg

mov ds,ax ;初始化数据段

;准备要加载到GDTR的伪描述符

mov bx,16 ;乘数为16,是为了在实模式中计算地址

mul bx ;计算并设置GDT基地址

add ax,offset GDT ;此时AX中为GDT在实模式中的地址,界限在已定义时设置妥当

adc dx,0 ;如果有进位那么ADC加上

mov word ptr VGDTR.Base,ax;填入GDT的实际地址的低word到伪描述符结构体中

mov word ptr VGDTR.Base+2,dx ;填入GDT的实际地址的高word到伪描述符结构体中

;设置代码段描述符

mov ax,cs

mul bx ;计算代码段在实模式中的实际地址

mov code.Basel,ax ;代码段开始偏移为0

mov code.Basem,dl ;代码段界限已在定义时设置妥当

mov code.Baseh,dh

;设置目标数据段描述符

mov ax,ds

mul bx ;计算数据段在实模式中的实际地址

;计算并设置目标数据段基地址

add ax,offset BUFFER ;加上offset

adc dx,0 ;如果有进位那么ADC加上

mov DATAD.Basel,ax ;不解释了,同上

mov DATAD.Basem,dl

mov DATAD.Baseh,dh

;加载GDTR

DB 66H ; execute a 32 bit LGDT

LGDT VGDTR ;命令不熟悉的话去查查

cli ;关中断

call EnableA20 ;打开地址线A20

;切换到保护模式

mov eax,cr0

or eax,1

mov cr0,eax

;清指令预取队列,并真正进入保护方式

JUMP <CDDE_SEL>,<offset VIRTUAL> ;far jmp目的是显示地修改CS,刷新段选择子的hidden部分(此步骤后面我会详细回头探讨)

VIRTUAL:

MOV AX,DATAS_SEL ;源数据段选择子

MOV DS,AX ;加载源数据段描述符

MOV AX,DATAD_SEL ;源数据段选择子

mov es,ax ;加载目标数据段描述符

cld ;指针(si & di)累加

xor si,si ;设置指针初值

xor di,di

mov cx,BUFFERLEN/4 ;设置4字节为单位的缓冲区长度

repz movsd ;传送

;切回到实方式

mov eax,cr0

and eax,0fffffffeh

mov cr0,eax

;清指令预取队列,进入实方式

;JUMP <seg REAL>,<OFFSET REAL> ;far jmp目的是显示地修改CS,刷新段寄存器的hidden部分(此步骤后面我会详回头探讨)

PUSH ss

push sp

REAL: ;现在又回到实方式

call DisableA20 ;③关闭地址线A20

sti ;开中断

mov ax,dseg ;重置数据段寄存器

mov ds,ax

mov si,offset BUFFER

cld ;显示缓冲区内容

mov bp,BUFFERLEN/16 ;bp作为显示的行数的计数器

Nextline:

mov cx,16 ;每行只显示16*2个字符

NextCH:

lodsb ;mov al,ds:[si]si++

push ax ;保存ax

shr al,4 ;准备显示高4bit中的值

call ToASCII ;al中的值转换成ASCII

ECHOCH al ;显示之

pop ax ;回复ax

call ToASCII

ECHOCH al

ECHOCH ' ' ;在字符于字符之间显示空格

loop NextCH ;处理下一个字符

ECHOCH 0dh

ECHOCH 0ah ;显示这两个ASCII回车+换行

dec bp ;显示完了否?

jnz Nextline ;bp = 0显示完了

mov ax,4c00h ;结束

int 21h

;---------------实模式下代码段定义结束--------------------------

;---------------子程式定义开始----------------------------------

;***************************************************************

;子程序名 HtoASC

; :十六进制数转换成 ASCII

;入口参数 al=8位二进制数

;出口参数 :无

; :无

;***************************************************************

toASCII proc

and al,0fh ;屏蔽al的高4 bits

cmp al,9 ;compare 9

jbe toASCII1 ;小于9,直接+30H

add al,37h ;否则,al+37H

ret

toASCII1:

add al,30h

ret

toASCII endp

;***************************************************************

;打开a20地址线

;***************************************************************

EnableA20 proc

push ax

in al,92h

or al,00000010b

out 92h,al

pop ax

ret

EnableA20 endp

;***************************************************************

;关闭a20地址线

;***************************************************************

DisableA20 proc

push ax

in al,92h

and al,11111101b

out 92h,al

pop ax

ret

DisableA20 endp

;---------------子程式定义结束----------------------------------

cseg ends

;---------------实模式下代码段定义结束--------------------------

end start ;指明程序入口

3、說明程序中我標記(一定需要說明的)

3.1 ①伪描述符

爲什麽我們要專門拿一個結構體來給LDGT這個命令用呢?原因是這樣的,由於GDT不能由GDT本身之類的描述符進行描述定義,所以處理器採用GDTRGDT這一特色的系統段提供一個偽描述符。它的數據格式如圖1

1 GDTR給定GDT的地址

在用GDTR的是,我們必須要注意到,由於CPU取的時候是以word的形式來讀取的,所以GDTR的地址必須是以word對齊的。

那麼我們怎麼用這個GDTR呢?用programming guide里的原話說“Before the GDT can be used, the base address and limit for the GDT must be loaded into the GDTR register using an LGDT instruction

並且我們在用LGDT這個命令的時候必須要始終保證是word對齊的。於是我們就用一個結構體來存儲這個特殊的偽描述符了。(當然啦,你是可以不用定義結構體的,只要能夠讓CPU識別的到就沒有任何問題啦。)

3.2 far jmp的作用

3.2.1 提出疑問,爲什麽要用far jmp

爲什麽我要專門把這個提出來呢?是因為在當時學的時候,就有個疑問。“爲什麽在programming guide”裏面和《80X86彙編語言程序設計教程》里都“淺淺”地說,“清指令預取隊列(prefetch queue)”。

3.2.2 prefetch queue的實質

我們再詳細查資料可以知道,在mov cr0,eax执行后,处理器实际上已经处于保护模式。然而,Prefetch Queue值仍为实模式下的值。(其中最重要的是cs)注意,段寄存器不仅包括16位可见部分,还包括48位高速缓冲(也就是我们programming guide里的,“visible” part and a “hidden” part)。在进入保护模式后,这些段寄存器高速缓冲中的内容仍然存在,而且CPU为了其执行指令的效率只要CS或者其他的DS等等段寄存器如果没有改变,那么CPU都直接从这些高速缓冲器中去取CSDS的值。(但是进入保护模式后,CPU预取的指令却全是实模式下的地址,当然会在保护模式下寻址时出问题。)那么我们就需要改变CS等等段寄存器的值(不過在保护模式下应该叫做段选择子了)需要显式(就是直接修改CS的意思)地重新加载。加载其他段寄存器可以mov,但是只有

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值