Unreal Mode
Unreal mode(“非实模式)包括通过调整描述符缓存来打破实模式段的64KiB限制(同时保留16位指令和段*16+偏移量寻址模式)。
用途
非实模式通常在以下两种情况下被推荐使用:
- 你正在尝试扩展传统的16位DOS程序,以便它可以处理更大的数据,Virtual 8086 Mode和xms都不适合你的需要。
- 你正在尝试加载将在大于640K的32位模式下运行的程序(因此你不能将其加载到常规内存中),并且你还不想费心编写保护模式磁盘驱动程序,但你也希望避免在实模式和保护模式之间切换,以将块从传统内存缓冲区复制到扩展内存中。
如果你没有启用 A20 Line,你仍然无法完全访问所有物理RAM; 所有 “奇数” 1 MiB块将不可用。
实现
为此,你需要将段寄存器的描述符缓存限制设置为大于64KiB的任何值(通常为完整的4GiB(0xffffffff))。
在保护模式下,段寄存器中的位3-15表示全局描述符表的索引。 这就是为什么在下面的代码0x08=1000b中,会得到条目#1(条目#0总是一个空描述符)。
当(在保护模式下)段寄存器被加载了“选择器”时,“段描述符缓存寄存器”被填入描述符值,包括大小Size(或限制limit)。 切换回实模式后,无论16位段寄存器中的值是什么,这些值都不会被修改。 因此,64KiB限制不再有效,32位偏移量可以在实模式下用于实际访问64KiB以上的区域(段*16 + 32位偏移量)。
=大非实模式(Big Unreal Mode)
这不会触及CS。
因此,IP不受所有这些影响,代码本身仍被限制在64KiB。
; 汇编示例
; nasm boot.asm -o boot.bin
; partcopy boot.bin 0 200 -f0
[ORG 0x7c00] ; add to offsets
start: xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
cli ; no interrupts
push ds ; save real mode
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; tell 386/486 to not crash
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
pop ds ; get back old segment
sti
mov bx, 0x0f01 ; attrib/char of smiley
mov eax, 0x0b8000 ; note 32 bit offset
mov word [ds:eax], bx
jmp $ ; loop forever
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
times 510-($-$$) db 0 ; fill sector w/ 0's
dw 0xAA55 ; Required by some BIOSes
- At least on the 486SL the jmp $+2 instruction is needed after toggling the PM bit off, not just on. - BASICFreak
巨非实模式(Huge Unreal Mode)
巨非实模式使代码超过64KiB。 然而,由于实模式中断不会自动保存EIP的高16位,因此实现起来更加困难。 初始化很简单,你只需加载一个4GiB限制的代码段:
; 汇编示例
; nasm boot.asm -o boot.bin
; partcopy boot.bin 0 200 -f0
[ORG 0x7c00] ; add to offsets
start: xor ax, ax ; make it zero
... ; As before
mov cr0, eax
jmp 0x8:pmode
pmode:
mov bx, 0x10 ; select descriptor 2, instead of 1
mov ds, bx ; 10h = 10000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
jmp 0x0:huge_unreal
huge_unreal:
... ;As before
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatcode db 0xff, 0xff, 0, 0, 0, 10011010b, 10001111b, 0
flatdata db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
times 510-($-$$) db 0 ; fill sector w/ 0's
dw 0xAA55 ; Required by some BIOSes
警告:这可能在某些模拟器或某些硬件上不起作用。
编译器支持
Smaller C
Smaller C编译器支持非实模式。 它为虚实模式生成MZ可执行文件(可以用BootProg加载)。
代码和堆栈将位于1MB标记以下,并且堆栈大小受64KB限制 (瞧,CS:(E)IP,SS:(E)SP,这是DOS中MZ可执行文件的自然设置)没有什么不寻常的。 DS和ES段寄存器设置为0,因此C指针可以作为平面32位物理地址和地址数据或内存映射设备在前4 GB内存中的任何位置工作。
这些可执行文件的启动代码执行必要的重定位(只有自定义重定位,没有标准的MZ重定位,这可能会简化可执行文件的加载),并在将控制权传递给等价的“main()”之前设置非真实模式。 有关如何编写虚实模式的汇编代码位的信息,请参阅编译器源代码树中srclib下的“srclib/c0du.asm”和其他C/汇编代码(请查看#ifdef__unreal__‘下的(“内联汇编”)”)。
你可以在DOS中尝试非实模式 (例如在DOSBox,VirtualBox FreeDOS中),因为编译器完全支持其C库中的DOS非实模式组合。 tests/vesalfb.c是一个简单的例子,可以在启用线性帧缓冲区的情况下设置VESA图形模式,并在屏幕上以非真实模式绘制一些东西。
有关用Smaller C实现非实模式BootLoader的示例,请参阅FYSOS。