Real Mode(实模式)-16位寻址

    真实模式是一种简单的16位模式,存在于所有x86处理器上。真实模式是第一个x86模式设计,在保护模式诞生之前被许多早期操作系统使用。出于兼容性的目的,所有x86处理器都以实模式开始执行。(请参看linux内核源码 )

    实模式是计算机微处理器的内存寻址方案和操作状态。在实模式下,程序可以访问的内存——通常是随机存取内存 (RAM)——不受硬件、软件或基本输入和输出服务 (BIOS) 的任何方式管理或缓冲。这意味着程序能够访问所有可到达的内存地址,无论内存用于什么,并且必须自行管理读取和写入内存位置的所有方面。使用实模式会带来一些限制,包括可访问内存的数量限制为 1 兆字节,因为处于这种模式的处理器只允许地址长度为 20 位

信息

    所有现代操作系统(Windows、Linux…)都在保护模式下运行,这是由于实模式存在许多限制和问题(请参阅下文和真实模式操作系统警告页面)。较旧的操作系统(如DOS)和程序以实时模式运行,因为这是当时唯一可用的模式。有关如何从真实模式切换到保护模式的信息,请参阅相应的文章。

   注意:有一种称为虚拟8086模式的模式,它允许在保护模式下运行的操作系统为单个应用程序模拟真实模式分段模型。这可以用于允许受保护模式操作系统在需要时仍然可以访问例如BIOS功能。

 下面你会发现一个缺点和优点的列表。这些主要是“与保护模式相比”。

Cons

  • 可供使用的RAM不足1 MB.
  • 没有基于硬件的内存保护(GDT),也没有虚拟内存。
  • 没有内置的安全机制来防止出现错误或恶意应用程序
  • 默认的CPU操作数长度仅为16-bit。
  • 所提供的存储器寻址模式比其他CPU模式更具限制性。
  • 访问64k以上需要使用难以使用的段寄存器(segment register)。

Pros

  • BIOS安装设备驱动程序以控制设备并处理中断。
  • BIOS功能为操作系统提供了低级别API功能的高级集合。
  • 由于缺少要检查的描述符表和较小的寄存器,内存(Memory )访问速度更快。

常识错误

    程序员经常认为,由于Real Mode默认为16位,因此32位寄存器(32 bit registers)是不可访问的。这是不正确的。

    只要在任何指令的开头添加“操作数大小(Operand Size )覆盖前缀”(0x66),所有32位(32-bit)寄存器(EAX,…)仍然可用。如果您只是尝试使用32位寄存器,那么汇编程序很可能会为您完成这项工作。

内存寻址(Memory Addressing)

   在实模式下,有一个略高于1MB的“可寻址”内存(包括高内存区)。请检测内存(x86)和内存映射(x86),以确定实际可用的内存量。可用容量将远远小于1MB。系统内存访问是通过分段 即通过 segment:offset。

   有六个16位段寄存器:CS、DS、ES、FS、GS和SS。当使用段寄存器(segment registers)时,使用以下符号给出地址

 12F3  :  4B27
   ^       ^
Segment   Offset

   解释:  其中“segment”是段寄存器中的值,“Offset”是地址寄存器中的一个值

  分段(Segments )和偏移(Offsets )通过以下等式与物理地址相关:

 PhysicalAddress = Segment * 16 + Offset

因此,12F3:4B27对应于物理地址0x17A57。任何物理地址都可以用多种方式表示,具有不同的段和偏移。例如,物理地址0x210可以是0020:0010、0000:0210或0021:0000

 栈(Stack)

     SS是16位 segment寄存器和 SP是offset的 寄存器,从而组成 segment:offset 形成的地址,来指定 20位物理地址(如上所述),即栈的当前“顶部”。栈存储116-bit的字(word),向下增长,并且必须在字(16-bit)边界上对齐。
     每当程序执行PUSH、POP、CALL、INT或RET 操作码(opcode)时,以及当BIOS处理任何硬件中断时,都会使用它。

高内存区域(High Memory Area)

 如果将DS寄存器(或任何段寄存器)设置为0xFFFF值,则它指向低于1MB 16字节的地址,如果使用该段寄存器作为基址,偏移量为0x10到0xFFFF,则你可以访问从0x100000 to 0x10FFEF的物理内存地址,这个超过1MB的区域(几乎64kB)在实模式下被称为“高内存区域”。请注意,您必须激活 A20 地址线才能工作。

注意: linux内核中有A20 的实现 ,实现为: E:\linux内核\linux-2.6.38.5\arch\x86\boot\a20.c

/*
 * Actual routine to enable A20; return 0 on ok, -1 on failure
它尝试使用不同的方法启用 A20 门
*/ #define A20_ENABLE_LOOPS 255 /* Number of times to try */ int enable_a20(void) { int loops = A20_ENABLE_LOOPS; int kbc_err; while (loops--) { /* First, check to see if A20 is already enabled (legacy free, etc.) */ if (a20_test_short()) return 0; /* Next, try the BIOS (INT 0x15, AX=0x2401) */ enable_a20_bios(); if (a20_test_short()) return 0; /* Try enabling A20 through the keyboard controller */ kbc_err = empty_8042(); if (a20_test_short()) return 0; /* BIOS worked, but with delayed reaction */ if (!kbc_err) { enable_a20_kbc(); if (a20_test_long()) return 0; } /* Finally, try enabling the "fast A20 gate" */ enable_a20_fast(); if (a20_test_long()) return 0; } return -1; }
第一个是a20_test_short检查 A20 是否已经启用的函数a20_test

A20 线

    80286 之前的一些程序旨在利用环绕(模)内存寻址行为,因此 80286 存在向后兼容性问题。强制第 21 条地址线(从芯片出来的实际逻辑信号线)为逻辑低电平,表示零,导致模 2^20 效果以匹配早期处理器的地址算法,但 80286 没有内部执行此功能的能力。当 IBM 在他们的IBM PC/AT中使用 80286 时,他们通过在 80286 的 A20 引脚和系统总线之间包含一个软件可设置的门来启用或禁用(强制为零)A20 ​​地址线来解决这个问题;这被称为 Gate-A20(A20 门),至今仍在 PC 芯片组中实现。大多数版本的 IBM-/MS-DOS 的 HIMEM.SYS 扩展内存驱动程序在加载一条消息时著名地显示他们已经安装了“A20 处理程序”,这是一种控制 Gate-A20 并使其适应程序需求的软件. 在保护模式下,需要启用 A20 线,否则会发生物理寻址错误,可能导致系统崩溃。现代遗留引导加载程序(例如GNU GRUB)使用 A20 行

寻址模式(Addressing Modes)

  实模式默认使用16位寻址模式(16-bit addressing mode)。汇编程序员通常熟悉更常见的32位寻址模式,可能需要进行调整,因为在16位寻址模式下可用作“指针(pointers)”的寄存器要有限得多。在真实模式下运行的典型程序通常在可用字节数方面受到限制,并且每个操作码中需要一个额外的字节才能使用32位寻址。

   请注意,您仍然可以在Real Mode中使用32位寻址模式,只需在任何指令(instruction)的开头添加“地址大小覆盖前缀(Address Size Override Prefix)”(0x67)

   如果您只是尝试使用32位寻址模式,那么您的汇编程序很可能会为您做到这一点。但您仍然受到每次内存访问中使用的段的当前“限制”的限制(在实模式下总是64K——非实模式(Unreal Mode)可能更大)。

  • [BX + val]
  • [SI + val]
  • [DI + val]
  • [BP + val]
  • [BX + SI + val]
  • [BX + DI + val]
  • [BP + SI + val]
  • [BP + DI + val]
  • [address]

 从保护模式切换到真实模式(Switching from Protected Mode to Real Mode)

  我们有个感性的认识关于保护模式和实模式之前的转换,请下载源码参看 E:\linux内核linux-2.6.38.5\arch\x86\boot\pm.c

/*
 * Invoke the realmode switch hook if present; otherwise
 * disable all interrupts.
    切换到实模式
 */
static void realmode_switch_hook(void)
{
    if (boot_params.hdr.realmode_swtch) {
        asm volatile("lcallw *%0"
                 : : "m" (boot_params.hdr.realmode_swtch)
                 : "eax", "ebx", "ecx", "edx");  
    } else {
        asm volatile("cli");
        outb(0x80, 0x70); /* Disable NMI */
        io_delay();
    }
}

/*
 * Actual invocation sequence
    切换到保护模式
 */
void go_to_protected_mode(void)
{
    /* Hook before leaving real mode, also disables interrupts */
    realmode_switch_hook();

    /* Enable the A20 gate */
    if (enable_a20()) {
        puts("A20 gate not responding, unable to boot...\n");
        die();
    }

    /* Reset coprocessor (IGNNE#) */
    reset_coprocessor();

    /* Mask all interrupts in the PIC */
    mask_all_interrupts();

    /* Actual transition to protected mode... */
    setup_idt();
    setup_gdt();
    protected_mode_jump(boot_params.hdr.code32_start,
                (u32)&boot_params + (ds() << 4));
}

 

  保护模式操作系统可以使用虚拟8086模式来访问BIOS功能。然而,VM86模式(虚拟8086模式)有其自身的复杂性和困难。
一些操作系统设计人员认为,在需要访问BIOS功能的情况下,暂时返回真实模式更简单、更干净. 这需要创建一个特殊的Ring 0程序,并将其放置在Real Mode中可以访问到的物理内存地址中。操作系统通常需要传递一个关于要执行哪个BIOS功能的信息包。

下面程序是从保护模式切换到实模式需要的步骤

    1. 禁用中断:
      • 用CLI关闭 maskable interrupts.
      • 禁用 NMI (可选).
    2. 关闭分页(paging):
      • 将控制权转移到1:1页。
      • 确保GDT和IDT在1:1的页中。
      • 清除第零个控制寄存器中的PG-flag。.
      • Set 第三 control register 为 0.
    3. 使用  16-bit GDT tables (skip this step if one is already available):
      • 创建一个带有16-bit的 data和code segment的新GDT(下面是说的段描述符,这个要注意)  :
        • Limit: 0xFFFFF
        • Base: 0x0
        • 16-bit
        • Privilege level(DPL): 0 (ring 0)
        • Granularity(G flag): 0
        • Read and Write(RW flag): 1
      • Load new GDT ensuring that the currently used selectors will remain the same (index in cs/ds/ss will be copy of original segment in new GDT)
    4. Far jump to 16-bit protected mode:
      • Far jump to 16-bit protected mode with a 16-bit segment index.
    5. 加载具有16位索引的数据段选择器(data segment selectors):

    • 用16位数据段加载ds、es、fs、gs、ss。
  1. 加载 real mode IDT:
    • Limit: 0x3FF
    • Base 0x0
    • Use lidt
  2. 禁用 protected mode:
    • 用 CR0 设置  PE 这个位 为false.
  3. Far jump to real mode:
    • Far jump to real mode with real mode segment selector (usually 0).
  4. 加载具有实模式值的数据段寄存器:
    • 加载 具有适当实模式值(通常为0)的ds、es、fs、gs、ss。
  5. 将栈指针设置为适当的值:
    • 将sp设置为不会干扰实模式程序的栈值
  6. 启用interrupts:
    • 启用可屏蔽(maskable) interrupts with STI.
  7. Continue on in real mode with all bios interrupts.

    段描述符如下:



  实模式下的 x86 汇编样例

[bits 16]
 
idt_real:
	dw 0x3ff		; 256 entries, 4b each = 1K
	dd 0			; Real Mode IVT @ 0x0000
 
savcr0:
	dd 0			; Storage location for pmode CR0.
 
Entry16:
        ; We are already in 16-bit mode here!
 
	cli			; Disable interrupts.
 
	; Need 16-bit Protected Mode GDT entries!
	mov eax, DATASEL16	; 16-bit Protected Mode data selector.
	mov ds, eax
	mov es, eax
	mov fs, eax
	mov gs, eax
	mov ss, eax
 
 
	; Disable paging (we need everything to be 1:1 mapped).
	mov eax, cr0
	mov [savcr0], eax	; save pmode CR0
	and eax, 0x7FFFFFFe	; Disable paging bit & disable 16-bit pmode.
	mov cr0, eax
 
	jmp 0:GoRMode		; Perform Far jump to set CS.
 
GoRMode:
	mov sp, 0x8000		; pick a stack pointer.
	mov ax, 0		; Reset segment registers to 0.
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
	lidt [idt_real]
	sti			; Restore interrupts -- be careful, unhandled int's will kill it.

也参考:

Articles

External Links

References

https://wiki.osdev.org/Real_Mode
https://en.wikipedia.org/wiki/Real_mode
 https://www.easytechjunkie.com/what-is-real-mode.htm
http://aturing.umcs.maine.edu/~meadow/courses/cos335/Asm05-Subprograms.pdf   关于 16-bit addressing mode
https://en.wikipedia.org/wiki/Protection_ring
https://www.baeldung.com/cs/os-rings

posted @ 2023-04-08 03:55  jinzi  阅读(60)  评论(0编辑  收藏  举报