当前位置:首页 > 嵌入式 > 嵌入式软件
[导读]浅析在DOS实模式下直接存取4GB内存

作为软件开发人员,大多数对于保护模式都感到神秘和不易理解。本人在开发32位微内核抢占式多线程操作系统过程中,深入了解到CPU的地址机理,在这里将分析CPU的工作原理,解开保护模式的神秘面纱,读者将会发现保护模式其实与实模式一样简单和易于控制。在此基础上用四五十行C语言程序做到进出保护模式和在实模式之下直接访问整个4GB内存空间。

虽然有许多书籍对保护模式作解释,但没有一本能简单明了地解释清楚,冗长烦杂的术语让人看着想打瞌睡,甚至还有许多用汇编写的(可能根本不能运行的)保护模式试验程序,事实上用C语言本身就可以做保护模式的进出工作。

我们可能知道CPU上电后从ROM中的BIOS开始运行,而Intel文档却说80x86CUP上电总是从最高内存下16字节开始执行,那么BIOS是处在内存的最顶端64K(FFFF0000H)还是1M之下的64K(F0000H)处呢?事实上在这两个地方都同时出现(可用后面存取4GB内存的程序验证)。

为什么?为了弄清楚以上问题,首先要了解CPU是如何处理物理地址的?真的是在实模式下用段寄存器左移4位与偏移量相加,在保护模式下用段描述符中的基地址加偏移量而两者是毫无关联的吗?答案是两者其实是一样的。当Intel把80286推出时其地址空间变成了24位,从8086的20位到24位,十分自然地要加大段寄存器才行,实际上它们都被加大了,只是由于保护的原因加大的部分没有被程序看见,到了80386之后地址又从24位加大到32位(80386SX是24位)。整个段寄存器如下图所示:

@@12A08400.GIF;图1@@

在8086中CPU只有“看得见部分”,从而也直接参与了地址形成运算,但在80286之后,在“看不见部分”中已经包含了地址值,“看得见部分”就退化为只是一个标号再也不用参与地址形成运算了。地址的形成总是从“不可看见部分”取出基址值与偏移相加形成地址。也就是说在实模式下当一个段寄存器被装入一个值时,“看不见部分”的界限被设成FFFFH,基址部分才是要装入值左移4位,属性部分设成16位0特权级。这个过程与保护模式时装入一个段存器是同理的,只是保护模式的“不可见部分”是从描述表中取值,而实模式是一套固定的过程。

对于CPU在形成地址时,是没有实模式与保护模式之分的,它只管用基址(“不可见部分”)去加上偏移量。实模式与保护模式的差别实际上只是保护处理部件是否工作得更精确而已,比如不允许代码段的写入。实模式下的段寄存装入有固定的形成办法从而也就不需要保护模式的“描述符”了,因此保持了与8086/8088的兼容性。而“描述符”也只是为了装入段寄存器的“不可见部分”而设的。

从上面的“整个段寄存器”可见CPU的地址形成与“看得见部分”的当前值毫无关系,这也解释了为什么在刚进入保护模式时后面的代码依然被正确地运行而这时代码段寄存器CS的值却还是进入保护模式前的实模式值,或者从保护模式回到实模式时代码段CS被改变之前程序是正常地工作,而不会“突变”到CS左移4位的地址上去,比如在保护模式时CS是08H的选择器,到了实模式时CS还是08H但地址不会突然变成80H加上偏段量中去。因为地址的形成不理会段寄存器“看得见部分”的当前值,这一个值只是在被装入时对CPU有用。

地址的形成与CPU的工作模式无关,也就是说实模式与0特权级保护模式不分页时是一模一样的。明白了这一机理,在实模式下一样可以处理通常被认为只有在保护模式才能做的事,比如访问整个机器的内存。可以不必理会保护模式下的众多术语,或者更易于理解,如选择器就是“看得见部分”,描述符是为了装入“不可见部分”而设的。

作为验证CPU的这种机理,这里写了一个实模式下访问4GB内存的C程序。有一些书籍也介绍有同样功能的汇编程序,但它们都错误地认为是利用80386芯片的设计疏漏。实际上Intel本身就在使用这种办法,使得CPU上电时能从FFFFFFF0H处开始第一条指令,这种技术在286之后的每一台机器每一次冷启动时都使用,只是我们不知道罢了。CPU上电也整个代码段寄存器是这样的:

@@12A08401.GIF;图2@@

EIP=0000FFF0H

这样CS∶EIP形成了FFFFFFF0H的物理地址,当CPU进行一次远跳转重新装入CS时,基址就变了。

为了访问4G内存空间,必须有一个段寄存器的“不可见部分”的界限为4G-1,基址为0,这样就包含了4GB内存,不必理会可见部分的值。显然要让段寄存器在实模式下直接装入这些值是不可能的。唯一的办法是让CPU进入一会儿保护模式在装入了段寄存器之后马上回到实模式。

进入保护模式十分简单,只要建好GDT把CRO寄存器的位0置上1,CPU就在保护模式了,从前面所分析CPU地址形成机理可知,这时不必理会寄存器的“看得见部分”值是否合法,各种段寄存器是一样可用的,就像没进保护模式一样。在把一个包含有4GB地址空间的值装入某个段寄存器之后就可返回实模式。

预先可建好GDT如下:

unsigned long GDT-Table[]={0,0, //空描述符,必须为零0x0000FFFF,0xCF9A00, //32位平面式代码段0x0000FFFF,0xCF9200 } , //32位平面式数据段只是为了访问数据的话只要2个GDT就足够了,因为并没有重装代码段,这里给出3个GDT只是为了完整性。

通常在进入保护模式时要关闭所有的中断,把IDTR的界限设置为0,CPU自动关闭所有中断,包括NMI,返回实模式后恢复IDTR并开中断。

另外A20地址线的控制对于正确访问整个内存也很重要,在进入保护模式前要让8042打开A20地址线。

在这个例子里FS段寄存器设成可访问4GB内存的基址和界限,由于在DOS中很少有程序会用到GS、FS这两个386增加的段寄存器,当要读写4GB范围中的任一个地方都可通过FS段来达到,直到FS在实模式下被重装入冲掉为止。

这个例子在386SX、386DX、486上都运行通过。例子里加有十分详细的注释,由于这一程序是用BC 3.1编译连接的,而其连接器不能为DOS程序处理32位寄存器,所以直接在代码中加入操作码前缀0x66和地址前缀0x67,以便让DOS实模式下的16位程序可用32位寄存器和地址。程序的右边以注释形式给出等效的32位指令。要注意16位的指令中mov al, byte ptr [BX]的指令码正好是32位的指令mov al, byte ptr[EDI]。

读者可用这个程序验证BIOS是否同时在两个区域出现。如果有线性定址能力的VESA显示卡(如TVGA9440)还可进一步验证线性显示缓冲区在1MB之上的工作情况。

#include <dos.h>

unsigned long GDT-Table[]=

{0,0, //NULL - 00H

0x0000FFFF,0x00CF9A00, //Code32 - 08h Base=0 Limit=4G-1 Size=4G

0x0000FFFF,0x00CF9200 //Data32 - 10h Base=0 Limit=4G-1 Size=4G

};

unsigned char OldIDT [6]={0}; //Save The IDTR before Enter Protect Mode.

unsigned char pdescr-tmp [6]={0}; //NULL The IDTR s Limit=0 CPU will

// disable all Interrupts, include NMI.

#define KeyWait() {while(inportb(0x64) &2);}

void A20Enable(void)

{

keyWait ();

outportb(0x64,0xD1);

KeyWait();

outportb(0x60,0xDF); //Enable A20 with 8042.

KeyWait();

outportb(0x64,0xFF);

KeyWait ();

}

void LoadFSLimit4G(void)

{

A20Enable (); //Enable A20

//***

Disable ints & Null IDT

//***

asm {

CLI //Disable inerrupts

SIDT OldIDT //Save OLD IDTR

LIDT pdescr-tmp //Set up empty IDT.Disable any interrupts,

} // Include NMI.

//***

Lodd GDTR

//***

asm{ // The right Code is Real, But BC++ s Linker NOT

// Work with 32bits Code.

db 0x66 //32 bit Operation Prefix in 16 Bit DOS.

MOV CX,DS //MOV ECX,DS

db 0x66 //Get Data segment physical Address

SHL CX,4 //SHL ECX,4

MOV word ptr pdescr-tmp [0],(3*8-1)

//MOV word ptr pdescr-tmp [0], (3*8-1)

db 0x66

XOR AX,AX //XOR EAX,EAX

MOV AX,offset GDT-Table

// MOV AX,offset GDT-Table

db 0x66

ADD AX,CX //ADD EAX,ECX

MOV word ptr pdescr-tmp [2], AX

//GDTR Base low16 bits

db 0x66

SHR AX,16 //SHR EAX,16

MOV word ptr pdescr-tmp [4],AX

//GDTR Base high16 bits

LGDT pdescr-tmp //Load GDTR

}

//****

//* Enter 32 bit Flat Protected Mode

//****

asm{

mov DX,0x10 // The Data32 Selector

db 0x66,0x0F,0x20,0xC0 // MOV EAX,CR0

db 0x66

MOV BX,AX // MOV EBX,EAX

OR AX,1

db 0x66,0x0F,0x22,0xC0

//MOV CRO,EAX // Set Protection enable bit

JMP Flsuh

} //Clear machine perform cache.

flush: // Now In Flat Mode, But The CS is Real Mode Value.

asm { //And it s attrib is 16Bit Code Segment.

db 0x66

MOV AX,BX //MOV EAX,EBX

db 0x8E,0xE2 //MOV FS,DX

//Load FS Base=0 Size=4G now

db 0x66,0x0F,0x22,0xC0 //MOV CRO,EAX

//Return Real Mode.

LIDT OldIDT //LIDT OldIDT //Restore IDTR

STI // STI //Enable INTR

}

}

unsigned char ReadByte (unsigned long Address)

{

asm db 0x66

asm mov di,word ptr Address // MOV EDI, Address

asm db 0x67 //32 bit Address Prefix

asm db 0x64 //FS:

asm mov al,byte ptr [BX] // =MOV AL, FS: [EDI]

return -AL;

}

unsigned char WriteByte(unsigned Long Address)

{

asm db 0x66

asm mov di,word ptr Address //MOV EDI, Address

asm db 0x67 //32 bit Address Prefix

asm db 0x64 //FS:

asm mov byte ptr [BX],al //=MOV FS: [EDI],AL

return -AL;

}

//////// Don t Touch Above Code ///

# include <stdio, h>

void Dump4G (unsigned long Address)

{

int i;

int j;

for (i=0; i<20; i++)

{

printf (“%081X: ”, (Address+i*16));

for (j=0; j<16;j++)

printf ("% 02X" ,ReadByte (Address+i*16+j));

printf (" ");

for (j=0;j<16;j++)

{

if (ReadByte (Address+i*16+j) <0x20) printf (" . ");

else printf (" %C ", ReadByte (Address+i*16+j));

}

printf ("n");

}

}

main ()

{

unsigned long Address=0;

unsigned long tmp;

LoadFSLimit4G ();

printf ("====Designed By Southern. 1995.7.17====n");

printf (" Now you can Access The Machine All 4G Memory.n");

printf (" Input the Start Memory Physical to DUMP. n");

printf (" Press D to Cuntinue DUMP, 0 to End & Quit, n");

do {

printf ("-");

scanf ("%IX", &tmp);

if (tmp==0x0d) Address+=(20*16);

else Address=tmp;

Dump4G (Address);

}while (Address !=0);

return 0;

}

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭