微机原理与接口技术——快速看懂第二章的8086/8088CPU结构图
前言
伴随着从研究生考场摇摇晃晃的走出来,这半年的考研生活算是告一段落。正当准备扔掉一堆讲义开始潇洒的时候,发现考研居然复试有笔试,而且复试内容《微机原理》我还没学过(乐)。简单学了一下,发现这玩意跟《计算机组成原理》差不多,不仅内容有相似之处,令人头痛程度更是不分伯仲。一方面是为了借着写作学的更扎实一点,一方面是手痒了想写东西,总之记录一下自己学习这门课的理解过程。
关于8086/8088的CPU图
网上找到的图(本图来自西安交通大学的mooc,课程质量很好,推荐学习)基本都如下,充满意义不明且对于初学者来说视觉冲击力极大的的英文缩写。

那么本文的内容就是从我的学习过程,也就是一切以初学者角度出发,较为肤浅的,尽可能易于理解且正确的梳理整个结构图,使得梳理完之后能让阅读者有一个理解并且能有个印象。
8086和8088
这大概是在上网查资料学习过程中遇到的第一个问题。这两个型号在网上经常伴随着出现,然而课本上却是以8086居多。事实上,这两个型号在内部没有太大的差别。最最主要体现在以下:
8086:内部和外部数据总线均为16位。
8088:内部数据总线为16位,外部数据总线为8位
两者都是intel推出的第三代处理器,8086在前,8088在后。8086使用了16位的外部数据总线,在与外界交互时效率更高,然而有一个问题是,当前当时许多外围设备和芯片使用的都是8位数据总线,没法兼容这个新推出来的芯片,那么intel做出一些调整,在内部数据总线不变的情况下,砍掉8个外部数据总线,就有了8088。
此外,8086和8088在包括但不限于:指令队列容量,引脚功能差异,时钟频率等都有一些小的差异,但最核心的还是外部数据总线的不同至于内部结构,基本是大同小异的。所以,不管课本上是8086还是8088,都不必太纠结。
什么是BIU和EU
BIU的中文全称是总线接口单元,EU全称是执行单元。
BIU和总线打交道,主要负责和外界交互。CPU仅仅是一个处理器,他要处理的数据,执行的指令,来自内存,内存和CPU通过总线交互。除此之外,CPU还要接受时间同步,中断请求(在有更重要的指令的时候),总线控制器(总线控制器和BIU协同管理总线访问)。数据要通过总线运输进来,那运输什么,运输到哪,什么时候运输,输进来还是输出去,由BIU负责和外界交互协调。
EU(执行单元) 负责从BIU的指令队列中获取指令,进行译码和执行,完成算术逻辑运算、数据移动和控制操作,是CPU中实际执行指令的核心部件。
也就是说,8086的两个部分一个主外,一个主内,两个部分协同工作,完成指令。
BIU的各个部分
那么接下来就是逐个分析BIU的各个部件,看看他们分别负责什么,怎么工作。
指令缓存队列
首先把最简单的秒了。指令缓存队列的作用是储存BIU从总线接受到的指令内容,也就是即指令的操作码和操作数。(在学习这个的时候指令数据地址和指令数据内容总是让人挺头晕,我尽量把这个用词理清楚,这里是指令内容)这样CPU执行完一条指令就可以直接从缓存队列中去下一条指令并执行,不用一条指令执行完之后再去通知BIU去取下一条,那样的话中间会有很多CPU等待的时间,降低了CPU的利用效率。
总线控制电路(总线控制逻辑)
网上有的内容喜欢叫总线控制电路,有的叫总线控制逻辑,严格来说这两个词的含义并不一样,但是在这里姑且认为指的都是一个东西,用总线控制电路指代。总线控制电路的可以理解为BIU与外部总线沟通的桥梁,其核心功能可以概括为以下几点:
1.生成并驱动总线信号: 包括地址信号(用于定位存储器或I/O端口)、控制信号(如读/写信号、存储器/IO选择信号等),确保CPU发出的指令能够正确传达给外部设备。
2.控制数据传输: 管理CPU内部数据缓冲器,控制数据的流向(输入或输出),并协调数据传输的时序,保证数据在CPU和外部设备之间准确无误地传输。
3.响应EU请求: 接收来自EU(执行单元)的存储器或I/O访问请求,并执行相应的总线操作,完成数据或指令的读取和写入。
补充:总线控制电路和总线控制器
注意,这俩一字之差,指的可完全不是一个东西。总线控制电路在CPU内部,总线控制器在CPU外部。前者负责CPU内部与总线相关的操作,后者则是整个系统总线的管理和仲裁。
内部寄存器
CS,DS,SS,ES,IP,巴拉巴拉巴拉。如同侵蚀人心智的咒语,干的学生头晕眼花不知多云。这里的每个东西都是寄存器,有着自己的功能。
以一次实际指令执行流程为例
观各路课程,都是如数家珍一般把这几个寄存器一一展示出。但是我就是不习惯这种解释方式。于是我尝试以一次实际指令执行流程顺序为引导,引出各个步骤,大概这样会更易于接受。地址加法器也会在这里介绍,不过地址加法器不是寄存器。
首先,我们假设指令和数据已经存到了内存中,只需要从内存取出来就可以美美执行了。
背景知识:
8086地址总线20位: 寻址能力为1MB(2^20字节)。
8086数据总线16位: 一次可以传输16位数据。
段寄存器16位: 用于提供段基址。
偏移地址16位: 用于提供段内偏移量。
物理地址20位: 实际访问内存的地址,由段基址和偏移地址合成。
以上大概是基础知识?不过考虑到学这门课的同学大概率是电气自动化的没学过计算机组成原理,那就多BB一点。如果还没学到,课本后面章节应该有。
主要理解基址到物理地址的转换过程是:段基址 << 4(二进制状态下右移四位) + 偏移地址。
8086的内部寄存器是16位的,这意味着它们最多能表示2^16 = 64KB的地址范围。然而,8086的地址总线是20位的,可以访问2^20 = 1MB的内存空间。这就产生了一个矛盾:16位的寄存器无法直接表示20位的地址。简单来说就是你的门牌号板板只写的下三个数字(如702),那就不能直接找到十楼(比如门牌号为1001)
为了解决这个矛盾,8086采用了分段存储管理机制。它将1MB的内存空间划分为若干个逻辑段,每个段的大小最大为64KB。每个段都有一个起始地址,称为段基址。
将段基址左移4位(即段基址 << 4),相当于将段基址乘以16(2^4)。由于段基址的最后四位必须是0,左移4位相当于在段基址的末尾添加了四个0,使其扩展为20位。例如,如果段基址是1000H,左移4位后就变成了10000H。这个10000H就是该段在1MB内存空间中的实际起始地址。
将左移后的段基址(20位)与偏移地址(16位)相加,就得到了最终的20位物理地址。这个物理地址可以直接送到地址总线上,用于访问内存中的特定单元。
现在有了大概的背景知识,来一次实际的指令执行吧。(别看晕了!我们现在仅限于BIU讨论)
1.指令读取
(1)CS(Code Segment,代码段寄存器)
指令写在地址为20位的内存里面,BIU现在要把这个指令拿出来。由背景知识可知,我们需要段基址和偏移地址组成物理地址。那肯定需要两个寄存器才能完成任务。首先对于指令的段基址(或者叫基地址),我们找一个寄存器叫他CS(Code Segment,代码段寄存器)。指令是用代码写的,指令的段基址寄存器叫代码段寄存器太合适了。他来放基地址。假设CS的值为 1000H。
这里问题又来了,这CS里面的值哪来的?咋一开始就有啊?姑且看作是系统启动过程中直接初始化的,或者操作系统给他的。在我们目前的讨论范围内,就当他是一开始就有的。
(2)IP(Instruction Pointer Register,指令指针寄存器)
有了CS寄存器,我们就有了代码段的基地址。现在需要一个偏移量来确定段内具体的指令地址。这个偏移量存储在**IP(Instruction Pointer Register,指令指针寄存器) ** 中。BIU使用 地址加法器将 (CS << 4) 与IP相加,得到20位的物理地址,然后通过总线从内存中读取指令。假设IP的初始值为 2000H。
指令指针寄存器是什么?在计算机组成原理中,指令在内存中通常是顺序存放的,这是最基本的存放方式。例如,如果一条指令从地址0000开始,那么下一条指令通常就紧接着存放在0001、0002等后续地址。在这种情况下,只需要将IP的值递增,就可以指向下一条指令的地址。
之所以叫他 “指令指针”(Instruction Pointer),是因为它就像一个指针一样,指向内存中下一条即将被执行的指令其所在代码段内的偏移地址。在计算机组成原理中,这个东西叫程序计数器(PC),PC是一个更通用的概念,而IP是8086架构中PC的具体实现。
然而,实际情况要复杂得多:指令在内存中通常是顺序存放的,但由于指令长度可变以及存在控制转移指令,IP的更新并不总是简单的加1。BIU在执行完一条指令后,会根据指令的长度更新IP,或者根据控制转移指令的目标地址更新IP,以指向下一条需要执行的指令。
(3)地址加法器
BIU中的地址加法器将 CS << 4(左移4位,相当于乘以16)与IP相加,得到物理地址:10000H + 2000H = 12000H。由地址加法器来进行这个形成物理地址的操作。
(4)总线控制逻辑与指令队列
然后, BIU的总线控制逻辑控制地址加法器输出的物理地址 12000H 到地址总线上。同时,发出读总线周期信号。
BIU从内存地址 12000H 处读取指令 ADD AX, [1000H] 的机器码(假设为3字节),并将其存入指令队列中。
2.指令译码和执行
这一步主要是EU从指令缓存队列中读取指令,然后进行译码。EU会后面再细说。简而言之,EU解码指令“MOV AX, [1000H]”,并确定需要从内存中获取数据。
3.访问内存获得数据
除了指令之外,数据也存储在内存中。想要读取数据,我们需要数据的物理地址。
(1)DS(Data Segment, 数据段寄存器)
保存数据段的基地址。假设DS = 3000H。
(2)有效地址 (Effective Address, EA)
按照刚才的规律,现在应该提出一个存储数据偏移地址的寄存器。然而经过我的一顿搜索,惊讶得出:BIU中没有。 那我们发出疑问:为什么数据段的偏移地址不用BIU中寄存器提供?由什么提供呢?
首先,我们来对比一下指令和数据在读取过程中的差异。前面也说过,指令在内存中通常是紧挨着分布的,所以使用IP寄存器进行读取会很方便,每次把数值加一下就可以对应下一指令了。但是! 数据也是这样在内存中排布的吗?那可不一定。数据在存储和访问过程中,多样性和灵活性远高于指令。
数据类型多样: 程序需要处理不同类型的数据,例如字节、字(16位)、双字(32位)等。这些数据在内存中占据的空间大小不同,访问方式也可能不同。
数据结构复杂: 程序需要使用各种数据结构,例如数组、结构体、记录等。这些数据结构在内存中的组织方式各不相同,需要不同的寻址方式来访问其中的元素。
因此,数据段的偏移地址之所以不由BIU中的寄存器直接提供,而是由EU计算(来自EU中的寄存器)或在指令中直接指定,叫做有效地址。
4.剩下的SS和ES
一个最最简单的流程到这里就写完了,也已经掌握了BIU中大多数部分的作用,现在还剩下 SS(堆栈段)寄存器 和 ES(附加段)寄存器。现在一一道来。
(1) SS(Stack Segment,堆栈段)寄存器
我们早在数据结构中学过栈结构,在做C语言实验的时候大概率也报过所谓”栈溢出“的bug。在我们的内存中,就有一个栈。 它在内存中同样由一个基地址和一个偏移地址来确定具体的位置。
SS寄存器用于存储堆栈段的段基址,也就是栈在内存中的起始位置。就像CS存储代码的起始位置,DS存储数据的起始位置一样,SS存储的是栈的起始位置。
SS通常与SP(Stack Pointer,堆栈指针寄存器,这个是EU里面的)配合使用。SP寄存器存储的是栈顶在堆栈段内的偏移地址,也可以理解为栈顶指针。访问堆栈中的数据时,物理地址的计算方式与CS和DS类似:物理地址 = (SS << 4) + SP
(2) ES(Extra Segment,附加段)寄存器
ES寄存器是一个“额外的”数据段寄存器。它的主要用途是辅助进行字符串操作。为什么字符串就有这个特权让CPU为他专门设计一个寄存器?简而言之,是为了提高字符串处理的效率。字符串操作通常涉及大量的数据移动和比较,例如复制字符串、比较字符串、搜索子串等。这些操作需要频繁地访问内存。
假设没有ES,只有DS用于数据段的寻址。那么,在进行字符串操作时,如果源字符串和目标字符串位于不同的数据段,就需要频繁地修改DS寄存器的值。每次修改DS寄存器都需要执行额外的指令,这会大大降低字符串操作的效率。
例如,要将一个字符串从地址DS:1000H复制到地址DS:2000H,如果两个地址位于不同的段,就需要:
- 读取源字符串的一个字节。
- 修改DS寄存器的值为目标字符串所在的段基址。
- 将该字节写入目标地址。
- 再次修改DS寄存器的值为源字符串所在的段基址。
- 重复以上步骤,直到复制完整个字符串。
可以看到,在这种情况下,每复制一个字节都需要两次修改DS寄存器的操作,效率非常低下。而如果使用ES寄存器作为目标字符串的段基址,可以将源字符串和目标字符串分别放在不同的段中,而无需频繁地修改段寄存器。
所以,ES的存在并非是“必须”的,如果没有ES,字符串操作也能完成,只是效率会大大降低,我们CTRL C + CTRL V的编程之路也将徒增困难。
至此,BIU中的所有模块就解释完毕。加下来就是EU的模块的介绍,让我们来迎接更多更刺激的寄存器吧。
EU中的各个部分

再来一张图,免得还要再翻上去。
EU(执行单元) 是8086 CPU的核心部分,负责指令的执行,包括算术逻辑运算、数据移动等。EU与BIU(总线接口单元)紧密配合,完成指令的取指、译码和执行。
通用寄存器组
和BIU相比,EU中的通用寄存器组的名词有点过于多了,所以把名称和解释先列一边,然后再以一个简单流程来梳理。8086 CPU的EU中有8个16位通用寄存器,分别是AX、BX、CX、DX、SI、DI、BP和SP。这些寄存器可以分为数据寄存器和指针/变址寄存器两大类。
(1)AX(Accumulator,累加器)
AX是累加器,主要用于存储操作数和运算结果。例如在指令ADD AX, [偏移地址] 中,AX寄存器用于存储运算结果。BIU从内存中读取数据后,EU将数据与AX中的值相加,结果存储回AX。
(2)BX(Base Register,基址寄存器)
BX是基址寄存器,常用于存储内存操作的基址。它在内存寻址中非常有用,特别是在处理数组和数据结构时,可以通过BX寄存器快速定位数据。这就引出一个问题:基址是什么。这个基址和前面的段基址仅有一字之差。但是却是完全俩东西。物理地址是:段基址<<4+偏移地址。而这个基址,却是在数据操作中的偏移地址的一部分。
以一个指令为例:MOV AX, [BX + SI + 10](想知道SI就往下翻翻)。右边的框框内是偏移地址,左边AX是目标寄存器。这条指令的内容是用于将内存中的数据加载到AX寄存器中。既然框框里是偏移地址,那么段基址哪去了?
答案是:段基址是由BIU中的段寄存器提供的。对于指令MOV AX, [BX + SI + 10],段基址默认来自DS(数据段寄存器)。这么一来,前面BIU中缺乏的对应数据段基址的偏移地址寄存器,就在这里补上了(surprise)。
(3)CX(Counter Register,计数器寄存器)
CX是计数器寄存器,主要用于循环和字符串操作中的计数。它在循环指令和字符串操作指令中起到关键作用。可以当作我们喜闻乐见的 for (int i=0,i <=10,i++)里面的 i 变量来理解。
(4)DX(Data Register,数据寄存器)
DX是数据寄存器,通常与AX寄存器一起使用,用于存储较长的数据或作为扩展寄存器。它在乘除法运算和I/O操作中非常重要。DX寄存器的设计使得CPU能够处理更长的数据(如32位数据),特别是在乘除法运算中,DX与AX配合使用,可以存储高16位的结果。在I/O端口地址操作中, DX专门用于存储I/O端口地址。
(5)SI(Source Index Register,源变址寄存器)
刚才在MOV AX, [BX + SI + 10] 看到了SI,现在就来解释SI。可以看到不同于指令读取时简单的CS<<4+IP,这里的偏移地址由BX(基址)+SI(源变址寄存器)+10(立即数) 组成。SI广泛用于循环、字符串操作和数据结构遍历中。前面我们已经知道BX是什么了,那既然已经有了BX偏移地址,还要SI和立即数作甚?我们以一个实际例子来解释:
例子:访问数组元素
访问一个包含5个字的数组 my_array,并将其每个元素乘以2。
伪代码如下:
for i = 0 to 4
my_array[i] = my_array[i] * 2
end for
- 不使用SI(仅用BX)的情况
如果不使用变址寄存器(SI),我们只能通过手动修改BX的值来访问数组的不同元素。
;假设 my_array 的起始地址为 1000H
MOV AX, DS:[1000H] ; 读取 my_array[0]
ADD AX, AX ; AX = AX * 2
MOV DS:[1000H], AX ; 写回 my_array[0]
MOV AX, DS:[1002H] ; 读取 my_array[1]
ADD AX, AX ; AX = AX * 2
MOV DS:[1002H], AX ; 写回 my_array[1]
MOV AX, DS:[1004H] ; 读取 my_array[2]
ADD AX, AX ; AX = AX * 2
MOV DS:[1004H], AX ; 写回 my_array[2]
MOV AX, DS:[1006H] ; 读取 my_array[3]
ADD AX, AX ; AX = AX * 2
MOV DS:[1006H], AX ; 写回 my_array[3]
MOV AX, DS:[1008H] ; 读取 my_array[4]
ADD AX, AX ; AX = AX * 2
MOV DS:[1008H], AX ; 写回 my_array[4]
可以看出,每次访问数组元素时,都需要手动修改偏移量的值,每次修改偏移量的值都需要额外的指令,增加了指令的数量和执行时间。
- 使用SI的情况
如果使用变址寄存器(SI),我们可以通过SI来动态调整偏移量,从而更高效地访问数组元素。例如:
; 假设 my_array 的起始地址为 1000H
MOV SI, 0000H ; 初始化 SI 为 0 (数组索引)
MOV CX, 5 ; 设置循环计数器 CX 为 5 (数组长度)
loop_start:
MOV AX, DS:[1000H + SI] ; 读取 my_array[SI]
ADD AX, AX ; AX = AX * 2
MOV DS:[1000H + SI], AX ; 写回 my_array[SI]
ADD SI, 2 ; SI += 2 (指向下一个字)
LOOP loop_start ; CX--,如果 CX != 0,则跳转到 loop_start
一眼高下立判。
(6) DI(Destination Index Register,目的变址寄存器)
目的变址寄存器通常用于字符串操作和内存访问中作为目标地址的偏移量。DI寄存器与SI寄存器类似,也是一个不一定要有,但是有了很有用的寄存器。但它的主要作用是作为目标地址的变址寄存器,而SI通常作为源地址的变址寄存器。以字符串复制的过程为例。
例子:字符串复制
假设我们需要将一段字符串从内存中的一个位置复制到另一个位置。源字符串的起始地址为1000h,目标字符串的起始地址为2000h,字符串长度为10个字节。
- 不使用DI的情况
如果不使用DI,我们需要手动管理目标地址的偏移量。
–设置源字符串的起始地址为1000h,目标字符串的起始地址为2000h。
–使用一个通用寄存器(如BX)来存储目标地址的偏移量。
–从源地址1000h读取第一个字节,存储到目标地址2000h。
–手动修改目标地址的偏移量(如BX = BX + 1),指向下一个目标位置2001h。
–从源地址1001h读取第二个字节,存储到目标地址2001h。
–重复上述过程,直到复制完10个字节。 - 使用DI的情况
如果使用DI,我们可以通过DI来动态管理目标地址的偏移量。
–使用SI寄存器存储源地址的偏移量(初始为0)。
–使用DI寄存器存储目标地址的偏移量(初始为0)。
–从源地址1000h + SI读取第一个字节,存储到目标地址2000h + DI。
–自动增加SI和DI的值(如SI = SI + 1,DI = DI + 1),指向下一个源位置和目标位置。
–重复上述过程,直到复制完10个字节。
如此一来,DI通过动态管理目标地址的偏移量,简化代码、提高灵活性,并提升执行效率。
(7) SP(Stack Pointer Register,堆栈指针寄存器)
接下来还剩下两个寄存器,这两个寄存器主要用于和内存中的堆栈交互。
我们之前在BIU中有SS(Stack Segment,堆栈段)寄存器,这是我们第二次看到与栈有关的内容。SS 寄存器存储的是栈段的段地址。与 CS(代码段)、DS(数据段)、ES(附加段)类似,SS 决定了栈在内存中的起始位置。CPU 在访问栈中的任何数据时,会将 SS 的值乘以 16(左移 4 位),而SP(栈指针寄存器) 就存储栈顶相对于栈段起始地址的偏移量。SP 的值始终指向栈顶。SP就是栈顶的偏移地址。
(8) BP(Base Pointer Register,基指针寄存器)
那既然已经有了栈的基地址和偏移量,那按理说我们就不再需要什么别的东西了,但是这里还出现了一个BP(Base Pointer Register,基指针寄存器)。经过一系列搜索,大概得出BP的作用如下:
当一个函数被调用时,它的参数(实参)会被压入调用者的栈帧中。这意味着这些参数存储在栈上,并且它们在调用函数执行期间是可以通过栈指针(SP)访问的。然而,它们的位置相对于 SP 并不是固定的(因为随着压栈出栈,SP会不断变化)。只用SP来访问数据的话,就需要不断根据这个相对的变化调整,很麻烦且容易出错。
BP 的作用就是提供固定的基准点。
函数栈帧:函数栈帧是指在程序执行过程中,当前正在运行的函数所使用的栈空间。当一个函数被调用时,系统会在栈上为其分配一块空间,这块空间就是该函数的栈帧。可以理解为相当于是在栈里面给一个函数又开了一个独立的小房间。
BP 提供当前函数栈帧内部的基准点,方便访问参数和局部变量。相当于是在栈里面给一个函数又开了一个小房间,这个BP是小房间的基地址
其他部分
标志寄存器(Flags Register/FR)
标志寄存器(Flags Register/FR)也称为程序状态字寄存器(Program Status Word, PSW),是一个特殊的寄存器,用于存储 CPU 执行指令后产生的各种状态信息和控制信息。
ALU(Arithmetic Logic Unit,算术逻辑单元)
ALU(Arithmetic Logic Unit,算术逻辑单元)是8086中的一个核心组件,负责执行所有的算术运算(如加法、减法)和逻辑运算(如与、或、非).
ALU的主要功能包括:
- 算术运算:
加法(ADD)
减法(SUB)
乘法(MUL)
除法(DIV)
增量(INC)
减量(DEC) - 逻辑运算:
与(AND)
或(OR)
非(NOT)
异或(XOR)
移位(SHL, SHR) - 比较运算:
比较两个数的大小(CMP)。
同时,ALU不仅仅是一个计算的单元,他也有一系列寄存器来辅助计算:
-
输入寄存器(Input Registers):
ALU 需要从外部获取参与运算的操作数。这些操作数通常会先存储在 ALU 的输入寄存器中,然后再由 ALU 进行处理。 -
累加器(Accumulator):
累加器是一个非常重要的寄存器,用于存储运算的中间结果或最终结果。
在一些简单的 ALU 中,只有一个累加器,所有的运算结果都会存储在这个累加器中。 -
输出寄存器(Output Register):
ALU 将运算的最终结果存储在输出寄存器中,然后再将结果传递到 CPU 的其他部件(如通用寄存器或内存)。 -
状态寄存器(Flags Register):
虽然我们通常将标志寄存器(Flags Register)视为一个独立的寄存器,但从某种意义上来说,它也可以看作是 ALU 的一部分,因为它存储的是 ALU 运算后的状态信息。标志寄存器中的各个标志位(如 CF、ZF、SF、OF 等)都是由 ALU 在运算过程中设置的。
The end
至此,整个8086CPU的内部结构就算是梳理了一遍,梳理的过程中也是让我又被各路寄存器狠狠问候一番。下一篇大概会更新机器学习相关的。如果哪里写错了可以私信我,我研究研究改改。



2175

被折叠的 条评论
为什么被折叠?



