文章目录
- 前言
- 指令系统
- 指令系统概述
- 指令格式
- 扩展操作码
- 指令寻址方式
- 指令寻址
- 数据寻址
- 基本的寻址思路
- 偏移寻址
- 隐含寻址
- 程序的机器级代码表示
- 汇编基础
- AT&T格式和Intel格式
- C语言控制结构的汇编表示
- 函数调用
- 栈帧切换
- 栈帧构造
- CISC与RISC
- 中央处理器
- CPU的功能和基本结构
- 运算器的基本结构
- 控制器的基本结构
- CPU整体结构
- 指令执行过程
- 指令周期
- 指令周期的数据流
- 指令执行方案
- 数据通路的功能和基本结构
- 单总线结构
- 专用通路结构
- 控制器的功能和工作原理
- 异常和中断机制
- 指令流水线
- 多处理器的基本概念
- 总线
- 总线概述
- 总线和事务定时
- 输入/输出系统
- I/O系统基本概念
- I/O接口
- I/O方式
前言
本文是对王道计算机考研《计算机组成原理》课程的总结,主讲咸鱼学长讲的确实清晰。
王道考研《计算机组成原理》
由于我们学校已经开设过汇编和计算机体系结构,所以计组的笔记内容会比较精炼,高屋建瓴,不适合无基础人听。
如果有不理解的,可以回去看看我前面的CSAPP笔记和汇编语言笔记(不过我感觉还是没啥必要,我这篇文章更多的是总结性质,不适合入门学习)
CSAPP笔记:
第一卷:程序结构与执行——信息表示、指令、处理器、性能优化、储存层次
第二卷:在系统上运行程序——链接、异常控制流、虚拟内存
第三卷:程序间的交流与通信——系统级IO、网络编程、并发编程
汇编语言笔记:
汇编语言笔记——微机结构基础、汇编指令基础
汇编语言笔记——汇编程序开发、汇编大作业
汇编语言笔记——接口技术与编程
指令系统
第二章学了运算器,第三章学了储存器,第四章(指令系统)和第五章(CPU)就是要学控制器,后面67章学习IO。
指令系统概述
指令格式
- 地址码数量分类。需要注意,指令长度固定,所以地址越多,寻址能力越差,同时地址越多,其访存次数也会增加,学过流水线CPU的人应该会知道这会增加指令周期数量。比如三地址指令,取指1次,A1A2A3各一次,总共就是4次。
- 指令长度和操作码长度通常放在一起说
- 指令长度会影响取指时间,如下图。
- 操作码长度影响译码灵活度与电路复杂度。长度固定,则译码电路简单,n位对应
2
n
2^n
2n条指令就可以,速度快;不固定就比较灵活,但是译码电路也复杂。
- 操作类型如下。
扩展操作码
扩展原理:通过前缀来区分不同的长度。判定的时候就像是一颗树一样:先判断前4为是否为1111,如果是1111,则判断中间4位,如果还是1111,继续判断,直到确定前缀是1111 1111 1111,那么指令就是0地址类型的。
本质上就是一个哈夫曼树,前缀匹配,和计网的CIDR子网划分异曲同工。CIDR中,IP分为网络号+主机号,而在这里,操作码=去掉地址码以后剩下的部分=前缀+指令实际可变部分,就是下图中去掉红色X后左边的部分。
和计网简直一模一样,实际CPU在识别指令的时候,会进行前缀匹配,匹配1直到遇到第一个0为止,通过1的数量确定是0地址还是123地址指令。这个判断过程遵循最长前缀原则,会尽量匹配长的前缀。
TODO:理解的还不够透彻,朋友们看着图一乐就行,我还没有相通下面哪个公式咋来的?
指令寻址方式
指令寻址
PC在取出指令的一瞬间(应该在译码阶段),就会结合指令长度进行自增变化,所以其实PC永远指向下一条指令,这就是顺序寻址
我们通常会写成PC=PC+1,然而自增没有那么简单:
- 如果编址不同,那么1会乘以一个倍数。一条指令长为一个字,假设字编址是+1,那么字节编址就得+2
- 如果指令长度不同,那么就是+n,n=指令长度,译码阶段可以得到这个信息,通过流水线转发到PC指针模块
除此之外,还有跳跃寻址
。跳转指令的直接作用就是在执行阶段修改PC值,注意,此前PC已经自增一次了,所以修改PC值是基于自增结果的。
这就是偏移的本质,无论PC在哪,目标=PC+(目标-PC),这就一定能跳过去,所以在偏移跳转的情况下,JMP后面跟的值是这个(目标-PC)的差值。本质上和直接跳没啥区别,可能就是电路的区别。做题的时候,两种情况都可能给出。
数据寻址
结合上学期学的汇编来看,指令寻址的方式是特别多的,如何区分一个地址是什么寻址方式呢?那就让地址=寻址特征+形式地址。其中,寻址特征表明了你是用什么方式去解读紧跟的形式地址的。
基本的寻址思路
寄存器寻址其实就是减少一次访存,变成访问一次寄存器,速度大大提升,所以寄存器寻址是可以与地址的寻址对标的,逻辑上要复杂一层,所以更灵活,寻址范围更大,但是速度上差不多:
寄存器直接寻址
=立即寻址
(补码)寄存器间接寻址
=直接寻址
,都是去内存中找一次间接寻址
最灵活,最复杂,形式多变,也最慢。要先去内存中找一次,然后再找一次。甚至会找多次(如果从内存中取出的地址值开头=0)
下图给的访存次数忽略了取指的那一次访存。
偏移寻址
总结:
-
基址寻址。
- EA=(BR)+A,BR为基,A为偏移
- 其意义在于,可以让程序在内存中整体浮动而不影响指令地址码的解释。
- 不论是BR还是通用寄存器,都是由操作系统管理的,所以这个寻址是计算机系统内部默认的寻址方式,会自动实现,不需要去写汇编指令
- 附带的作用是可以扩大寻址范围,因为寄存器的位数更大
-
变址寻址。
- EA=(XI)+A,以A为基地址,XI为偏移,不断变化
- 其意义在于,可以让用户可以便捷的编写循环指令
- 实际中,因为基址寻址是OS默认的,所以实际上都是基址+变址复合的。
-
相对寻址。
- EA=(PC)+A,以PC为基,加上偏移(补码)
- 其意义在于让代码段在可以内部浮动而不影响指令地址码的解释。
- 注意,相对寻址不能保证数据的位置不变,为此需要将指令分段,分代码段,数据段。这样,代码段动就无所谓,数据段不可以动。
隐含寻址
指令中可以隐含操作数,不一定要用地址码指出。有的在寄存器(比如下面的ACC),有的在堆栈(比如push和pop指令)
堆栈的方向,一般是栈顶朝着0地址走,也就是说扩容是SP指针减小。
平时说的堆栈都是软堆栈,在我印象里,汇编语言中的浮点指令,有一些就使用的硬堆栈,是一组浮点寄存器。
程序的机器级代码表示
汇编基础
这一块因为我学过汇编,所以就过得很快。
ESI中,S=Source,I=Index。EDI中,D=Destination
需要注意的是,我似乎没有发现间接寻址的指令,实际上就是如此,因为间接寻址可以用寄存器间接寻址实现,有寄存器你不用,非要用间接寻址那不是傻吗。
AT&T格式和Intel格式
需要注意一下偏移量的指令。我们前面学过基址寻址之类的,我们结合这片知识来理解一下汇编中的偏移量指令。
以mov eax,[ebx+ecx*32+4]
举例:
- 首先一个方框告诉我们,这是间接寻址,要去内存找数据
- 基址寻址默认就有,不需要显式写出来
- ebx是寄存器间接寻址的基本元素
- ecx其实应该用esi,这个是变址寻址,搭配一个放大因子,实际是比例变址
- 4的作用是进行元素内部的偏移
C语言控制结构的汇编表示
控制结构依赖于跳转指令,跳转指令和算术指令搭配使用。
算术指令,包括cmp之类的,会修改标志位(PSW,或者FLAG寄存器)。跳转指令读取标志位,进而决定是否跳转。
选择语句
不需要你去写,你能看懂了这是个选择结构,并且知道哪一部分是IF,哪一部分是else就行了。if和else不一定哪个在前哪个在后,得仔细看看条件。
循环语句
,for循环如下。while其实也类似,都可以写出来,会看就行。
实际应用中,loop语句更加简洁,可以直接实现for循环,loopz可以实现while循环。
函数调用
栈帧切换
我们且不论参数传递,局部变量,寄存器保护等栈帧具体操作,我们只关注一下调用和返回过程中,栈帧整体是怎么切换的。
首先要知道三个点:
- ebp。指向栈帧底部,这个位置内储存着上一个栈帧的esp旧值
- esp。指向栈帧顶部
- IP旧值。在上一个栈帧的顶部。
切换过程如下:
- call的时候,上一个栈帧会提前做好足够的准备,然后push一个IP后进行跳转。
- 跳转后的第一件事情就是执行enter,把ebp旧值保存(push ebp),切换新值(mov ebp,esp),此后就是新栈帧的具体操作了。
- ret之前,需要执行leave指令。在此之前,要先回退esp(mov exp,ebp),然后恢复ebp。说来神奇,就是新栈帧的ebp里面保存着旧栈帧的地址,所以回退毫无压力。
- 最后ret,恢复IP值
栈帧构造
- 上一层栈帧基址(ebp旧值)
- 寄存器保护。这个就暂且不论
- 定义局部变量。倒着来,靠后定义的,先压栈。
- 对齐。因为栈帧是16B的倍数,所以中间要填充一点
- 栈内传参。倒着来,参数列表中靠后的,先压栈。
- 你会发现,这两个东西都是倒着压栈的,这是因为栈是倒着的,所以负负得正。结果就是,程序逻辑中,靠前的东西,其地址也就更小,很顺,便于我们小端法机器的数据读取。
- 传参还可以通过寄存器
- IP旧值
下图为调用的具体全过程
CISC与RISC
- RISC的运算都是用寄存器,访存需要单独的指令加载到寄存器。
- 如果一个运算指令里面有访存操作,那么一定是CISC。
- 正因此,RISC寄存器特别多
- CISC就好比是封装好的类库,RISC就是原始的C语言。CISC开发效率高,但是不易优化,RISC更快,容易优化
- RISC的指令时间差不多,所以是一定会用流水线的。
中央处理器
CPU的功能和基本结构
运算器的基本结构
运算器内部,以ALU为核心,辅助有寄存器组与控制器件,他们需要和ALU连接,而最直接的思路是使用专用数据通路
。
为了不出现冲突,需要搭配多路选择器或者三态门实现同一时间只有一个寄存器的输入输出。三态门可以通过信号控制数据流向,以及是否可以流通,是很常用的控制器件。
这种方式缺点是成本大,而且线太多,布线很难。
解决的办法是使用CPU内部的单总线
。
给寄存器输入一个out信号,那么就可以把数据送到总线上,输入in信号,就可以把数据放到寄存器里。虽然一根总线可能会导致数据冲突,但是只要搭配暂存寄存器以及控制信号,就不会有问题。
重点理解一下暂存寄存器
的逻辑。为什么要暂存?本质就是,ALU是一个组合逻辑器件,是实时计算的,必须保证两端同时有稳定的输入,才能给出稳定的结果。但是我们的数据总线同时只能有一个数据,所以另一个稳定数据得寄存器给出。
从下图可以看出,如果让ALU的输入和总线直接相联,那么在总线输入变化的一瞬间,ALU的输入也会跟着同步变化。我们把一个数据送到总线上后,要先把这个数据存起来,才能把另一个数据送到总线上,不然原来的数据会被破坏。同理,输出的数据,在放到总线前,肯定也要先暂存,等总线上的数据无效了,我们再把这个数据放上去。
具体到运算器,输入端的暂存寄存器
,用于暂存来自总线的一个数据。输出端的暂存寄存器,用于暂存计算的最终结果,增加了移位功能后,变成了移位器
。举个例子ADD R0,dword ptr[100h]
。这个例子需要三步:
- CPU给访存信号,总线数据=来自主存的数据,使用时钟触发暂存寄存器,将数据暂存
- CPU给R0一个out信号,总线数据=寄存器R0,此时ALU的输入准备就绪
- ALU计算结果。此时如果没有移位寄存器的隔离,其不稳定的输出会干扰总线上的R0数据。
- 时钟触发移位器,暂存结果,总线数据=结果
- CPU给R0一个in信号,寄存器R0=总线数据
可以看到,总线上同时只允许一个数据存在,所以需要严格使用寄存器暂存,配合时钟来实现有条不紊地计算。
再说其他的寄存器:
- ACC(累加寄存器),用于存放运算的中间结果。注意,ACC不是暂存寄存器,因为其和ALU不直接相联,中间有三态门进行控制,和其他的寄存器组是平级的。
- PSW(程序状态寄存器),又叫flag寄存器,作为指令执行的决策依据
- 计数器。常常和移位器搭配,实现乘除运算,控制乘除运算中的计算次数。
控制器的基本结构
整体连线如下图。
指令寄存器(IR)
储存指令。
译码器(ID)
负责翻译操作码,翻译结果送到微操作信号发生器
里执行,这是控制器的核心。
微操作,其实是若干组控制信号,分批次送出。CPU里的组合逻辑早就摆在那里了,你只需要控制哪些数据可以导通,就可以产生对应的效果,结合时钟,就可以分几步执行完一套微操作。微操作发生器需要很多辅助信息:
- 译码器翻译结果。这个信息告诉微操作发生器我要做什么事情,比如je指令,我要去跳转,修改PC。
- 标志位辅助微操作发生器决策,比如je指令,我是要不要跳呢?
- 时钟,控制执行的节拍,因为总线只有一根,微操作要分几步有条不紊地执行,这就需要时钟来控制对外信号的切换了。
微操作发生器,以一定的步骤向外发出指挥信号,外面的电路导通,执行,得到结果。比如我们要访存,那就让AdIRout=1,MARin=1,此时就是把我们的地址码送到了MAR里,来一个时钟就可以进行访存了。
CPU整体结构
CPU:
- 运算器
- ALU
- 辅助寄存器与辅助电路
- 控制器
- CU(译码器+微操作信号发生器)
- 辅助寄存器与辅助电路
- 内部总线与外部总线,中断系统
换一种表示方式如下图,CPU=ALU+CU+寄存器+中断系统。
寄存器中,上图橙色标出的是用户可见寄存器,凡是用户可以通过指令直接修改的,都算。比如cmp指令修改PSW,jmp修改pc,其他那几个更不用说,直接mov。
指令执行过程
指令周期
结合流水线的知识,我们知道CPU的执行其实是可以划分阶段的,理论上,每个阶段应该时间差不多,这样才不会出现浪费。
我们不论5级流水线,但从指令执行时间来看,其实取指是很费时间的,要访存,译码反而只是一个组合逻辑,所以取指+译码放一起。执行可能很复杂,所以单独成立一个阶段,至于附带的写回,时间也很快。
注意,这个阶段不是时钟周期,要区分三个概念:指令周期,CPU周期(机器周期),CPU时钟周期(时钟周期)
一般来说,一个指令周期内部,机器周期数量不等。一个机器周期内部,时钟周期数量不等。
- 空指令只占一个机器周期,通常时间周期数量也比较少,就是用来控制步调。
取指周期
+执行周期
。加法指令比较常规- 乘法指令执行时间更长,所以执行周期比较长
间址周期
。间接寻址因为多一次访存,所以给访存单独设置一个机器周期中断周期
。正常来说,都会留一个时间检查中断,除非CPU被OS关了中断。
把上面这几类指令结合起来,变成所有指令通用的流程图,如下:
使用4个触发器储存当前处于的状态,是one-hot编码,含义如下:
- FE:FEtch
- IND:INDerect
- EX:EXecute
- INT:INTerrupt
总的来说,只要分给一个机器周期,就都可能有访存周期,这个是操作中最费时间的。不同周期的访存目的不同,但是都有可能。
指令周期的数据流
取指周期:
- 先送地址(地址线)。这一步要一个周期,触发MAR,把MAR的值更新为PC的值
- 再读取。分一个来回,CU一个周期就可以完成
- 送控制信号(控制线)
- 回传数据(数据线)
- 接收+复制。这一步两个时钟周期
- 触发MDR寄存器保存总线上的值
- 触发IR寄存器,保存MDR传来的数据
- PC自增。CU一个周期
总的来说,要5时钟周期(我猜的)
间址周期:
- 更新MAR。一个时钟周期
- MDR可以提前传送到MAR中。
- 再读取。分一个来回,CU一个周期就可以完成
- 送控制信号(控制线)
- 回传数据(数据线)
- 接收。一个周期
- 触发MDR寄存器保存总线上的值
- 修改地址。一个周期,这个可选。
执行周期比较复杂,无统一数据流。
直接看中断周期,中断周期的执行逻辑和call指令一样:
- 确定写地址,送到MAR。看起来是两步,其实都可以有CU一步完成。
- 确定写内容,PC值送到MDR,MDR的数据上数据线
- CU发出写信号,信号上控制线,内存写入
- 调用中断处理程序,修改PC值,这个值从中断向量表里来。
指令执行方案
- 单指令周期比较简单,大家的周期数都一样,浪费就浪费了。
- 多指令周期电路复杂,但是效率高
- 流水线是现在的主流,我感觉其实也结合了多指令的思想。具体很复杂,后面会讲。
数据通路的功能和基本结构
数据流动方向整体有三类:
- 寄存器之间
- 寄存器和主存之间
- 寄存器和ALU之间
这些数据都要经过数据通路。有三种方案:
- 单总线。部件挂载到总线上
- 多总线
- 专用数据通路。两个部件之间直接相联。
单总线结构
区分一下:
- 内部总线。CPU内部的单总线
- 系统总线。链接计算机整体的总线,CPU,内存,通道,IO等各种部件都挂载上面,比如我们前面说的数据总线和控制总线。
下面给出三种数据流的执行情况。
写题
的时候,两个注意点:
- 主要写两个东西,左边一列是数据流,右边一列是控制信号和注解。
- (PC)指的是PC寄存器的内容,如果是要取内存中的东西,那就要((R0)) 这样去做了。当然这个不是特别重要,因为这里也不是按照指令写。可以粗暴的理解为,多加一层括号
逐一解释:
- 寄存器那个比较简单。
- 主存和CPU。在CU发出控制信号的时候,其实还缺一个MARoutE有效。在读入的时候,应该是MDRinE有效,因为是从系统总线读入的。
- 寄存器和ALU的算术加法。这个CU加命令,其实更像是触发移位器的时钟,因为ALU已经把结果计算出来了,我这个加命令,只是让移位器把ALU的结果储存起来罢了。
来个例子,MemR就是我们CU给出的读取信号。PC自增可以直接写到访存取指那个地方,因为只要访存了,原来的PC就没用了,同步自增就好。
专用通路结构
略,tm要考试了,先放过,看看后面的总线。