- A+
31337 h4x0r 有一回对我说道,“你读过 x86 平台的底层代码么?”我略略点一点头。他说,“读过底层,……我便考你一考。x86 的电脑上,怎样重启的?”我想,脚本小子一样的人,也配考我么?便回过脸去,不再理会。
31337 h4x0r 等了许久,很恳切的说道,“不会重启电脑罢?……我教给你,记着!这些方法应该记着。将来做黑客的时候,开发要用。”我暗想我和黑客的等级还很远呢,而且我们编程序也不是用来重启机器;又好笑,又不耐烦,懒懒的答他道,“谁要你教,不就是 ACPI 去写一个
ResetReg
么?”31337 h4x0r 显出极高兴的样子,将两个指头的长指甲敲着键盘,点头说,“对呀对呀!……x86 有六样重启法,你知道么?”我愈不耐烦了,努着嘴走远。
31337 h4x0r 刚启动了 Emacs,想在编辑器上打字,见我毫不热心,便又叹一口气,显出极惋惜的样子。
Linux 内核支持全部六种方法……但为什么要支持六种?
有一些重启方法可能存在硬件实现问题,例如 8042 键盘控制器的方法,是支持最广泛,但也是电脑硬件 bug 最可能发生的方法。尽管 Linux 内核会逐一尝试每一种方法,很可能在尝试其中一种后导致硬件卡死。由于这些机器多半只在 Windows 上测试过,因此直到 2010 年左右,在 Linux 上重新启动机器时无法断电依然是比较常见的问题。此时,用户就需要逐一尝试这些重启方法,找到一种能用的,并向上游报告,上游将机器的型号和需要的重启方法加入表中。在现在出产的新机器上,这种问题已经非常罕见。如果你日后遇到了重启的问题,希望你能想起这篇文章,并对你有所帮助。
没错,六种方法重启,而且每种都不一定能工作,重启时需要逐一尝试,必然时候还需要查表,这就是充满历史遗产的 x86 世界的现状。
方法一:8042 键盘控制器的错误使用方法
8042 键盘控制器控制 x86 计算机上的 PS/2 键盘鼠标,但这大概也是 x86 计算机上能力最强大的硬件设备,因为它名义上是键盘控制器,但由于 IBM 工程师的脑洞,实际上还拥有控制 CPU 和内存的功能。历史上,8042 与 CPU 的 Reset 信号相连,还与内存总线上的一个逻辑门相连。
向 8042 键盘控制器(端口 0x64
)写入 0xFE
. 这会触发 CPU 的重置信号,并重启计算机。
1 2 | <span class="pln"><span class="hljs-function">ioperm</span></span><span class="pun"><span class="hljs-function">(</span></span><span class="lit"><span class="hljs-function"><span class="hljs-number">0</span>x64</span></span><span class="pun"><span class="hljs-function">,</span></span> <span class="lit"><span class="hljs-function"><span class="hljs-number">1</span></span></span><span class="pun"><span class="hljs-function">,</span></span> <span class="lit"><span class="hljs-function"><span class="hljs-number">1</span></span></span><span class="pun"><span class="hljs-function">)</span>;</span><span class="pln"> <span class="hljs-function">outb</span></span><span class="pun"><span class="hljs-function">(</span></span><span class="lit"><span class="hljs-function"><span class="hljs-number">0</span>xfe</span></span><span class="pun"><span class="hljs-function">,</span></span> <span class="lit"><span class="hljs-function"><span class="hljs-number">0</span>x64</span></span><span class="pun"><span class="hljs-function">)</span>;</span> |
在 Linux 上,可以使用内核参数 reboot=kbd
,让 Linux 内核使用这种方式实现重新启动的功能。
方法二:向 PCI 的 0xCF9
端口发送重置信号
Intel 的 ICH/PCH 南桥芯片同时负责部分电源工作。向 PCI 的 0xCF9
端口发送重置信号,可以要求南桥芯片重启计算机。
1 2 3 4 5 6 | <span class="pln">ioperm</span><span class="pun">(</span><span class="lit"><span class="hljs-number">0xcf9</span></span><span class="pun">,</span> <span class="lit"><span class="hljs-number">1</span></span><span class="pun">,</span> <span class="lit"><span class="hljs-number">1</span></span><span class="pun">);</span> <span class="typ">uint8_t</span><span class="pln"> reboot_code </span><span class="pun">=</span> <span class="lit"><span class="hljs-number">0x06</span></span> <span class="com"><span class="hljs-comment">/* 热启动,冷启动 0x0E */</span></span> <span class="typ">uint8_t</span><span class="pln"> cf9 </span><span class="pun">=</span><span class="pln"> inb</span><span class="pun">(</span><span class="lit"><span class="hljs-number">0xcf9</span></span><span class="pun">)</span> <span class="pun">&</span> <span class="pun">~</span><span class="pln">reboot_code</span><span class="pun">;</span><span class="pln"> outb</span><span class="pun">(</span><span class="pln">cf9</span><span class="pun">|</span><span class="lit"><span class="hljs-number">2</span></span><span class="pun">,</span> <span class="lit"><span class="hljs-number">0xcf9</span></span><span class="pun">);</span> <span class="com"><span class="hljs-comment">/* Request hard reset */</span></span><span class="pln"> usleep</span><span class="pun">(</span><span class="lit"><span class="hljs-number">50</span></span><span class="pun">);</span><span class="pln"> outb</span><span class="pun">(</span><span class="pln">cf9</span><span class="pun">|</span><span class="pln">reboot_code</span><span class="pun">,</span> <span class="lit"><span class="hljs-number">0xcf9</span></span><span class="pun">);</span> <span class="com"><span class="hljs-comment">/* Actually do the reset */</span></span> |
在 Linux 上,可以使用内核参数 reboot=pci
,让 Linux 内核使用这种方式实现重新启动的功能。
方法三:让 CPU 产生三重异常
如果代码触发了 CPU 的一个异常,并触发了事先注册的异常处理代码,但是异常处理代码本身也触发了异常,这就叫做双重异常。但这个异常处理代码本身也注册了异常处理代码,而且在执行这个异常处理代码的异常处理代码时又触发了异常,就产生了三重异常。从 80286 开始,三重异常会导致 CPU 进入 SHUTDOWN 状态,主板电路会重新启动系统。
例如:
1 2 3 4 | <span class="pln"><span class="hljs-function">load_idt</span></span><span class="pun"><span class="hljs-function">(&</span></span><span class="pln"><span class="hljs-function">no_idt</span></span><span class="pun"><span class="hljs-function">)</span>;</span> <span class="com"><span class="hljs-comment">/* 清空中断描述表 */</span></span><span class="pln"> __asm__ __<span class="hljs-function">volatile__</span></span><span class="pun"><span class="hljs-function">(</span></span><span class="str"><span class="hljs-function"><span class="hljs-string">"int3"</span></span></span><span class="pun"><span class="hljs-function">)</span>;</span> <span class="com"><span class="hljs-comment">/* 触发中断 */</span></span> <span class="com"><span class="hljs-comment">/* 此时发生中断,由于 IDT 为空,无论是中断的异常处理代码还是双重异常处理代码都不存在,触发了三重异常 /* CPU 进入 SHUTDOWN 状态,主板重置 */</span></span> |
在 Linux 上,可以使用内核参数 reboot=triple
,让 Linux 内核使用这种方式实现重新启动的功能。
尽管这个特性就在 80286 的手册附录里,而且 IBM 的工程师看到了而且还实现了必要的电路,然而 IBM 的工程师完全没有意识到这可以用来在切换到实模式时重置 CPU,而是将键盘控制器的一个端口接到 CPU 的 Reset 信号线上来进行重置 CPU 实现模式切换……
这个方法的成功率几乎是最高的,但大多数操作系统(包括 Linux 内核)不到万不得已,不会使用。虽然原则上这是为了正确进行电源管理,然而事实上的原因是,三重异常往往意味着操作系统出现了严重的故障,例如将内存中负责处理 Swap 的代码本身也移动到了 Swap 里,会立刻导致整个系统重启,数据荡然无存,开发者本来对三重异常感到恐惧,然而现在却要自己主动触发一个,实在是不能接受。此外,开发者也都认为触发个三重异常让 CPU 被迫重置实在是太残忍了,不忍心下手……因此,这种方法只会被开发者当作最终的手段。
方法四:切入实模式,跳转到 BIOS 代码重启
禁用各种东西,将 CPU 从保护模式切换回实模式,然后 ljmp $0xffff, $0x0000
跳转到 BIOS 的重置代码。 至于 BIOS 怎么重启,谁知道呢?不过只要 BIOS 实现正确,就可以工作(仅限于 32 位计算机)。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="pln">movl </span><span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">cr0</span></span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">eax</span> andl <span class="hljs-variable">$0</span>x00000011</span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">eax</span> orl <span class="hljs-variable">$0</span>x60000000</span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">eax</span> movl </span><span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">eax</span></span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">cr0</span> movl </span><span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">eax</span></span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">cr3</span> movl </span><span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">cr0</span></span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">ebx</span> andl <span class="hljs-variable">$0</span>x60000000</span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">ebx</span> jz f invd f</span><span class="pun">:</span><span class="pln"> andb <span class="hljs-variable">$0</span>x10</span><span class="pun">,</span><span class="pln"> al movl </span><span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">eax</span></span><span class="pun">,</span> <span class="pun"><span class="hljs-variable">%</span></span><span class="pln"><span class="hljs-variable">cr0</span> ljmp <span class="hljs-variable">$0</span>xffff</span><span class="pun">,</span><span class="pln"> <span class="hljs-variable">$0</span>x0000</span> |
在 Linux 上,可以使用内核参数 reboot=bios
,让 Linux 内核使用这种方式实现重新启动的功能。
方法五:通过 ACPI 重新启动
ACPI 是现代 x86 计算机必不可少的标准电源管理接口,自然也提供了重新启动计算机的功能。
在支持 ACPI 的机器固件提供的「固定 ACPI 描述表」(Fixed ACPI Description Table, FADT)中,保存有 reset_register
和 reset_value
两个数值,告知操作系统重启的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <span class="pln">rr </span><span class="pun">=</span> <span class="pun">&</span><span class="pln">acpi_gbl_FADT</span><span class="pun">.</span><span class="pln">reset_register</span><span class="pun">;</span> <span class="com"><span class="hljs-regexp">/* 获取重启寄存器 */</span></span><span class="pln"> reset_value </span><span class="pun">=</span><span class="pln"> acpi_gbl_FADT</span><span class="pun">.</span><span class="pln">reset_value</span><span class="pun">;</span> <span class="com"><span class="hljs-regexp">/* 获取重启魔法数值 */</span></span> <span class="com">/* The reset register can only exist <span class="hljs-keyword">in</span> I/O, Memory <span class="hljs-keyword">or</span> PCI config space * <span class="hljs-literal">on</span> a device <span class="hljs-literal">on</span> bus <span class="hljs-number">0.</span> */</span> <span class="com"><span class="hljs-regexp">/* 在 PCI、内存和 IO 地址中寻找重启寄存器 */</span></span> <span class="kwd"><span class="hljs-keyword">switch</span></span> <span class="pun">(</span><span class="pln">rr</span><span class="pun">-></span><span class="pln">space_id</span><span class="pun">)</span> <span class="pun">{</span> <span class="com"><span class="hljs-regexp">/* 如果寄存器在 PCI,就写 PCI */</span></span> <span class="kwd"><span class="hljs-reserved">case</span></span><span class="pln"> <span class="hljs-attribute">ACPI_ADR_SPACE_PCI_CONFIG</span></span><span class="pun">:</span> <span class="com"><span class="hljs-regexp">/* The reset register can only live on bus 0. */</span></span><span class="pln"> bus0 </span><span class="pun">=</span><span class="pln"> pci_find_bus</span><span class="pun">(</span><span class="lit"><span class="hljs-number">0</span></span><span class="pun">,</span> <span class="lit"><span class="hljs-number">0</span></span><span class="pun">);</span> <span class="kwd"><span class="hljs-keyword">if</span></span> <span class="pun">(!</span><span class="pln">bus0</span><span class="pun">)</span> <span class="kwd"><span class="hljs-keyword">return</span></span><span class="pun">;</span> <span class="com"><span class="hljs-regexp">/* Form PCI device/function pair. */</span></span><span class="pln"> devfn </span><span class="pun">=</span><span class="pln"> PCI_DEVFN</span><span class="pun">((</span><span class="pln">rr</span><span class="pun">-></span><span class="pln">address </span><span class="pun">>></span> <span class="lit"><span class="hljs-number">32</span></span><span class="pun">)</span> <span class="pun">&</span> <span class="lit"><span class="hljs-number">0xffff</span></span><span class="pun">,</span> <span class="pun">(</span><span class="pln">rr</span><span class="pun">-></span><span class="pln">address </span><span class="pun">>></span> <span class="lit"><span class="hljs-number">16</span></span><span class="pun">)</span> <span class="pun">&</span> <span class="lit"><span class="hljs-number">0xffff</span></span><span class="pun">);</span><span class="pln"> printk</span><span class="pun">(</span><span class="pln">KERN_DEBUG </span><span class="str"><span class="hljs-string">"Resetting with ACPI PCI RESET_REG."</span></span><span class="pun">);</span> <span class="com"><span class="hljs-regexp">/* Write the value that resets us. */</span></span><span class="pln"> pci_bus_write_config_byte</span><span class="pun">(</span><span class="pln">bus0</span><span class="pun">,</span><span class="pln"> devfn</span><span class="pun">,</span> <span class="pun">(</span><span class="pln">rr</span><span class="pun">-></span><span class="pln">address </span><span class="pun">&</span> <span class="lit"><span class="hljs-number">0xffff</span></span><span class="pun">),</span><span class="pln"> reset_value</span><span class="pun">);</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="com"><span class="hljs-regexp">/* 如果是内存或者 I/O 地址,直接用 writeb() /</span> outb() 写内存或 I<span class="hljs-regexp">/O */</span></span> <span class="com"><span class="hljs-regexp">/* acpi_reset() 调用的是 acpi_os_write_port(), acpi_hw_write() */</span></span> <span class="com"><span class="hljs-regexp">/* 但只是多了一些检查,最后依然调用的是 writeb() /</span> outb() */</span> <span class="kwd"><span class="hljs-reserved">case</span></span><span class="pln"> <span class="hljs-attribute">ACPI_ADR_SPACE_SYSTEM_MEMORY</span></span><span class="pun">:</span> <span class="kwd"><span class="hljs-reserved">case</span></span><span class="pln"> <span class="hljs-attribute">ACPI_ADR_SPACE_SYSTEM_IO</span></span><span class="pun">:</span><span class="pln"> printk</span><span class="pun">(</span><span class="pln">KERN_DEBUG </span><span class="str"><span class="hljs-string">"ACPI MEMORY or I/O RESET_REG.\n"</span></span><span class="pun">);</span><span class="pln"> acpi_reset</span><span class="pun">();</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="pun">}</span> |
写入数值后触发硬件重启。 在 Linux 上,可以使用内核参数 reboot=acpi
,让 Linux 内核使用这种方式实现重新启动的功能。
方法六:通过 EFI/UEFI 重启
EFI/UEFI 是更现代计算机固件的接口标准,其中包括 UEFI 启动服务和 UEFI 运行服务,它们提供了一组标准的函数调用,提供一些和硬件相关的服务。好比系统调用为应用程序提供服务,UEFI 服务则为操作系统提供服务。在操作系统启动后,会运行 ExitBootServices
禁用 UEFI 启动服务,但是 UEFI 运行服务的代码会一直处于内存中,而且随时可以被系统调用执行。
1 2 3 4 5 6 7 | <span class="pln">efi_mode </span><span class="pun">=</span><span class="pln"> EFI_RESET_WARM</span><span class="pun">;</span> <span class="com"><span class="hljs-comment">/* 或 efi_mode = EFI_RESET_COLD; */</span></span> <span class="com"><span class="hljs-comment">/* reset_system 是 efi 结构体中的函数指针,其原型是:*/</span></span> <span class="com"><span class="hljs-comment">/* void efi_reset_system_t (int reset_type, efi_status_t status, unsigned long data_size, efi_char16_t *data); */</span></span> <span class="com"><span class="hljs-comment">/* 系统在通过 EFI 启动时,正确的内存地址会被初始化 */</span></span><span class="pln"> efi</span><span class="pun">.</span><span class="pln">reset_system</span><span class="pun">(</span><span class="pln">efi_mode</span><span class="pun">,</span><span class="pln"> EFI_SUCCESS</span><span class="pun">,</span> <span class="lit"><span class="hljs-number">0</span></span><span class="pun">,</span><span class="pln"> <span class="hljs-keyword">NULL</span></span><span class="pun">);</span> |
由于 EFI 的高度复杂性,可以直接阅读 EFI 相关文献。 在 Linux 上,可以使用内核参数reboot=efi
,让 Linux 内核使用这种方式实现重新启动的功能。
Linux 内核的逐一尝试
Linux 内核中,默认使用一个无限循环的 switch {}
语句逐一执行这些重启方法,如果成功了,那么机器就会重启,否则就会切换到下一种方法。如果硬件有已知问题,Linux 内核会查表,并根据机器型号选择合适的方式,并可能会进行一些 workaround。最后,有专门支持的特定计算机硬件会使用硬件自身的方法重启。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <span class="kwd"><span class="hljs-keyword">for</span></span> <span class="pun">(;;)</span> <span class="pun">{</span> <span class="kwd"><span class="hljs-keyword">switch</span></span> <span class="pun">(</span><span class="pln">reboot_type</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_ACPI</span><span class="pun">:</span> <span class="com"><span class="hljs-comment">// ...</span></span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_KBD</span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_KBD</span><span class="pun">:</span> <span class="com"><span class="hljs-comment">/* 代码 */</span></span> <span class="kwd"><span class="hljs-keyword">if</span></span> <span class="pun">(</span><span class="pln">attempt </span><span class="pun">==</span> <span class="lit"><span class="hljs-number">0</span></span> <span class="pun">&&</span><span class="pln"> orig_reboot_type </span><span class="pun">==</span><span class="pln"> BOOT_ACPI</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> attempt </span><span class="pun">=</span> <span class="lit"><span class="hljs-number">1</span></span><span class="pun">;</span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_ACPI</span><span class="pun">;</span> <span class="pun">}</span> <span class="kwd"><span class="hljs-keyword">else</span></span> <span class="pun">{</span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_EFI</span><span class="pun">;</span> <span class="pun">}</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_EFI</span><span class="pun">:</span> <span class="com"><span class="hljs-comment">/* 代码 */</span></span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_BIOS</span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_BIOS</span><span class="pun">:</span> <span class="com"><span class="hljs-comment">/* 代码 */</span></span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_CF9_SAFE</span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_CF9_FORCE</span><span class="pun">:</span><span class="pln"> port_cf9_safe </span><span class="pun">=</span> <span class="kwd"><span class="hljs-literal">true</span></span><span class="pun">;</span> <span class="com"><span class="hljs-comment">/* Fall through */</span></span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_CF9_SAFE</span><span class="pun">:</span> <span class="com"><span class="hljs-comment">/* 代码 */</span></span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_TRIPLE</span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">case</span></span><span class="pln"> BOOT_TRIPLE</span><span class="pun">:</span> <span class="com"><span class="hljs-comment">/* 代码 */</span></span> <span class="com"><span class="hljs-comment">/* We're probably dead after this, but... */</span></span><span class="pln"> reboot_type </span><span class="pun">=</span><span class="pln"> BOOT_KBD</span><span class="pun">;</span> <span class="kwd"><span class="hljs-keyword">break</span></span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
为什么用键盘控制器能重启电脑?
随着个人计算机和半导体技术突飞猛进的发展,到了 1984 年,IBM 发表了具有划时代意义的 IBM PC/AT 计算机(即 IBM 5170)。PC/AT 搭载了强大的 80268 CPU,支持的内存也从 20 位的 1 MiB 变成了 24 位的 16 MiB。这一扩展地址可就惨了。在旧的 8086 上,尝试访问大于 1 MiB 的内存,会溢出回到内存的开头,后来有程序员也发现了这个现象,并利用它优化程序。但在 80286 上,由于大于 1 MiB 的内存确实存在,因此不会溢出也不会回到内存开头……本来 80286 默认使用的是实模式(而不是新的保护模式),就是为了完美兼容现有的 8086 程序的,但由于 Intel 并没有意识到这个问题,因此导致大量现有的程序停止工作。
IBM 的工程师为了解决这个兼容问题,他们在主板的内存地址总线的信号线上装了一个逻辑门,作为信号开关。当关闭时,就会一直产生信号 0,相当于关闭了超过 1 MiB 的内存,同时由于电路的特点,也能让这个溢出魔法重新恢复正常。但是,在哪里控制这个开关呢?PC AT 计算机使用 8042 芯片控制键盘,工程师看到芯片上正好有多余的端口,于是脑洞大开,用这个键盘芯片来控制 1 MiB 内存的开启。这就是著名的 A20。至今,让键盘控制器开启 A20 依然是进入保护模式不可或缺的一步。
同时,由于很多时候还需要在系统里兼容旧程序,有时还需要进入新的保护模式后,再切换回旧的实模式,但 80268 进行这个模式切换时,必须进行 CPU 重置。于是,IBM 的工程师把 8042 键盘控制器上另一个端口,连接到了CPU 的 Reset 信号线上……直到今天,x86 计算机上依然需要兼容 AT 机的 8042,因此,8042 键盘控制器能用来重启几乎一切 x86 电脑。
后来发现,键盘控制器的反应速度实在是太慢了,如果需要频繁 A20 切换会有性能问题,因为 IBM 当初的这个坑爹设计,日后系统开发者和 Intel 还花费了很大的代价解决。到了今天,x86 处理器里有一个专门模拟 A20 的 I/O 口……
如果说 8086 芯片是通往 x86 大坑的一把洛阳铲,那么这台 IBM 电脑则是造就了 x86 体系无数大坑的挖掘机。觉得 AT 很陌生?那在 AT 后面加个「X」呢?此外,你垃圾键盘右上角的 3 个刺眼的指示灯,Windows 用户不知道有什么用的 SysRq 按键,以及主板里一没电就掉时间日期,还时不时漏液损坏主板的纽扣电池,还有那经常出现诡异问题不能正常引导系统的 MBR,全都是 AT 的设计。更别说那个电源适配器负载一低就没有 +12V 输出,于是 IBM 在低端机器上安装了个 50 W 的功率电阻用来费电,幸好这一点上我们没有保持兼容……无论怎么说,这开启了一个时代,你现在的计算机很可能依然或多或少的兼容 AT。我们所说的「IBM 兼容机」指的就是 PC/AT 兼容。
