目的
要在实模式下访问4G地址空间是为了将系统文件事先读入内存,而不用切换到32位保护模式后,自己写硬盘和文件系统的驱动。因为在现阶段,很多设备的驱动还没写出来,当然,那种已经成熟的操作系统,各种设备的驱动程序基本都齐全了的,在哪个阶段载入都可以。但是,对我来说,是基本没有各种设备的驱动程序,只好现借用BIOS来完成磁盘读写。
实现的方法
进入保护模式,给fs装入一个段基址是0,段界限是4G的选择符,然后立刻切换回实模式,这样在以后的程序中,就可以用这个选择符来实现访问4G地址空间了
注意:在设置完FS后,就不要修改FS了。
关键点
这个实现有几个关键的地方是,切换到保护模式,还有一个就是保证实模式和保护模式来回切换的时候,要保证程序运行
切换到保护模式
注意,这里是切换到16位的保护模式,并不是32位保护模式,所以要计算出对应的段基址,段界限就是64K,至于段的属性,大概是代码段可执行,数据段可读写,具体说明就留到书里写了,
源代码
这是一个演示用的程序,仅仅为了说明功能。
由于编译器的问题,还是采用了汇编和C混合的方式,在汇编里完成保护模式的初始化,也实现了访问4G空间的函数,其实在探测内存那里就有一部分。
汇编代码
这里有个地方会比较怪,就是那个返回实模式的代码,是一个手工构造的远跳转指令,但是段寄存设成了0,看上去会错,但实际上在Init4G的开头,已经往这个位置填入了当前CS的值,实际上已经构造出了
jmp cs:back_to_real_mode
的远跳转地址,并不是源程序上的
jmp 0:back_to_real_mode
; file name: rm4gasm.asm, robin @ 2019-10-25
.386p
.model tiny
;
EXTRN _main:PROC
PUBLIC _ReadByte4G
PUBLIC _WriteByte4G
PUBLIC _Init4G
;
DESC STRUC
LimitL DW 0 ;segment limit(BIT0-15)
BaseL DW 0 ;segment base address(BIT0-15)
BaseM DB 0 ;segment base address(BIT16-23)
Attributes DB 0 ;segemtn attribues
LimitH DB 0 ;segment limit (BIT16-19)(include segment attributes
;high 4 bit)
BaseH DB 0 ;segment base address(BIT24-31)
Desc ENDS
;
PDesc STRUC
Limit DW 0 ;16-bit limit
Base DD 0 ;32-bit base address
PDesc ENDS
;
TEXT SEGMENT USE16 PUBLIC 'CODE'
assume cs:TEXT, ds:TEXT
org 0100h
start:
mov sp, 0FFFCh
call _main;
mov ax, 04C00h
int 021h
;
;void WriteByte4G(byte_t nData, uint32_t nPhyAddr)
;
_WriteByte4G PROC
mov bx, sp
mov al, [bx + 2]
mov ecx, [bx + 4]
mov ebx, ecx
mov fs:[ebx], al
ret
_WriteByte4G ENDP
;
;byte_t ReadByte4G(uint32_t nPhyAddr)
;
_ReadByte4G PROC
mov bx, sp
xor ax, ax
mov ecx, [bx + 2]
mov ebx, ecx
mov al, fs:[ebx]
ret
_ReadByte4G ENDP
;
;void Init4G(void)
;
_Init4G PROC
push bp
mov bp, sp
mov ax, cs
mov bx, offset back_to_real_mode_segment
mov [bx], ax
;
mov bx,16 ; 构造当前运行环境的段选择子
mul bx ; 计算当前段的段地址,包括数据段和代
mov word ptr temp_code.BaseL,ax ; 码段,由于在这个环境中,代码段和数
mov byte ptr temp_code.BaseM,dl ; 据段相同可以使用相同段基址
mov word ptr temp_data.BaseL,ax
mov byte ptr temp_data.BaseM,dl
mov bx,offset dummy ; 计算GDT地址
add ax,bx ;
adc dx,0 ;
mov word ptr p_desc.Base,ax ;
mov word ptr p_desc.Base + 2,dx ;
;
;initial pm 保护模式初始化
lgdt p_desc ; 装填GDT
pushf
cli
mov eax,cr0 ; 打开保护模式,就是将CR0寄存器的0位设
or eax,1 ; 置1。在开启保护模式后,地址定位方式
mov cr0,eax ; 已经变化,需要按新的段选择子来取指令
;
db 0eah ; 构造调转指令,完成清空指令预取队列
dw offset init ; 由于CPU有指令预取功能,因此这条指令会在
dw temp_code_sel ; 打开保护模式前取得
init:
;
; 这个时候不是设置段寄存器,而是设置段选择符
;
mov ax, data_sel
push ax
push ax
pop fs
pop gs
mov eax, CR0
and eax, 0FFFFFFFEh
mov CR0, eax
db 0EAh
dw offset back_to_real_mode;
back_to_real_mode_segment:
dw 0
back_to_real_mode:
popf
mov sp, bp
pop bp
ret
_Init4G ENDP
;
align qword
gdt label byte
dummy DESC <> ;空描述符
temp_code DESC <0ffffh, , ,9ah, 0h,0h> ; 8
temp_data DESC <0ffffh, , ,92h, 0h,0h> ; 16
code_desc DESC <0ffffh, 00h, 0h,9ah,0cfh,0h> ; 24, 4G
data_desc DESC <0ffffh, 00h, 0h,92h,0cfh,0h> ; 32, 4G
vbuf_desc DESC <0ffffh,8000h,0bh,92h, 40h,0h> ; 40
gdt_len = $ - gdt
temp_code_sel = temp_code - gdt
temp_data_sel = temp_data - gdt
code_sel = code_desc - gdt
data_sel = data_desc - gdt
vbuf_sel = vbuf_desc - gdt
p_desc PDesc <gdt_len -1,>
data_len = $ - gdt
TEXT ENDS
END start
C代码
由于是编译成COM格式,标准的printf不能用,就另外写了一个直接写屏的print来显示信息。程序就是往4M的地方写入一个字节,然后在读出来,比较是否一致,一致则显示succeed,也就表明在实模式下访问4G空间成功。
// file name: rm4g.c, robin @ 2019-10-25
#include "stdio.h"
// WriteByte4G, ReadByte4G, Init4G imply in rm4gasm.asm
void WriteByte4G(unsigned char nData, unsigned long nPhyAddr);
unsigned char ReadByte4G(unsigned long nPhyAddr);
void Init4G(void);
#define VBUF (unsigned short far *)(0xB8000000l)
//
void print(int x, int y, char * msg)
{
unsigned loc = y * 80 + x;
unsigned short far * vbuf = VBUF;
while( *msg ){
vbuf[loc++] = 0x700 + *msg++;
}
}
//
int main(void)
{
Init4G();
// read/write address 0x400000, over 64K
WriteByte4G(0xA5,4l*1024l*1024l);
if( 0xA5 == ReadByte4G(4l*1024l*1024l) )
print(10,10,"succeed");
else
print(10,10,"failed");
//
return 0;
}
编译
编译还是有点啰嗦的,还是贴出来了吧,虽然只是一个演示程序用的
tasm rm4gasm.asm
bcc -c -mt -zCTEXT -zDTEXT -zRTEXT -zTCODE -zBCODE rm4g.c
tlink /t rm4gasm.obj rm4g.obj
del rm4g.com
rename rm4gasm.com rm4g.com
运行结果
运行结果显示成功了