x86 来历

早期的 IBM 凭借大型机技术成为计算机市场的领头羊,直到后来个人计算机兴起,苹果公司诞生。但是,那个时候,无论是大型机还是个人计算机,每家的 CPU 架构都不一样。如果一直是这样,个人电脑、平板电脑、手机等等,都没办法形成统一的体系,就不会有我们现在通用的计算机了,更别提什么云计算、大数据这些统一的大平台了。好在历史将 x86 平台推到了开放、统一、兼容的位置。我们继续来看 IBM 和 x86 的故事。

IBM 开始做 IBM PC 时,一开始并没有让最牛的华生实验室去研发,而是交给另一个团队。一年时间,软硬件全部自研根本不可能完成,于是他们采用了英特尔的 8088 芯片作为 CPU,使用微软的 MS-DOS 做操作系统。

谁能想到 IBM PC 卖得超级好,好到因为垄断市场而被起诉。IBM 就在被逼的情况下公开了一些技术,使得后来无数 IBM-PC 兼容机公司的出现,也就有了后来占据市场的惠普、康柏、戴尔等等。

能够开放自己的技术是一件了不起的事。从技术和发展的层面来讲,它会使得一项技术大面积铺开,形成行业标准。就比如现在常用的 Android 手机,如果没有开放的 Android 系统,我们也没办法享受到这么多不同类型的手机。对于当年的 PC 机来说,其实也是这样。英特尔的技术因此成为了行业的开放事实标准。由于这个系列开端于 8086,因此称为 x86 架构。

8086 处理器

2dc8237e996e699a0361a6b5ffd4871c.webp

我们先来看数据单元。为了暂存数据,8086 处理器内部有 8 个 16 位的通用寄存器,也就是 CPU 内部的数据单元,分别是 AX、BX、CX、DX、SP、BP、SI、DI。这些寄存器主要用于在计算过程中暂存数据。

这些寄存器比较灵活,其中 AX、BX、CX、DX 可以分成两个 8 位的寄存器来使用,分别是 AH、AL、BH、BL、CH、CL、DH、DL,其中 H 就是 High(高位),L 就是 Low(低位)的意思。这样,比较长的数据也能暂存,比较短的数据也能暂存。

接着我们来看控制单元。IP 寄存器就是指令指针寄存器(Instruction Pointer Register),指向代码段中下一条指令的位置。CPU 会根据它来不断地将指令从内存的代码段中,加载到 CPU 的指令队列中,然后交给运算单元去执行。

如果需要切换进程呢?每个进程都分代码段和数据段,为了指向不同进程的地址空间,有四个 16 位的段寄存器,分别是 CS、DS、SS、ES。

其中,CS 就是代码段寄存器(Code Segment Register),通过它可以找到代码在内存中的位置;DS 是数据段的寄存器,通过它可以找到数据在内存中的位置。

SS 是栈寄存器(Stack Register)。栈是程序运行中一个特殊的数据结构,数据的存取只能从一端进行,秉承后进先出的原则,push 就是入栈,pop 就是出栈。ES(Extra Segment)看作是一个扩展。

在 CS 和 DS 中都存放着一个段的起始地址。代码段的偏移量在 IP 寄存器中,数据段的偏移量会放在通用寄存器中。这时候问题来了,CS 和 DS 都是 16 位的,也就是说,起始地址都是 16 位的,IP 寄存器和通用寄存器都是 16 位的,偏移量也是 16 位的,但是 8086 的地址总线地址是 20 位。怎么凑够这 20 位呢?

方法就是“起始地址 *16+ 偏移量”,也就是把 CS 和 DS 中的值左移 4 位,变成 20 位的,加上 16 位的偏移量,这样就可以得到最终 20 位的数据地址。从这个计算方式可以算出,无论真正的内存多么大,对于只有 20 位地址总线的 8086 来讲,能够区分出的地址也就 2^20=1M,超过这个空间就访问不到了。

而且因为偏移量只能是 16 位的,所以一个段最大的大小是 2^16=64k。对于 8086CPU,最多只能访问 1M 的内存空间,还要分成多个段,每个段最多 64K。尽管我们现在看来这不可想象得小,根本没法儿用,但是在当时其实够用了。

32 位处理器

在 32 位处理器中,有 32 根地址总线,可以访问 2^32=4G 的内存。使用原来的模式肯定不行了,但是又不能完全抛弃原来的模式,因为这个架构是开放的。我们下面来说说,在开放架构的基础上,如何保持兼容呢?

首先,通用寄存器有扩展,可以将 8 个 16 位的扩展到 8 个 32 位的,但是依然可以保留 16 位的和 8 位的使用方式。

e3f4f64e6dfe5591b7d8ef346e8e8884.webp

以前由于硬件原因,地址总线有 20 位, 但是寄存器只有 16 位。 寄存器没能保存整个地址的能力。所以使用【段寄存器+其他寄存器】来组合表示 20 位的地址。

段寄存器里的数据左移四位 + 其他寄存器里的数据得到完成地址。 后来发展到 32 位寄存器,已经能独立存储完整的地址,为了兼容以前历史的情况,段寄存还是保留了 16 位。不过获取段起始地址的方式有所改变:

段起始地址存储在一段连续的内存中,可以理解为一个表格。段寄存器里存的是指向表格某一项的索引。 在32位的架构下, 系统启动有两种模式可以切换,【实模式】直接获取到段的起始地址,即以前的实现方式;【保护模式】则是间接获取其实地址,即第二种实现方式 通过切换模式,来兼容历史,同时在保护模式下,又具备很强的扩展性。
e2e92f2239fe9b4c024d300046536d76.webp

总结延伸

  • CPU 包括: 运算单元, 数据单元, 控制单元
    • 运算单元 不知道算哪些数据, 结果放哪
    • 数据单元 包括 CPU 内部缓存和寄存器, 暂时存放数据和结果
    • 控制单元 获取下一条指令, 指导运算单元取数据, 计算, 存放结果
  • 进程包含代码段, 数据段等, 以下为 CPU 执行过程:
    • 控制单元 通过指令指针寄存器(IP), 取下一条指令, 放入指令寄存器中
      • 指令包括操作和目标数据
    • 数据单元 根据控制单元的指令, 从数据段读数据到数据寄存器中
    • 运算单元 开始计算, 结果暂时存放到数据寄存器
  • 两个寄存器, 存当前进程代码段和数据段起始地址, 在进程间切换
  • 总线包含两类数据: 地址总线和数据总线

  • x86 开放, 统一, 兼容
  • 数据单元 包含 8个 16位通用寄存器, 可分为 2个 8位使用
  • 控制单元 包含 IP(指令指针寄存器) 以及 4个段寄存器 CS DS SS ES
    • IP 存放指令偏移量
    • 数据偏移量存放在通用寄存器中
    • 段地址<<4 + 偏移量 得到地址

  • 32 位处理器
  • 通用寄存器 从 8个 16位拓展为 8个 32位, 保留 16位和 8位使用方式
  • IP 从 16位扩展为 32位, 保持兼容
  • 段寄存器仍为 16位, 由段描述符(表格, 缓存到 CPU 中)存储段的起始地址, 由段寄存器选择其中一项
    • 保证段地址灵活性与兼容性

  • 16位为实模式, 32位为保护模式
  • 刚开机为实模式, 需要更多内存切换到保护模式