架构图
8086 CPU 架构图
数据单元
为了暂存数据,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 就是出栈。
凡是与函数调用相关的操作,都与栈紧密相关。例如,A 调用 B,B 调用 C。当 A 调用 B 的时候,要执行 B 函数的逻辑,因而 A 运行的相关信息就会被 push 到栈里面。当 B 调用 C 的时候,同样,B 运行相关信息会被 push 到栈里面,然后才运行 C 函数的逻辑。当 C 运行完毕的时候,先 pop 出来的是 B,B 就接着调用 C 之后的指令运行下去。B 运行完了,再 pop 出来的就是 A,A 接着运行,直到结束。
在 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,超过这个空间就访问不到了。这又是为啥呢?如果你想访问 1M+X 的地方,这个位置已经超过 20 位了,由于地址总线只有 20 位,在总线上超过 20 位的部分根本是发不出去的,所以发出去的还是 X,最后还是会访问 1M 内的 X 的位置。
那一个段最大能有多大呢?因为偏移量只能是 16 位的,所以一个段最大的大小是 2^16=64k。
32 位处理器
在 32 位处理器中,有 32 根地址总线,可以访问 2^32=4G 的内存。
首先,通用寄存器有扩展,可以将 8 个 16 位的扩展到 8 个 32 位的,但是依然可以保留 16 位的和 8 位的使用方式。
其中,指向下一条指令的指令指针寄存器 IP,就会扩展成 32 位的,同样也兼容 16 位的。
改动比较大,有点不兼容的就是段寄存器(Segment Register)。
CS、SS、DS、ES 仍然是 16 位的,但是不再是段的起始地址。段的起始地址放在内存的某个地方。
这个地方是一个表格,表格中的一项一项是段描述符(Segment Descriptor)。
这里面才是真正的段的起始地址。而段寄存器里面保存的是在这个表格中的哪一项,称为选择子(Selector)。
将一个从段寄存器直接拿到的段起始地址,就变成了先间接地从段寄存器找到表格中的一项,再从表格中的一项中拿到段起始地址。
这样段起始地址就会很灵活了。当然为了快速拿到段起始地址,段寄存器会从内存中拿到 CPU 的描述符高速缓存器中。
因而到了 32 位的系统架构下,我们将前一种模式称为实模式(Real Pattern),后一种模式称为保护模式(Protected Pattern)。
当系统刚刚启动的时候,CPU 是处于实模式的,这个时候和原来的模式是兼容的。
也就是说,哪怕你买了 32 位的 CPU,也支持在原来的模式下运行,只不过快了一点而已。
当需要更多内存的时候,你可以遵循一定的规则,进行一系列的操作,然后切换到保护模式,就能够用到 32 位 CPU 更强大的能力。