Unreal Mode

来自osdev
跳到导航 跳到搜索

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