什么是A20门
在8086/8088中,只有20根地址总线,所以可以访问的地址是2^20=1M,但由于8086/8088是16位地址模式,能够表示的地址范围是0-64K,所以为了在8086/8088下能够访问1M内存,Intel采取了分段的模式:16位段基地址:16位偏移.其绝对地址计算方法为:16位基地址左移4位+16位偏移=20位地址.但这种方式引起了新的问题,通过上述分段模式,能够表示的最大内存为:FFFFh:FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes(1M多余出来的部分被称做高端内存区HMA).但8086/8088只有20位地址线,如果访问100000h-10FFEFh之间的内存,则必须有第21根地址线.所以当程序员给出超过1M
(100000H-10FFEFH)的地址时,系统并不认为其访问越界而产生异常,而是自动从重新0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around.到了80286,系统的地址总线发展为24根,这样能够访问的内存可以达到2^24=16M.Intel在设计80286时提出的目标是,在实模式下系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,在实模式下80286以及后续系列,应该和8086/8088完全兼容.但最终,80286芯片却存在一个BUG:如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存,而不是象过去一样重新从0开始.
为了解决上述问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根),被称为A20 Gate.如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式.绝大多数IBM PC兼容机默认的A20 Gate是被禁止的.由于在当时没有更好的方法来解决这个问题,所以IBM使用了键盘控制器来操作A20 Gate.在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制.为了能够访问10FFEFH以上的内存,则必须进入保护模式.
A20,其实它就是对于20-bit(从0开始数)的特殊处理(也就是对第21根地址线的处理).如果A20 Gate被禁止,对于80286来说其地址为24bit,其地址表示为EFFFFF;对于80386极其随后的32-bit芯片来说,其地址表示为FFEFFFFF.这种表示的意思是如果A20 Gate被禁止,则其第20-bit在CPU做地址访问的时候是无效的,永远只能被作为0;如果A20 Gate被打开,则其第20-bit是有效的,其值既可以是0,又可以是1.所以,在保护模式下,如果A20 Gate被禁止,则可以访问的内存只能是奇数1M段,即1M,3M,5M…,也就是00000-FFFFF,200000-2FFFFF,300000-3FFFFF….如果A20 Gate被打开,则可以访问的内存则是连续的.
如何开启A20地址线
多数PC都使用键盘控制器(8042芯片)来处理A20 Gate.从理论上讲,打开A20 Gate的方法是通过设置8042芯片输出端口(64h)的2nd-bit,但事实上,当你向8042芯片输出端口进行写操作的时候,在键盘缓冲区中,或许还有别的数据尚未处理,因此你必须首先处理这些数据.处理过程如下:
1. 禁止中断;
2. 等待,直到8042 Input buffer为空为止;
3. 发送禁止键盘操作命令到8042 Input buffer;
4. 等待,直到8042 Input buffer为空为止;
5. 发送读取8042 Output Port命令;
6. 等待,直到8042 Output buffer有数据为止;
7. 读取8042 Output buffer,并保存得到的字节;
8. 等待,直到8042 Input buffer为空为止;
9. 发送Write 8042 Output Port命令到8042 Input buffer;
10. 等待,直到8042 Input buffer为空为止;
11. 将从8042 Output Port得到的字节的第2位置1(OR 2),然后写入8042Input buffer;
12. 等待,直到8042 Input buffer为空为止;
13. 发送允许键盘操作命令到8042 Input buffer;
14. 打开中断。
如何检测A20地址线是否开启
我们在之前已经提到,如果A20 Gate被打开了,则在实模式下,程序员可以直接访问100000H~10FFEFH之间的内存,如果A20 Gate被禁止,则在实模式下,若程序员访问100000H~10FFEFH之间的内存,则会被硬件自动转换为0H~0FFEFH
之间的内存,所以我们可以利用这个差异来检测A20 Gate是否被打开.
CR0寄存器
CR0寄存器是处理器4个控制寄存器之一,结构如下
+----+--------------------------+----+----+----+----+----+
| PG | Reserved | ET | TS | EM | MP | PE |
+----+--------------------------+----+----+----+----+----+
31 30 5 4 3
BIT 0:PE 如果该位被置位,则运行保护模式,否则运行实模式
BIT 1:MP 控制wait指令
BIT 2:EM 表示协处理器功能是否可以被仿真
BIT 3:TS 用于任务转换
BIT 4:ET 表示当前协处理器的类型(80287或80387)
BIT 31:PG表示处理器是否使用分页机制
描述符表
在保护模式下内存的寻址方式跟实模式下不同,它并不是简单的将段寄存器的16位地址左移4位,然后加上偏移量来构成20位地址.在保护模式下,是可以寻址4GB空间的,因为有32跟地址线,所以2^32 = 4GB.在该模式下是通过段寄存器的索引值在描述符表(全局描述符表GDT或者局部描述符表LDT)内寻找到相应的表项,然后通过获取表项里保存的段基地址,段基地址与偏移量相加得到线性地址.如果系统没采用分页处理模式,那么线性地址就对应着实际的物理地址了.分页处理模式放到内存管理文档里详细说明.
段寄存器格式:
+--------------------------------+------+-----+
| Index | TI | RPL |
+--------------------------------+------+-----+
15 32 1 0
RPL: 优先级别 0 - 4 LINUX只用了0核心态 3用户态
TI: 0使用全局描述符表(GDT),1使用局部描述符表(LDT)
描述符表是保存在gdtr或者ldtr寄存器里的,该寄存器共占用6个字节大小.0-15位是表限,16-47位是表的基地址.表限是表示该表的大小,基地址是表示描述符表所在的线性地址中的位置.
全局描述符号表和局部描述符号表的结构是一样的,如下:
+---------------------------------+----------+
| Base address | Limit |
+---------------------------------+----------+
47 1615 0
描述符表项是存储在描述符表里的内容.Intel公司将表项的第一个项设为空,从第2个表项开始使用.描述符表项的结构定义如下:
63 5655 5251 4847 4039 3231 1615 87 0
+---------+----+-------+-----+---------+-------------+-----------------+
|B31 - B24| |L19-L16| |B23 - B16|基地址 B15-B0|段上限L15 - L0|
+---------+----+-------+-----+---------+-------------+-----------------+
+------+------+------+------+
| G | D/B | O | AV |
+------+------+------+------+
55 52
G: =1 段长以4K字节为单位 =0 段长以字节为单位
D/B: =1 表示对该段访问为32位指令 =0 表示为16位指令
O: 永远为0
AV: 可由软件使用,CPU忽略该位
+-----+----------+-----+-----+-------+-------+------+
| P | DPL | S | E | ED/C | R/W | A |
+-----+----------+-----+-----+-------+-------+------+
47 44 43 40
A: =1 已经被访问过 =0 还未被访问过
R/W: ---+
ED/C: ---|----+---> +---- E = 0 数据段
E: ---+ | |
| +---- ED = 0 向上伸(数据段) ED = 1 向下伸(堆栈段)
| |
| +---- W = 0不可写 W = 1 可写
|
+---> +---- E = 1代码段
|
+---- C = 0 忽略特权级 C = 不忽略特权级
|
+---- R = 0 不能读 R = 1可读
S = 0表示用于系统管理的系统段 = 1表示一般的代码段或者数据段
DPL: 特权级
P = 1该段在内存中 = 0 不在内存中