摘 要: 分析了80486CPU 的寻址机制, 提出了在实方式下直接访问4GB 内存的策略和C 程序设计方法。
关键词: DO S 程序 扩展内存 程序设计
目前常用的访问扩展内存方法有: (1) 采用“IN T15H”或HTM EM 1SYS 的功能调用; (2) 采用虚拟磁盘。前者只能实现数据块在常规内存与扩展内存之间移动, 这样, 不但还要占用一定的常规内存空间, 而且数据块的移动还需占用程序运行时间, 使程序的运行效率降低。后者可以以文件的形式将数据存储在扩展内存中, 采用文件的访问方式进行数据操作。显然, 这2 种方法只能间接访问扩展内存, 不能直接访问扩展内存, 而且数据的操作效率低, 难以满足实时性要求。
本文将从80486CPU (以下简称CPU ) 的寻址机制研究入手, 讨论在实方式下直接访问4GB 内存的方法和C 程序设计方法。
1.1 物理地址形成的统一性
无论CPU 在实方式下或保护方式下, 其物理地址的形成都将使用段描述符高速缓冲寄存器。其差别是:在实方式下, 每当向段寄存器赋予新的内容(段地址) 时, 段描述符高速缓冲寄存器的基地址值相应发生改变, 其值为16×SEG, 这是线性基地址; CPU 最终形成的物理地址为某地址值加偏移量。段描述符高速缓冲寄存器的界限值和属性值始终不变。当CPU 复位后, CPU 的工作模式为实方式, 段描述符高速缓冲寄存器的界限值自动设置为FFFFH。因此, CPU 能够访问内存的空间为0~ 10FFEFH ( FFFFH × 16 +FFFFH =1114095) , 每个段的大小为64KB。在保护方式下, 每当向段寄存器赋予新的内容SEL (选择字) 时, 段描述符高速缓冲寄存器的内容将由SEL 对应的段描述符更新; 段描述符高速缓冲寄存器的基地址值、界限值和属性值依据段描述符的设置而发生改变。段的基地址可设置在4GB 内存的任意地址处, 段的最大界限值可达FFFFFFFFH (4GB - 1)。
在不分页的情况下, CPU 最终形成的物理地址同样是基地址值加偏移量。所以, CPU 能够整个访问4GB 内存。
显然, 对于CPU 在形成物理地址时, 在实方式下与在0 特权级不分页的保护方式下是相同的。只是段的基地址和段的大小设置范围不同。工作方式确定是由控制寄存器CR0 的最低位PE 位决定的, 若PE 为0, 则工作在实方式; 若PE 为1, 则工作在保护方式。在通过PE 位的改变时, 就进行了工作方式切换。这种切换只影响段描述符高速缓冲寄存器的基地址值运算方式, 不影响段描述符高速缓冲寄存器的段界限值。
当CPU 复位后, CPU 处于实方式下, 尽管在实方式下可执行诸如“MOV AX,〔ES I〕”指令的32 位寄存器间接寻址操作, 但是ES I 的内容必须在0~ FFFFH范围内, 否则, 将引起13 号异常中断。基于CPU 物理地址形成的统一性, 在实方式下直接访问4GB 内存的关键是扩大段描述符高速缓冲寄存器的界限值。使诸如“MOV AX,〔ES I〕”指令的32位寄存器间接寻址操作实现4GB 内存的访问。然而,CPU 在实方式下并没有提供改变段描述符高速缓冲寄存器的界限值的操作指令。改变段描述符高速缓冲寄存器的内容只能在保护方式下进行, 即向段寄存器赋予段的选择字SEL , 段描述符高速缓冲寄存器的内容将由SEL 对应的段描述符更新。当CPU 从保护方式切换到实方式时, 段描述符高速缓冲寄存器的内容不发生变化。
因此, 在DO S 实方式下直接访问4GB 内存之前,让CPU 进入保护方式下, 通过装载具有4GB 界限的段描述符到段描述符高速缓冲寄存器中去, 然后返回到实方式下, CPU 就可以通过32 位寄存器间接寻址 操作实现4GB 内存的访问。
2.1 编程环境
本文采用Bo rland C+ + 3. 1 程序设计环境, 在程序中使用内嵌汇编方法实现特定的操作, 在O P t ions的“Comp ile”- “A dvanced Code generat ion”中选择386 指令集。由于集成开发环境下的内部编译器不能识别内嵌的386 汇编指令, 对于32 位寄存器和32 位地址操作汇编指令, 可以采用直接在代码前加入操作数前缀0x66 或地址前缀0x67, 以便实现32 位寄存器地址的操作。最好的方法是让集成开发环境调用TA SM 1EXE 进行编译, 即设置O P t ions 中的“Comp ile”- “Code generat ion ”- “ Comp ile via as2sem ler”为ON。这样便可完整地运用386 汇编指令。在以下编程示例中采用了这种编译方法。
本程序是采用基于实方式下的编程方法。它不能在保护方式下和虚拟8086 方式下运行, 不能装载扩充内存EM S 驱动程序(如EMM 3861EXE) , 因为, 它使DO S 系统处于虚拟8086 方式下。
CPU 的A 20 地址线受到键盘接口处理器8042 的输出口P21 控制, 如果DO S 系统启动后, 没有装载XM S 驱动程序(如h imem. sys) , 此时A 20 被关闭, 要访问4GB 内存, 必须打开A 20, 是A 20 受到CPU 的控制。
vo id openA 20 ()
{ wh ile ( inp (0x64) & 2) ; outp (0x64, 0xd1) ;
wh ile ( inp (0x64) & 2) ; outp (0x60, 0xdf) ;
wh ile ( inp (0x64) & 2) ; outp (0x64, 0xff) ;
}
首先, 建立1 个全局描述符表GDT, 即GDT- def,它含有2 个描述符, 第1 个为空描述符(保护方式下系统要求的) , 第2 个是具有4GB 段界限的数据段描述符, 它的选择字为8。再计算出GDT 的基地址和长度存入GDT- A ddr 中。然后, 装载GDT, 进入保护方式,把选择字8 赋给DS 和ES, 此时第2 个是具有4GB 段界限的数据段描述符被装载到DS 和ES 的描述符高速缓冲寄存器中, 最后返回实方式。同理, 也可设置FS和GS 的4GB 界限。
unsigned long GDT2def [ ]= {0, 0, 0x0000FFFF, 0x008F9200}; //全局描述符表GDT
unsigned char GDT- A ddr[ 6 ]= {0}; //存放GDT 的基地址和长度
vo id set4gb ()
{ asm{
cli
push ds ; push es
mov wo rd p tr GDT- A ddr[ 0 ], (23 8- 1) //GDT 的长度存入GDT- A ddr 中
mov eax, ds //计算GDT 描述符表的线性基地址31~ 0
sh l eax, 4 //段地址eax= ds x 16
xo r ebx, ebx //ebx 清零
mov bx, offset GDT- def //b x= GDT 的偏移地址
add eax, ebx //GDT 的线性基地址= eax+ ebx
mov dwo rd p tr GDT- A ddr [ 2 ]eax //GDT 的线性基地址存入GDT- A ddr 中
lgdt fwo rd p tr GDT- A ddr
mov bx, 8 /设置数据段描述的选择字
mov eax, cr0
o r al, 1
mov cr0, eax
jmp flush l
} //进入保护方式
flush l: asm{
mov ds, bx //D S 装载具有4GB 界限的数据段描述符
mov es, bx //ES 装载具有4GB 界限的数据段描述符
and al, 0feh
mov cr0, eax
jmp flush2
} //返回实方式
flush2: asm{
pop es ; pop ds
sti
}
}
在DS 和ES 具有4GB 的访问界限后, 通过32 位寄存器间接寻址的指令就可实现4GB 内存的访问。以下仅给出字节的读写函数, 其中, addr 为RAM 的32位线性地址。
1. 读字节函数, 函数的返回值为读出的字节。
unsigned char read- ram (unsigned long addr)
{ asm push ds
asm mov ax, 0
asm mov ds, ax
asm mov esi, addr
asm mov al, [ esi]
asm pop ds
return- AL;
}
2. 写字节函数, ch r 为要写入的字节。
vo id w rite- ram (unsigned long addr, unsigned char ch r)
{ asm push ds
asm mov ax, 0
asm mov ds, ax
asm mov esi, addr
asm mov al, ch r
asm mov [ esi], al
asm pop ds
}
本示例将使用上面所设计的功能函数, 由内嵌汇编程序完成对主机内存的测试和容量的检测任务。在PC386/33、PC486/100 上通过。
# include < do s. h>
# include < stdio. h>
vo id main ()
{unsigned long addr;
openA 20 () ;
set4gb () ;
asm push ds
asm mov ax, 0
asm mov ds, ax
asm mov esi, 00100000h //从1M b 开始测试
N 1: asm mov eax, [ esi] //暂存esi 地址指定双字单元的内容
asm mov dwo rd p tr [ esi], 0 //向esi 地址指定双字单元写零
asm mov ebx, [ esi] //读出由esi 地址指定的双字单元的内容给ebx
asm mov dwo rd p tr [ esi], 0ffffffffh//向esi 地址指定双字单元写全1
asm mov ecx, [esi] //读出由esi 地址指定的双字单元的内容给ecx
asm mov [ esi], eax //恢复esi 地址指定双字单元的内容
asm inc ecx //若ecx 的内容为全1, ecx 增1后, ecx= 0
asm cmp ebx, ecx //比较2次读出的内容
asm jne N 2 //不相等, 则退出循环检测
asm add esi, 4 //e si 地址增4
asm jmp N 1 //继续循环检测
N 2: asm mov addr, esi
asm pop ds
p rintf ("RAM = % 1d (M b) " , addr > > 20) ;
}
3 与XM S 驱动程序的兼容方法
3.1 基本原理如果没有装载XM S 驱动程序H imem. sys, 采用上述方法可无限制地使用整个扩展内存。若系统中的某些系统程序和其它应用程序需要使用H imem. sys,通过它分配和访问扩展内存。这样, 扩展内存的部分空间被占据, 用户应用程序所使用的扩展内存空间将受到限制, 否则, 发生冲突。
因此, 在这种情况下, 用户应用程序可通过H imem. sys 的功能调用, 为其分配未使用的扩展内存存储块EMB, 并通过锁定EMB, 得到EMB 的32位线性基址, 便可采用上述方法对存储块进行直接访问。
1.取得安装状态
只有当H imem. sys 已被加载, 才能调用它的功能。当AX= 4300H 时, 执行“IN T2FH”软中断指令后,若AL = 80H, 则已加载; 若AL = 00H, 则未加载。
2.取得入口地址
H imem. sys 的各项功能调用是通过功能调用的入口地址进行的, 当AX= 4310H 时, 执行“IN T2FH”软中断指令后, ES= 入口的段地址,BX= 入口的偏移地址。
3.功能调用
H imem. sys 为用户提供许多XM S 的内存管理功能, 与本文相关的功能和接口如表1所示。
4 结束语
DO S 实方式下直接访问4GB 内存具有一定的特殊性和重要的实际意义。本文提出的方法在图像实时识别系统中得到了成功的应用。该系统需同时采集4幅图像, 每幅图像为512×512的256色点阵, 4幅图像共需1MB 内存空间, 在对图像进行处理时还需要2幅图像的工作缓冲区。显然, 在常规内存中难以进行存储和处理。若采用虚拟磁盘或XM S 驱动程序H imem. sys, 由于图像的数据量大、运算时间长, 影响了实时性要求。
通过采用本文提出的方法, 运用C 程序的内嵌汇编直接对扩展内存进行图像的存储和处理, 发挥了计算机(下转第28页)