A20:历史的妥协

A20: 历史的妥协

今天看 MIT 6.828 中 JOS 的 Bootloader 部分,可以说是看几行代码查一小时资料了。

最近看英文文档比较多,中文社区这块相关的内容是在太过匮乏,还有不少错误,所以自己就来翻译一下。

boot.S中,Boot loader 开启了 A20 Line,并且注释说到

为了向下兼容旧 PC(的应用程序),物理地址的第 20 位被锁定为低位(也就是 0),所以高于 1MB 的内存会

被 wrap around 为 0 。而这段代码则正是为了将这一机制取消。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.1

movb $0xd1,%al # 0xd1 -> port 0x64
outb %al,$0x64

seta20.2:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.2

movb $0xdf,%al # 0xdf -> port 0x60
outb %al,$0x60

这个 20 位和 1MB 比较好理解。

寻址空间由地址总线数量 N 决定:

Size = 2^N Bytes

2021 年了,寻址空间已经大大的有了,而在上个世纪,Intel 8088 的地址总线只有 20 条,所以寻址空间为 2^20 Bytes,也就是 1 MB。至于这个 Wrap around是什么,还得搜搜才知道。

A20 是什么

A20,或者说 20 号地址总线,是 X86 地址总线的第 21 根,对应总线上的第 21 位。

注意,从第 0 位开始,而不是第 1 位。这就是为什么控制第 21 根总线却被叫做 A20

Gate-A20 是什么

Intel 8088 的时代遗风

最初,PC 中的 Intel 8088 只有 20 条地址总线,可寻址空间达到 1MB (2^20 Bytes) ,但是 8088 的寄存器都是 16 位的,这意味着其可表达地址最大为64KB(2^16 Bytes)。这差距可太大了,Intel 想出了分段寻址的方式解决了这个问题,使用两个 16 位寄存器,最终地址可以采用如下公式表达:

Address = selector * 16 + offset

基于该算法,地址最大为FFFF:FFFF,通过算法可以得到其对应的物理地址是0x10FFEF,这显然已经超过了 1MB,于是 Intel 采用了一个截断方案:

当程序员给出超过 1MB(即位于0x100000-0x10FFEF)的地址时,因为逻辑上正常,系统并不认为其访问越界而产生异常,而是自动从 0 开始计算,也就是说系统计算实际地址的时候是按照对 1MB求模的方式进行的。

所以,0x10FFEF就变成了0x00FFEF

许多程序利用这一点,使不改变微处理器的段寄存器而去访问最开始的 64KB 内存成为一个通用的技巧

译注

与国内大部分的转载文不同,OSDev Wiki 的说法是:

For some reason a few short-sighted programmers decided to write programs that actually used this wraparound

Intel 80826 时代的妥协

随后 Intel 80286 横空出世,得益于其地址总线增加到 24 根,Intel 引入了 保护模式(Protected Mode),使得可访问寻址空间达到了16MB

为了向后兼容,Intel 还引入了实模式,旨在对 Intel 8088 的百分百兼容并仍然使用 20 根地址总线,这意味着实模式下寻址空间仍然被限制在 1MB 且沿用截断机制。然而,由于一个 BUG,这种地址截断并未生效:如果程序员访问0x100000-0x10FFEF之间的内存,系统将实际访问这块内存,而不是预期中的那样像 8088 一样截断。

由于历史遗留,大量程序依赖于这种截断运作,为了实现完美的兼容性,IBM 在系统总线与处理器中间加了一个逻辑门,用于控制 A20 的工作,因此得名 Gate-A20

Gate-A20 可以通过软件进行开/关从而达到允许/阻止系统总线接受 A20 的信号,为了使依赖于截断的旧程序正常运作,需要设置 Gate-A20 为关闭状态。

但是对于许多运行在保护模式下的 OS,开启 Gate-A20 是在控制权交给 Kernel 之前做的第一件事情,否则无法访问所有内存。因为在断掉来自 A20 的信号后,地址的第21位永远是 0,这相当于内存有接近一半的合法地址无法被表示。

A20 原理

上段提到,当 Gate-A20 关闭后,地址总线的第 21 位永远为 0。

当表示 1MB 以下的空间时,表现不受影响。

当尝试表示 1MB 以上的空间时,将会出现断层:

[1MB,2MB) , [3MB,4MB)……这些区间的内存地址将无法表示,且占据[1MB,infinity]的一半

由于实模式下的程序最多访问到 1MB 以上再加一点点的空间,所以这对于实模式的兼容是完全足够的

如何控制 Gate-A20

键盘

起初,这个逻辑门被连接在 Intel 8042 键盘控制器上……因为这个芯片有一个空闲的接口

并通过 硬件 I/O 操作 发送信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void init_A20(void)
{
uint8_t a;
disable_ints();
kyb_wait_until_done();
kyb_send_command(0xAD); // disable keyboard

kyb_wait_until_done();
kyb_send_command(0xD0); // Read from input

kyb_wait_until_done();
a=kyb_get_data();

kyb_wait_until_done();
kyb_send_command(0xD1); // Write to output

kyb_wait_until_done();
kyb_send_data(a|2);

kyb_wait_until_done();
kyb_send_command(0xAE); // enable keyboard

enable_ints();
}

译注

如果你可以找到比较那种比较老的主机,上面的 PS/2 接口所连接的可能就是这个芯片

这听起来有些离谱,因为你根本想不到会把开关装在键盘控制器上,但是那个时代确实没什么所谓“标准”。

当 PC 启动时,Gate-A20 总是默认关闭的,但某些 BIOS 和 模拟器(比如 QEMU) 会打开,然后做高位内存管理。

译注

高位内存管理听起来怪怪的,附原话

do some high-memory managers

这好像是荷兰人写的 Wiki

来自 Kernel Developers 的参考提到,BIOS 会打开 Gate-A20 用于测试系统内存大小与可用性,这可能是访问高位内存的原因

有些 Bootload 也会打开 Gate-A20 ,比如 GRUB 会在进入保护模式时自动打开 Gate-A20

其他方法

还可以使用 INT 15H等方法开启 Gate-A20,不再赘述,参考 OSDev Wiki - A20 Line

参考

A20 - a pain from the past (tue.nl)

A20 line - Wikipedia

A20 Address Line - Kernel Developers (weebly.com)

A20 Line - OSDev Wiki

Author

BakaFT

Posted on

2021-10-24

Updated on

2023-12-28

Licensed under

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×