第二章:8086CPU的结构与功能
§ 2.1 微处理器的外部结构
微处理器的外部结构表现为数量有限的输入输出引脚,这些输入输出引脚构成了微处理器级总线,其输入输出引脚按功能分为数据引脚,控制引脚,地址引脚
8086/8088CPU采用双列直插式封装,一共有四十个引脚,8086内部外部均为16位结构,属于标准的16位微处理器,而8088的数据引脚只有八个,只能同时传输八位的数据,属于准16位微处理器
由于微处理器传输的信息很多,引脚有限,所以在研制的时候采用了部分引脚复用的设计,一个引脚可能会传输不同的信息,为了把这些数据分开来,就得设计时序,在不同的时间里传输不同的数据
引脚的功能: (1)与存储器之间交换信息(指令及数据)。 (2)与1/0设备之间交换值息 (3)能输入和输出必要的信号
8086一共有20个地址引脚,包括~,这20个地址引脚全部用来寻址存储器,由此可知,8086可管理的存储器大小为B=1MB 但是8086给IO提供地址的引脚只有~这十六根引脚,所以8086能寻址IO地址空间只有2^16^B=64KB
既然地址引脚既可以寻址io也可以寻址内存,那么怎么知道是寻址的io接口还是内存呢?是通过一个叫控制引脚,当这个引脚是低电平,说明正在用低十六根地址线寻址io接口,高电平说明正在寻址内存,还有两个引脚需要介绍,和,这两个引脚不能上面有横杠,说明是低电平有效,当有效的时候是写数据,当有效的时候是读数据,这两个引脚不能同时为低电平
总线是用于连接CPU与其他部件的一组连线。总线从功能上可分为三种; (1)数据总线(data bus)传送信息。 (2)地址总线(address bus) 传送地址码. (3)控制总线(control bus)传送控制信号

一个io端口就是用来传输一种信息的通道,一个io端口至少要占用一个io地址,这个地址叫做io端口地址,8086一共可以管理64k个io端口
某些微处理器采用统一的地址空间对存储器和I/O端口寻址,即存储器和I/O端口进行统一的地址编码,一个地址要么对应于存储单元,要么对应于端口寄存器,读写控制信号用来区分CPU是进行读/写操作。在这种方式下,对存储器和I/O端口的存取指令是一样的。 缺点:浪费了存储器地址空间 优点:CPU对存储器的操作同样适用于io操作,程序设计会更简单更灵活 如:MCS-51系列的cpu
但大多数微处理器则是采用两个独立的地址空间,即存储器地址空间和I/O地址空间,这时,某存储单元和I/O端口可能对应于相同的地址值。那么如何区分CPU是存取存储单元还是I/O端口?采用存储器读写信号和I/O读写信号来区分。在这种方式下,对存储器和对I/O)端口读写指令是不同的. 如:8086cpu 8086的低十六位地址线同时用来寻址io端口,采用区分开来 优点:节约存储器地址空间 缺点:对io接口需要独立的指令来操作,需要专门设计对io操作的指令,指令记忆复杂,对io寻址方式单一,io程序设计不灵活
例:
nasm
M0V AX,1000H
MOV [2000H],AX;[16位二进制数]表示地址,这里表示的是偏移地址,而非物理地址
MOV AX,1000H
MOV BX,2000H
MOV [BX],AX;也可以把偏移地址放在bx寄存器内,这种寻址方式叫间接寻址
NOP;对存储器的寻址方式有五种,用哪一种都可以,这里只列出了两种,后面都会学到,对于io而言,只能用in和out指令去输入输出§ 2.2 一般微处理器的内部结构
一般微处理器的内部结构如图:
- 需要运算,所以需要ALU算数逻辑运算单元
- cpu每次都去内存拿数据太慢了,需要在cpu里设计工作寄存器
- 数据寄存器是专门用来暂存数据的(如AX)
- 地址寄存器是专门用来暂存地址的(如BX),地址寄存器同时也可以用来暂存数据
- 控制器也就是中央指挥机关,控制各个部件的工作,控制器把指令取回来后,交给运算器执行,然后取下一条指令,上一条指令没有执行完就放到指令寄存器内排队,并且让指令译码器对取回来的指令去译码分析
- IO控制逻辑电路,是用来控制IO端口的电路
程序计数器指向一个单元地址,然后发出一个读信号读过来,如果是数据,就直接走其他内部总线读到寄存器,如果是指令就给指令寄存器,然后交给指令译码器译码,如果是控制指令,就要给控制逻辑部件进行操作,程序计数器自动跳到下一个地址,以此类推,但是这种结构是一个串形结构,运算速度很慢,那么8086中设计了指令预取队列来解决这个问题
下一个部分是堆栈指针,这里介绍一下堆栈 堆栈:堆栈由先进后出原则组织的一段存储区域
入栈对应的指令为PUSH,出栈对应的指令为PUSH,对8086CPU而言,堆栈必须按字(2字节)操作,堆栈很大的一个应用是用来备份存储器,当我们编程的时候感觉到寄存器不够用了,就可以把寄存器的数据压入堆栈,就把这个寄存器释放出来了,处理完了后再把数据pop拿出来就行了,因为堆栈必须按字操作,所以诸如PUSH AL这种对字节操作的堆栈指令都是错误的,请读者一定记住,8086的堆栈操作只能进行十六位操作
那么堆栈也是内存中的地址,我们要进行入栈或者出栈实际上也是在对内存的读写操作,那么问题就来了,我们入栈和出栈是在对内存中的哪个单元进行操作,栈顶地址由SS:SP提供,偏移地址是SP寄存器提供的,当程序初始化之后,SP指向的是栈底+1单元的地址
CPU又是按照字操作,所以CPU执行PUSH AX完成的功能:
- SUB SP,2
- MOV SS:[SP],AX;这个程序实际上是错误的,因为SP是无法用来做寻址寄存器的,只有BX BP SI DI这四个寄存器才可以用来寻址,这里主要是帮助读者理解
读者会看到SP每次都在-2一直在向上移,当sp<0的时候,明显就会溢出,8086没有对溢出操作良好的判读机制,基本上就死机了所以希望读者在定义堆栈区的时候要留出百分之三十的富裕量,以防堆栈溢出
当执行POP AX时与PUSH AX正好相反
- mov ax,ss:[sp]
- add sp,2
我们还可以看到,cpu内部还有一个IO控制逻辑的东西,这个很简单,就是用来操作IO端口的
§ 2.3 8088/8086内部结构
上文提到过,一般微处理器都是取一条指令执行指令然后再取下一个指令这样串行执行下去的,导致了CPU效率很低,Intel公司在研制8086/8088cpu的时候解决了这个问题 8086内部结构:
我们可以把8086内部结构分为两部分,右边为总线接口单元BIU,左边为执行单元EU,8086内部由这两部分组成 先看BIU最上面那一坨有五个东西,这五个东西都是寄存器,其中前四个为段寄存器,第五个IP为指令指针寄存器,这个IP的作用其实就是我们前面说过的程序计数器PC,用来指向下一条指令的首地址,我们把这四个段寄存器来说说,CS为代码段寄存器,ES为附加段寄存器,SS为堆栈段寄存器,DS为数据段寄存器.下面是地址产生与总线控制组成,地址产生器用来形成二十位物理地址(段基地址*16+偏移地址)
由于8086/8088是16位处理器,只能寻址个地址单元(64k),这个大小别说今天的CPU了,对于以前的CPU那也是非常小的,为了让CPU可以寻址个内存单元(1M),Intel公司在设计8086CPU的时候对内存进行了分段管理,引入了逻辑地址的概念,逻辑地址分为段地址和偏移地址,段寄存器提供段地址,在有用户给出一个偏移地址,将段寄存器左移四位(*16)加上偏移地址构成20位的物理地址,我们描述逻辑地址分别是段基地址:偏移地址,这样来描述,例如,下一条指令的物理地址是由cs和ip共同提供的,下一条指令的逻辑地址可以表示为cs:ip 注意:因为偏移地址只有16位,所以一个段最大只有64k
接下来是指令队列寄存器,也叫指令预取队列,在8086里有6个字节,在8088里有四个字节,指令队列可以暂存指令,前面说到,一般微处理器的运行过程是取指令,执行指令,但是执行指令有可能会很慢,这个时候就只有等着,导致效率低,有了这个指令队列后,如果指令没有执行完,BIU依旧可以把指令取过来放到指令队列里去,显然效率提高了
经过上面的学习,我相信读者已经对biu和eu的功能有了一个基本认识,这里总结一下biu和eu的功能
- BIU:主要负责从外部存储器中取指令,并将取回的指令放在指令队列中
- EU:主要负责从指令队列中获取指令,遵循先进先出原则,并对该指令进行译码执行
注:BIU取回去的不是整个指令,是取操作码放入指令队列,如果EU执行的时候发现需要操作数,BIU再通过数据总线把需要的数据取回来
读者可以看出,有了指令队列后,理想情况下BIU可以一直往指令队列中放指令,EU可以一直从指令队列中拿指令,这两个部件可以并行执行,这就是并行流水线工作模式,希望读者可以加以理解,也希望读者可以知道,这个工作模式可以大大提高总线的利用率,但并不能让流水线不断,当EU需要对存储器进行操作的时候,需要用到总线,此时BIU就不能去指令了,就要帮助EU完成指令
流水线工作模式图示:
在EU上面那一坨东西叫做通用寄存器,总共有8个,通用寄存器是用来暂存中间运算结果的,但他们在某些指令中也会专用,这些通用寄存器构成了汇编语言程序设计的编程模型
§ 2.4 8086CPU的寄存器组织
在8086内部一共有14个16位寄存器,我们把这14个寄存器分为三类 通用寄存器: AX BX CX DX SI DI SP BP,其中AX(累加器),BX(基址寄存器),CX(计数器),DX(数据寄存器)都是数据寄存器,这四个寄存器都可以分别分成两个8位寄存器§ 2.3中的图2.3已经有了,这里不赘述,我们一般用AX来进行运算,用BX来寻址,用CX来计数,DX用来保存数据,但是在IO操作时,DX充当了IO地址寄存器对IO间接寻址 地址指针寄存器和变址寄存器: SP叫堆栈指针寄存器,在堆栈操作的时候,用来指向栈顶的偏移地址,用SS:SP也就是栈顶的逻辑地址 BP叫地址指针寄存器这个寄存器和BX类似,但有区别,这里给个例子
nasm
MOV BX,0002H
MOV BP,0002H
MOV AL,34H
MOV [BX],AL;本操作将AL的内容写到了DS:BX单元
MOV [BP],AL;本操作将AL的内容写到了SS:BP单元
MOV AL,56H
MOV BP,0003H
MOV DS:[BP],AL;本操作就可以通过段重设的方式把AL写到DS:BP中去显然,BX和BP都可以提供16位的偏移地址,但BX提供的偏移地址默认是在DS段中,BP提供的偏移地址默认在SS段中,但我们还是可以加一个段前缀(段超越前缀)强制改变偏移地址,当然,加了一个段前缀后在汇编的时候机器码里肯定会多出一个段前缀字节
SI和DI都是16位的地址寄存器,SI:源变址寄存器,DI:目的变址寄存器,看到这个名字就知道,他们也是地址寄存器,所以,综上所述,我们对存储器进行寄存器间接寻址,我们只能用bx,bp,si,di四个寄存器,其中bp默认段在堆栈段,其余三个默认段都在数据段.SI和DI主要是用来对字符串进行操作,当我们要进行字符串传送的时候,当然也可以用数据传送指令,但为了简化,8086专门提供了字符串操作类指令,当我们要进字符串操作的时候,规定源串的首地址必须位DS:SI,目的串首地址必须是ES:DI 这里介绍一下字符串操作指令MOVSB和MOVSW,MOVSB是按字节传送,MOVSW是按字传送,当传送一个单元后,会让SI和DI自动变化,这就是变址这个名字的由来,至于应该是加还是减,就要看我们标志寄存器里的DF标志位了这个以后会说到
段寄存器: CS(代码段寄存器) 保存代码段的段基地址 SS(堆栈段寄存器) 保存堆栈段的段基地址 ES(附加数据段寄存器) 保存附加数据段的段基地址 DS(数据段寄存器) 保存数据段的段基地址 当我们要使用这些段的时候,我们要对这些段的段寄存器进行初始化,其中用户是不能修改CS寄存器的,CS初始化由操作系统完成,并且对段寄存器进行初始化的时候,必须要用通用寄存器给段寄存器赋值,诸如MOV DS,1000H这种都是错误的语法格式
控制寄存器(2个): IP(指令指针寄存器):这个寄存器已经说过了IP指向下一条指令的偏移地址,CS:IP功能构成了下一条指令的逻辑地址,相当于程序计数器PC,同样,用户无法修改CS,也无法直接修改IP,但可以通过转移指令来间接修改IP
PSW(状态字寄存器),这个前面也说过
这个寄存器虽然是16位的,但是有用的只有九位,这九位分成了两类,状态标志和控制标志
状态标志:OF SF ZF AF PF CF这六个在前面都说到过了,状态标志反映的是ALU运算后结果的状态,所以不参与运算的指令,一定不会影响状态,例如MOV这种传送指令,这六个标志位中,除了AF以外,其余五个分别都是分支程序设计中的条件之一
控制标志:DF IF TF 都是用来控制CPU的运行状态 DF叫做方向控制 如果DF为0,在执行字符串操作数的指令后,SI和DI会自动增加,DF为1就会自动减少 IF叫中断允许标志,所谓中断,就是打断,让cpu停止现在的工作去执行另一个程序,执行的这个程序叫做中断服务子程序,执行完中断服务子程序后,返回到刚才的地方继续执行,对于8086而言,外部中断是可以屏蔽掉的,但是内部中断是不能屏蔽的,中断请求可以从引脚进来,其中是不可屏蔽的中断请求,是可屏蔽的中断请求,怎么屏蔽?用IF控制,当IF为1,响应可屏蔽中断,当IF为0,不响应引脚提出的中断请求
TF叫陷阱(单步)标志位,一般情况下TF都是0,当TF为1时, CPU每执行一条指令后就中断,例如DEBUG的单步模式,并没有标志位操作指令让TF置1或者清零,但是可以通过栈操作+或运算来实现
nasm
PUSHF ;本指令可以把PSW压入栈
POP AX
OR AX,0100H
PUSH AX
POPF ;本指令可以把栈弹到PSW这些标志位可以用标志位操作指令置1或清零
§ 2.5 8086存储器和IO组织
在8086中,有20根地址线,全部用来寻址存储器,其中低16位同时用来寻址io端口,这里前面都说过,但是在IBM-PC机中,我们认为根本不需要这么多IO接口,所以就只用低10位来寻址IO,可寻址=1k个IO端口,对于这1k,还有规定前512个IO地址给了主板IO分配地址,后512个地址给插件板上的IO分配地址
数据存放格式:我们有字节型,字形,双字型数据,我们是不能直接向存储器写数据的,否则容易占用其他地方的内存,我们需要向计算机申请内存的使用权限来定义数据,如果我们需要字节类型的数据,我们使用DB伪指令,如果是字类型,就用DW,如果是双字,就用DD
字数据占用两个字节,一个地址又只能表示一个字节,那么字单元地址是多少呢?很简单,首地址,或者低地址单元的地址,如果一个字单元的地址为偶数,我们称其未对其(对准)的字单元,如果是奇数,我们称为未对齐(对准)的字单元,为什么要对这个进行区分呢?因为CPU对对准字单元的操作(一个总线周期)比未对准字单元的操作(两个总线周期)要快一倍,因为数据总线的低8位只能操作偶地址,高8位只能操作奇地址,如果是偶地址一次就能传过去,如果是奇地址会先从高八位传送奇地址的数据,再从低八位传送偶地址,就得两个总线周期,在实际程序设计中,我们尽可能把字单元定义在偶地址上
存储器的分段与物理地址的形成: 因为8086是16位处理器,无法管理20根地址线,所以要分段管理,一个逻辑段最大为=64k个字节,段地址由段寄存器提供,偏移地址由我们给出,要注意,每个逻辑段的起始地址必须可以被16整除,因为物理地址是段寄存器*16+偏移地址,这里读者必须要理解,当然,1M字节最多也只能分成64k个逻辑段,在实际程序设计中,我们用到的全部是逻辑地址,逻辑地址非常重要,是读者必须要理解的,读者可以把逻辑地址看成一个参照物加上一个相对于参照物的相对地址,段基地址就是参照物,偏移地址就是相对于参照物的位移量,例如我们要寻址00020H,我们可以表示为0001H:0010H,当然也不止这一种表示形式,一个物理地址可以对应多个逻辑地址,但请注意,一个逻辑地址只对应唯一一个物理地址,虽然理论上段与段之间可以重叠但实际上不会出现这种情况,编译器会帮助我们分开这些段
各类指令的地址信息: 

