Introduction to Big Real Mode
转自Merck Hung merck@olux.org, 洪豪謙
应朋友的要求, 希望我花一点时间整理一下 x86 Big Real Mode 的文章.另外也发现, 身边似乎有一些朋友也准备要开始从事 BIOS 方面之工作了.感谢你们偶而会來逛一下我的 Blog.虽然网路上已经有蛮多资料了, 不过今天我打算从 Intel 64 and IA32 Architecture Software Developer’s Manual (后面简称SDM), 以及 x86 Processor 的角度, 來解說如何打开 Big Real Mode.我将会花时间解說 Intel SDM 内的资料, 最后才丢一段 Code 给你.例如說明什么是 Big Real Mode, 地址空间的差異, A20, Segment Descriptor 等细节.而我会另外找时间再把 Protected Mode + Paging + PSE (存取大于 4GB Memory) 写完.
感谢啰!
Big Real Mode 定义
Big Real Mode 是一个有趣的 x86 Processor Mode, 正好处在 32bit protected mode 与 16bit real mode 中间.

Fig.1, Real Mode, Big Real Mode, 与 Protected Mode 比较图
以上图來說, Real Mode 的 Code 与 Data Segment 的最大范围均为 1MB (16bit).当 CPU 进入 Protected Mode 之后, Code 与 Data Segment 的最大范围将可以达到 4GB.而 Big Real Mode 很神奇的是, Code Segment 维持原來 1MB (16bit) 的限制, 但 Data Segment却可以存取到整个 4GB 的空间.
因为一般 real mode 只有办法存取到 1MB Memory, 然而以现在的计算机配备來說, 动辄 1GB, 2GB,或 4GB 的内存大小. 如果 BIOS 单纯仅运作在 real mode 内, 那样根本完全无法存取到你所安装的所有内存.而一般 OS, 如 Windows, Linux 等, 皆是以打开 protected mode 來存取到整个 4GB 的Memory Space.
但因为 BIOS Code 大部分都是以 real mode 撰写的, 所以当有那个需求要存取 1MB 以上的记忆体时, big real mode 就会是一个不错的选择.
虽然 protected mode 也可以达成存取 4GB 的目的, 但如果程序本身所执行的环境是 DOS 或BIOS Code 的话, 打开 protected mode 反而会造成麻烦. 例如你会无法呼叫原來写给 real mode呼叫的一些 routines, 或是必须频频不断的在兩个 mode 之间切换.
Address Space 差異

Fig.2, Real Mode 与 Protected Mode 地址空间之差異
在 real mode 中, CPU 最后实际存取 Memory 的位置 (Physical Address 或 Linear Address), 是透过 Segment Address 向左 Shift 4 bits, 然后加上 Offset Address 來计算.故 real mode 的 address space range 是从 0_0000 ~ F_FFFF, 也就是只能组合出 1MB 的空间.
而在 protected mode (或 big real mode) 中, 原 real mode 中的 “segment register” 换了个名称与定义, 改称作 “segment selector”. 而这个 “segment selector” 将不是直接填写 Address, 而改为载入 Segment Descriptor 在 GDT Table 裡面的 Offset Address.
而 Segment Descriptor 内将会含有 Base Address 这个栏位, 用以說明 Segment 起始地址.故将 Segment Descriptor 内之 Base Address, 加上 Offset Address 后, 便是实际 CPU 所将存取的 Memory 位置 (Physical Address 或 Linear Address).
Enable 与 Disable 流程

Fig.3, Enable 与 Disable Big Real Mode 的流程图
打开 Big Real Mode 的程序:
1. 设定 Global Descriptor Table (GDT)
2. 载入 GDT Pointer 的 Physical Address 到 GDTR Register
3. 打开 A20
4. Enable Protected Mode Bit, CR0 Bit 0 = 1, 并作一个 Flush Jump
5. 将 4GB 的 Data Segment Selector 载入 DS, ES, FS, GS
6. Disable Protected Mode Bit, CR0 Bit 0 = 0
7. 作一个 Far Jump Flush
关闭 Big Real Mode 的程序:
1. 关闭 A20
2. 将 DS, ES, FS, GS 的内容设定 (或从 Stack pop 回來) 为 Real Mode Segment Offset 即可
A20 开关

Fig.4, 在 Real Mode 中, A20 Enabled 所多出之約 64k Address Space
在早期 8086 CPU 位址線只有 20 條, 所以造就了 Segment:Offset 這樣的存取方式. 但其實大家可以注意到, FFFF:000F = FFFFF (1MB), 其中 Offset Address 還沒填到最大值 FFFF. 但因為8086 只有 20 隻腳位, 所以超過 FFFFF (1MB) 的搭配, CPU 將會回繞回 0MB 起算.
而後來的 CPU 的位址線增加, 如 286 有 24 隻腳位, 386 以上有 32 隻腳位. 但為了維持 PC架構的相容性, 所以增加了 A20 這樣的開關, 在 A20 關閉時讓超過 1MB 的寻址做回绕到 1MB的动作. 但只要 A20 打开后, 超过 1MB 将不会有回绕动作.
而在 real mode 中, 因为使用 Segment:Offset 的存取方式, 所以打开了 A20 后, 大约只能增加64k 左右的空间 (10000 ~ 10FFEF).但也因为 A20 开关牵涉到地址线回绕的问题, 所以当我们打算进入 protected mode (使用全部 32只地址线) 之前, 打开 A20 开关也是一个重要的课题.
GDT 与 Segment Descriptor
有别于 16bit real mode 将Segment Address 直接加载 DS, ES, FS, GS (Data Segment Registers) 的方式.在 protected mode 内, DS, ES, FS, GS 转换了一个名称, Segment Selector.因为 DS, ES, FS, GS 还是维持原來 16bit 的大小, 并非像 AX, BX,……等 16bit 缓存器, 推出32bit 的版本, 如: EAX, EBX,……等.
但因为 32bit protected mode 存取 Data 的方法还是维持 DS:XX, ES:XX, FS:XX, GS:XX 的方式,所以 Intel 提供存取 4GB 的新方法, 而这个方式就是利用 Segment Descriptor 与 Segment Selector.

Fig.5, Segment Descriptor 定义 Segment 的与地址的对应关系
一般为了方便使用, Segment Base Address 通常会被设为 0, 而 Segment Limit 会设为 4GB. 这不是我所创造的惯例, 而是现在的操作系统都是这样使用. 甚至现在支持 64bit 的 CPU, 在进入64bit long mode 后, Segment 这样的 feature 已经干脆被舍弃了 (因为过去大家都直接开 4GB,等同于不使用 Segment 这个特性).

Fig.6, Intel SDM Vol.3 3-13, Segment Descriptor 各栏位說明
由上图所知, Segment Descriptor 是一个 8 bytes 的资料结构.
其中 Base Address (31:00, 32bit), 将会用來 “說明”, 这个 Segment 的起始地址.
而 Segment Limit (19:00, 20bit), 将会用來 “說明”, 从起始地址开始算起的 “长度”, 是属于这个Segment.
而 G 栏位用來决定 Segment Limit 的单位, 0 为 1 bytes, 1 为 4k. 因为 Segment Limit 只有20bit, 所以当 G=0 (1 bytes) 时, 最大只能涵盖到 1MB. 但当 G=1 (4 kbytes) 时, 最大就能涵盖到4GB.
Type 是一个 4bit 栏位, 总共有 16 种 Type 可供填写, 主要的分類是 Code 或 Data, 细项分類将于 protected mode 一文中說明.
P 栏位让 OS 用來表示这个 Segment 是否被 Swap Out 到硬盘上, 而没有实际在内存上.
S 栏位, 为 0 时表示 System Segment, 为 1 时表示 Code 或 Data Segment.
;##############################################################################
; Globel Descriptor Table (GDT)
;
align
GDT_TABLE:
; NULL segment
DW , , , ; 四個 WORD 等於 8 bytes
; Data segment, read/write
FLAT_DATA_SEG EQU $ - GDT_TABLE
DW 0FFFFh
DW
DW 9200h
DW 00CFh
GDT_SIZE EQU $ - GDT_TABLE
;
; GDT Pointer
;
GDT_POINTER:
DW GDT_SIZE -
DW ;
DW ; GDT base address
我们來看一个实际的范例, 设定 GDT 的主要重点是:
1. GDT 起头要对齐 16 bytes (align 16)
2. 第一个 Segment Descriptor 务必要为NULL Segment
3. 最后需要填写GDT Pointer, Pointer 的 Linear Address 将用于加载 CPU 的GDTR 缓存器.
4. 整个GDT 的 Size 及 GDT 的起始 Linear Address 需填入 GDT Pointer.
而以 Big Real Mode 來說, 我们只需设定一个Data Segment Descriptor, 而FLAT_DATA_SEG就是所谓的Segment Selector (此例中为 08h), 将会用于载入 DS, ES, GS, FS 缓存器.

Fig.7,范例中各栏位 Bit标示
Segment Base = 0000_0000h
G = 1, 单位 = 4 kbytes
Segment Limit (00:19) = F_FFFFh
Segment Limit = 4GB (FFFF_FFFFh)
D/B = 1, 32bit Segment (为存取 4GB, 故为 32bit 区段)
DPL = 0, Kernel Segment, Ring 0 (属于 Kernel Ring 0, 最大权限)
Type = 2, Data Read/Write Segment (属于资料, 可讀可写区段)
AVL = 0, Reserved (保留)
L = 0, Reserved (保留)
P = 1, Present in Physical Memory (预设 1)
S = 1, Code or Data Segment (程序或资料区段)
Sample Code – Enter and Leave Big Real Mode
LIBFLAT SEGMENT USE16 'CODE'
;##############################################################################
; __enter_flat_mode – Enter Big Real Mode, 进入 Big Real Mode
;
; Input:
; None
;
; Output:
; None
;
; Modified:
; All possible
;
__enter_flat_mode PROC FAR PUBLIC
; Convert GDT base physical address to linear one
; 将 GDT 的 Base Address, 由 Segment:Offset 转为 Linear Address
xor eax, eax ; 另 EAX = 0 (32bit)
mov ax, cs ; 将 Code Segment Address 放入 AX (16bit)
shl eax, ; EAX (32bit) 向左位移 4 的 bits
add eax, OFFSET GDT_TABLE ; 加法演算, 加上 GDT 在 Code Segment 内的 Offset
mov dword ptr GDT_POINTER+, eax ; 将 EAX (32bit) 的结果值, 写入 GDT Pointer (本來为 0)
; Save original GDT and Load new one
; 利用 LGDT 指令, 将 GDT Pointer 加载 CPU 的 GDTR 缓存器
lgdt fword ptr GDT_POINTER
; Disable interrupt
; 关闭 CPU 中断
cli
; Enable A20
; 打开 A20
in al, 92h ; 从 Keyboard Controller (IO Port 92h) 讀入
or al, 02h ; 将 Bit 1, A20 设为 1
out 92h, al ; 写回 Keyboard Controller
; Enable protected mode
; 打开 CR0 Bit 0, 也就是 protected mode
mov eax, cr0 ; 先将 CR0 讀入 EAX
or eax, 01h ; 将 Bit 0, PM Flag 设定为 1
mov cr0, eax ; 将 EAX 写入 CR0
jmp @f ; Flush Jump, 让 CPU 更新狀态
@@: ; Flush Jump 到这裡
没有 Far Jump 的指令, 所以我们直接利用 DB, DW 等 Macro 來写 CPU 指令
; OpCode 部分是 EAh, 后面第一个 WORD 是 Offset Address, 第二个是 Segment Address
DB 0eah ; Far Jump 指令的 OpCode
DW OFFSET @f ; 请组译器帮我们填 Offset Address
DW SEG @f ; 请组译器帮我们填 Segment Address
@@: ; Far Jump 到这裡
ret
__enter_flat_mode ENDP
;##############################################################################
; __exit_flat_mode – Leave Big Real Mode, 離开 Big Real Mode
;
; Input:
; None
;
; Output:
; None
;
; Modified:
; All possible
;
__exit_flat_mode PROC FAR PUBLIC
; Disable A20
; 关闭 A20
in al, 92h ; 从 Keyboard Controller 讀入
and al, not 02h ; 将 Bit 1, A20 设为 0
out 92h, al ; 写回 Keyboard Controller
; Reenable interrupt
; 重新打开中断
sti
ret
__exit_flat_mode ENDP
;##############################################################################
; Globel Descriptor Table (GDT)
;
align
GDT_TABLE:
; NULL segment
DW , , ,
; Data segment, read/write
FLAT_DATA_SEG EQU $ - GDT_TABLE
DW 0ffffh
DW
DW 9200h
DW 00cfh
GDT_SIZE EQU $ - GDT_TABLE
;
; GDT Pointer
;
GDT_POINTER:
DW GDT_SIZE -
DW ;
DW ; GDT base address
LIBFLAT ENDS
;##############################################################################
; enter_flat_mode -- Enter Big Real Mode, 进入 Big Real Mode
;
; Input:
; None
;
; Output:
; None
;
; Modified:
; All possible
;
enter_flat_mode MACRO
; Save segment for FLAT exit
; 因为我们会将 DS, ES 设定为 4GB, 所以一般应用可以在进入前, 将原 DS, ES 推入 Stack
push ds
push es
call __enter_flat_mode
ENDM
;##############################################################################
; exit_flat_mode -- Leave Big Real Mode, 離开 Big Real Mode
;
; Input:
; None
;
; Output:
; None
;
; Modified:
; All possible
;
exit_flat_mode MACRO
; Restore original segments
; 回存进入时所推入的 ES, DS, Segment Address 值, 來回復 real mode 64k.
pop es
pop ds
call __exit_flat_mode ; 关闭 A20, 打开中断
ENDM
;------------------------------------------------------------------------------
; Code segment
;
_TEXT SEGMENT PARA USE16 'CODE'
libflat SEGMENT USE16 PUBLIC
EXTERN __enter_flat_mode:FAR
EXTERN __exit_flat_mode:FAR
@CurSeg ENDS
;##############################################################################
; MAIN procedure
;
MAIN PROC FAR PRIVATE
; Save for DOS return
push ds
push ax
ASSUME SS:STACK, DS:_DATA, CS:_TEXT, ES:_DATA
mov ax, _DATA
mov ds, ax
mov es, ax
;--------------------------------------------------------------------------
; Enter FLAT mode
;--------------------------------------------------------------------------
enter_flat_mode ; 进入 Big Real Mode
;--------------------------------------------------------------------------
; FLAT mode code start
;--------------------------------------------------------------------------
; 存取 Linear Address 12345678h, 讀入 Double Word 到 EAX
mov esi, 12345678h ; 将 ESI 设为 12345678h
mov eax, ds:esi ; 将 Linear Address 12345678h 的内容, 讀入 EAX
;--------------------------------------------------------------------------
; Exit FLAT mode
;--------------------------------------------------------------------------
exit_flat_mode ; 離开 Big Real Mode
;--------------------------------------------------------------------------
; REAL mode code
;--------------------------------------------------------------------------
; Return to DOS
ret
MAIN ENDP
_TEXT ENDS
Introduction to Big Real Mode的更多相关文章
- A chatroom for all! Part 1 - Introduction to Node.js(转发)
项目组用到了 Node.js,发现下面这篇文章不错.转发一下.原文地址:<原文>. ------------------------------------------- A chatro ...
- Introduction to graph theory 图论/脑网络基础
Source: Connected Brain Figure above: Bullmore E, Sporns O. Complex brain networks: graph theoretica ...
- INTRODUCTION TO BIOINFORMATICS
INTRODUCTION TO BIOINFORMATICS 这套教程源自Youtube,算得上比较完整的生物信息学领域的视频教程,授课内容完整清晰,专题化的讲座形式,细节讲解比国内的京师大 ...
- mongoDB index introduction
索引为mongoDB的查询提供了有效的解决方案,如果没有索引,mongodb必须的扫描文档集中所有记录来match查询条件的记录.然而这些扫描是没有必要,而且每一次操作mongod进程会处理大量的数据 ...
- (翻译)《Hands-on Node.js》—— Introduction
今天开始会和大熊君{{bb}}一起着手翻译node的系列外文书籍,大熊负责翻译<Node.js IN ACTION>一书,而我暂时负责翻译这本<Hands-on Node.js> ...
- Introduction of OpenCascade Foundation Classes
Introduction of OpenCascade Foundation Classes Open CASCADE基础类简介 eryar@163.com 一.简介 1. 基础类概述 Foundat ...
- 000.Introduction to ASP.NET Core--【Asp.net core 介绍】
Introduction to ASP.NET Core Asp.net core 介绍 270 of 282 people found this helpful By Daniel Roth, Ri ...
- Introduction to Microsoft Dynamics 365 licensing
Microsoft Dynamics 365 will be released on November 1. In preparation for that, Scott Guthrie hosted ...
- RabbitMQ消息队列(一): Detailed Introduction 详细介绍
http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...
- Introduction - SNMP Tutorial
30.1 Introduction In addition to protocols that provide network level services and application progr ...
随机推荐
- Python打包成exe:屡试不爽的cxfreeze!
Python用cxfreeze打包,不用像什么Pyinstaller繁杂的写配置文件,一行命令足以! 安装好cxfreeze后,进行python脚本目录,执行: C:\Python27\Scripts ...
- GIT 操作
1. 查看某个文件某次提交修改的内容 git show commitid a.txt 2. git rm 和 git rm --cached 当我们需要删除暂存区或分支上的文件, 同时工作区也不 ...
- 折腾一两天,终于学会使用grunt压缩合并混淆JS脚本,小激动,特意记录一下+spm一点意外收获
很长时间没有更新博客了,实在是太忙啦...0.0 ,以下的东西纯粹是记录,不是我原创,放到收藏夹还担心不够,这个以后常用,想来想去,还是放到这里吧,,丢不了..最后一句废话,网上搜集也好原创也罢,能解 ...
- 为什么匿名内部类的参数必须为finalhttp://feiyeguohai.iteye.com/blog/1500108
1) 从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变量(形式参数或局部变量)是天经地义的.是很自 ...
- 经典的javascript函数实例,css的. #区别
先贴javascript经典例子代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &quo ...
- TortoiseSVN的合并对比工具TortoiseMerge启动时很慢很卡的解决办法
用了新版本的TortoiseSVN,但是在进行文件版本对比或者是解决冲突的时候,每次都要等上好几秒钟,TortoiseMerge窗口才显示出来.在Visual Studio中使用这个作为SVN工具,也 ...
- TabSiPlus发布1749版本
拖了几天,终于可以发布了,下载地址如下: http://www.winmsg.com/download/TabSiPlus_0_99b2_1749.rar 这个包里没有附带VC的运行库,考虑到大多数人 ...
- TortoiseGit 相关操作
1.TortoiseGit 记住用户名和密码的方法当你安装且配置好git后,在C:\Documents and Settings\Administrator\ 目录下有一个 .gitconfig 的 ...
- sys.stdout.write与sys.sterr.write(二)
目标: 1.使用sys.stdout.write模拟火车道轨迹变化过程 2.使用sys.stderr.write模拟火车道轨迹变化过程 1.sys.stdout.write模拟火车道轨迹变化 代码如下 ...
- Android下安装应用不成功解决
在手机上安装应用程序不成功,可以尝试把手机连接电脑,然后使用adb进行安装,adb安装命令: adb install Android_65632.apk 当出现: success!就表示成功,但当不成 ...