文章目录
4.1 指令格式
指令(机器指令)是指示计算机执行某种操作的命令,一台计算机的所有指令的集合构成该机的指令系统,也称指令集。
指令系统是计算机的主要属性,位于硬件和软件的交界面上。
4.1.1 指令的基本格式
一条指令就是机器语言的一个语句,是一组有意义的二进制代码
指令通常包括操作码字段和地址码字段
操作码指出指令应该执行什么性质的操作以及具有何种功能。
地址码给出被操作的指令或数据的地址。
指令的长度是指一条指令中二进制代码的位数。和机器字长没有固定的关系,包括单字长指令、半字长指令、双字长指令。
在指令系统中,如果所有指令的长度都相等,则称为定长指令字结构,否则称为变长指令字结构。由于主存一般按字节编址,指令字长多为字节的整数倍
根据指令中操作数地址码数目的不同,可以将指令分为以下格式:
1. 零地址指令
只有操作码OP,没有显式地址。这种指令有两种可能:
- 不需要操作数的指令。例如 空操作、停机、关中断
- 用在堆栈计算机中,操作数隐含的从栈中弹出、结果压入栈
2. 一地址指令
- 只有目的操作数的单操作数指令,按A 1 A_1A1地址读操作数,OP后结果存回原地址。即OP(A 1 A_1A1)->A 1 A_1A1
- 隐含约定目的地址的双操作数指令,在A 1 A_1A1读取源操作数,目的操作数可以隐含约定由其他地方提供,例如ACC(累加寄存器),运算结果也存入ACC中
3. 二地址指令
指令含义:( A 1 )OP(A 2->A 1
源操作数从A 2得到,目的操作数以及结果存入在A 1位置
4. 三地址指令
指令含义:( A 1 ) O P ( A 2 ) − > A 3 (A_1)OP(A_2)->A_3(A1)OP(A2)−>A3
若地址字段都是主存地址,则执行一条三地址指令需要访问存储器四次,包括一次取指令,两次取数据和一次存数据
5. 四地址指令
A 4为下一条即将执行的指令的地址
4.1.2 定长操作码指令格式
定长操作码指令在指令字的最高位部分分配若干位表示操作码,一般n位操作码字段的指令系统最多能表示2^n条指令。
4.1.3 扩展操作码指令格式
为了在指令字长有限的前提下保持比较丰富的指令种类,可采取可变长度指令。即全部指令的操作码字段的位数不固定,且分散地存放在指令字的不同位置上。
最常见的变长操作码方法是扩展操作码,操作码的长度随着地址码的减少而增加,不同地址数的指令可具有不同长度的操作码
扩展操作码指令格式:定长指令字结构+可变长操作码
常见题型
单个地址长度为n,上一层留出m种状态,则下一层可以扩展出m2n种状态
设计扩展操作码指令格式时,需要注意
- 不允许短码是长码的前缀,即短操作码不能和长操作码的前面部分的代码相同
- 各指令的操作码一定不能重复
通常情况下,对使用频率较高的指令分配较短的操作码;使用频率较低的指令分配较长的操作码。从而减少指令译码和分析的时间
- (22 408) 设计某指令系统时,假设采用16bit定长指令字格式,操作码使用扩展编码方式,地址码6bit,包含零地址、一地址和二地址三种格式的指令。若二地址指令12条,一地址指令254条,则零地址指令最多多少条?
零地址操作码16bit、一地址操作码16-6=10bit、二地址操作码16-6-6=4bit
因此二地址空余2 4 − 12 = 4 2^4-12=424−12=4条
一地址空余4 ∗ 2 10 − 4 = 256 − 254 = 2 4*2^{10-4}=256-254=24∗210−4=256−254=2条
那么零地址最多:2 ∗ 2 16 − 10 = 128 2*2^{16-10}=1282∗216−10=128条
4.1.4 指令的操作类型
设计指令系统必须考虑提供哪些操作类型
- 数据传送:包括寄存器之间的传送(MOV)、内存单元读取数据到CPU寄存器(LOAD)、CPU寄存器写数据到内存单元(STORE)等
- 算术和逻辑运算:主要有加(ADD)、减(SUB)、比较(CMP)、乘(MUL)、除(DIV)、加一(INC)、减一(DEC)、与(AND)、或(OR)、取反(NOT)、异或(XOR)
- 移位操作:算数移位、逻辑移位、循环移位
- 转移指令:无条件转移指令(JMP)、条件转移指令(BRANCH)、调用(CALL)、返回(RET)、陷阱(TRAP)等
- 输入输出操作:完成CPU与外部设备交换数据或传送控制命令及状态信息
4.2 指令的寻址方式
寻址方式是指寻找指令或者操作数有效地址的方式,即确定本条指令的数据地址及下条待执行指令的地址的方法。
寻址方式分为指令寻址和数据寻址两大类。
如果指令中的地址码字段不代表操作数的真实地址,则这种地址称为形式地址(A)。形式地址(A)结合寻址方式,可以计算出操作数在存储器中的真实地址,称为有效地址(EA)
- (16 408) 某计算机主存空间4GB,字长32bit,按字节编址,采用32位字长指令字格式。指令按字边界对齐存放,则PC和IR的位数至少?
PC存放下一条指令地址,4GB/32bit = 2^30,需要30位
IR存放指令,指令字长32位,所以需要32位
4.2.1 指令寻址和数据寻址
指令寻址:寻找下一条需要执行的指令
数据寻址:寻找本条指令的数据地址
1. 指令寻址
指令寻址有两种方式:顺序寻址、跳跃寻址
- 顺序寻址:通过程序计数器PC自动加1,自动形成下条指令的地址
- 跳跃寻址:通过转移类指令实现。跳跃的结果是当前指令修改PC值,下一条指令依然由PC值给出
2. 数据寻址
数据寻址的方式比较多,为了区别各种方式,通常在指令中设置一个字段表示寻址方式
4.2.2 常见的数据寻址方式
1. 隐含寻址
指令不明显地给出操作数的地址,而在指令中隐含操作数的地址。
例如单地址指令就不给出第二操作数的地址,而规定ACC作为第二操作数地址,指令显式指出第一操作数的地址。
隐含寻址优点:缩短指令字长
缺点:需要增加存储操作数或隐含地址的硬件
2. 立即(数)寻址
指令的地址字段为操作数本身,而不是操作数的地址
3. 直接寻址
指令字中的形式地址A是操作数的真实地址EA
优点:指令在执行阶段仅访问一次主存,不需要专门计算操作数的
缺点:A的位数决定了操作数的寻址范围,操作数的地址不易修改
4. 间接寻址
间接寻址是相对直接寻址而言的,指令的地址字段给出的形式地址不是操作数的真正地址,而是操作数有效地址所在的存储单元的地址,即EA=(A)
间接寻址可以是一次间接寻址,也可以是多次间接寻址
优点:可以扩大寻址范围(可以让EA的位数大于A的位数)
缺点:在指令执行阶段需要多次访存,降低了指令执行速度
间接寻址方式不常用,一般问到扩大寻址范围时,指的是寄存器间接寻址
5. 寄存器寻址
寄存器寻址是指在指令字中直接给出操作数所在的寄存器编号,即E A = R i EA=R_iEA=Ri,操作数在R i R_iRi所指的寄存器中
优点:指令执行速度快,无需访问主存;寄存器数量少,可以缩短指令字长
缺点:寄存器价格昂贵,计算机中寄存器个数有限
6. 寄存器间接寻址
寄存器R i R_iRi中给出的是操作数所在主存单元地址,即EA=(R i R_iRi)
寄存器间接寻址的特点是:比一般间接寻址速度快,但指令执行阶段需要访问主存
7. 相对寻址
相对寻址是把PC的内容加上指令格式中的形式地址A形成操作数的有效地址,即EA=(PC)+A
A是相对当前指令的偏移量,可正可负,补码表示
优点:操作数的地址不固定,随PC值的变化而变换,和指令地址之间总是相差一个固定值,便于程序浮动(程序内部)。相对寻址广泛应用于转移指令
8. 基址寻址
基址寻址是CPU中基址寄存器BR的内容加上形式地址A形成操作数的有效地址,即EA=(BR)+A
基址寄存器可用专用寄存器或通用寄存器
基址寄存器是面向操作系统的,内容由操作系统或管理程序确定,主要用于解决程序逻辑空间与存储器物理空间的无关性。基址寄存器内容不变,作为基地址;形式地址可变,作为偏移量
优点:扩大寻址范围(基址寄存器的位数大于A的位数);便于程序浮动,方便实现多道程序并发运行
- (14 408) 某计算机有16个通用寄存器,采用32位定长指令字,操作码8位(含寻址方式位),Store指令源操作数和目的操作数分别采用寄存器直接寻址和基址寻址方式。若基址寄存器可以使用任意通用寄存器,偏移量用补码表示,则Store指令中偏移量的取值范围?
寄存器16个,寄存器直接寻址占4位
基址寄存器占4位
剩余32-8-8=16位表示偏移量 补码范围:-32768~32767
9. 变址寻址
有效地址EA等于指令字中形式地址A和变址寄存器IX的内容之和,即EA=(IX)+A
变址寄存器是面向用户的,程序执行过程中,变址寄存器的内容可由用户改变,作为偏移量;形式地址不可变,作为基地址
优点:扩大寻址范围;适合编制循环程序
10. 堆栈寻址
堆栈是存储器中一块特定的后进先出(LIFO)原则管理的存储区;区域中读/写单元的地址用特定寄存器给出,称为堆栈指针寄存器SP
硬堆栈:硬件实现(寄存器
软堆栈:主存中划分出一段区域用来做堆栈
采用堆栈结构的计算机系统中,大部分指令表面上都表现为无操作数指令的形式,操作数地址隐含使用SP
- 指令系统中采用不同寻址方式的目的是:缩短指令字长,扩大寻址空间,提高编程灵活性
- 指令中地址码为A,变址寄存器为X。则变址间址寻址方式的操作数的有效地址EA=((X)+A)
- 相对寻址方式中,指令提供的相对地址实质上是:以下条指令在内存中首地址为基准的偏移量
- 多道程序设计中,最重要的寻址方式是:相对寻址
- (14真题)某计算机有16个通用寄存器,采用32位定长指令字,操作码字段8位,Store指令的源操作数和目的操作数分别用寄存器直接寻址和基址寻址方式,基址寻址方式可以用任意通用寄存器,偏移量用补码表示,则Store中偏移量的取值范围?
解:16个寄存器需要4位指出,那么偏移量有32-8-4-4=16位,补码表示,因此取值范围-216~216-1- 相对寻址的指令占3B,第一字节为操作码,第二三字节为相对位移量(补码表示),数据在存储器中采用以低字节为字地址的存放方式。若PC当前值为240(十进制),要求转移到290(十进制),则转移指令的第二三字节的机器码是:
解:取出当前指令后,PC的值应该是240+3=243,那么偏移量47=32+15,补码为 0010,1111
数据在存储器中采用低字节为字地址,那么是小端存储,因此第二三字节机器码为:F2H
4.3 程序的机器级代码表示
22年大纲新增
4.3.1 常用汇编指令介绍
- (21 408)
1. 相关寄存器
x86处理器中有8个32位通用寄存器,如图。
- 为了向后兼容,EAX、EBX、ECX和EDX的高两位 字节和低两位字节可以独立使用,E表示Extend
- EAX的低两位字节称为AX,AX的高低两位又称为AH和AL
- 除了EBP和ESP,其余几个寄存器的用途比较随意
2. 汇编指令格式
一般有两种不同的汇编格式:AT&T和Interl格式,其区别主要如下:
AT&T格式 | Intel格式 | |
---|---|---|
大小写问题 | 只允许用小写字母 | 大小写不敏感 |
操作数位置 | 第一个为源操作数,第二个为目的操作数 | 第一个为目的操作数,第二个为源操作数 |
前缀 | 寄存器前需要加前缀%,立即数需要加前缀$ | 寄存器和立即数都不需要加前缀 |
内存寻址 | 使用"(“和”)" | 使用"[“和”]" |
处理复杂寻址方式 | disp(%edx,%eax,2)分别表示偏移量,基址寄存器,变址寄存器和比例因子。表示M[R[edx]+R[eax]*2+8] | 对应的Intel格式是:[edx+eax*2+8] |
指定数据长度方面 | 指令操作码后紧跟一个字符,表示操作数大小,例如b表示字节,w表示字,l表示双字 | Intel语法类似,在操作码后面显式指出byte ptr / word ptr 或 dword ptr |
由于32位和64位体系结构都是16位扩展来的,因此用word(字)表示16位
3. 常用指令
以下是操作数的标记:
<reg>
:表示任意寄存器,若其后带有数字,则指定其位数,例如<reg32>
表示32位寄存器<mem>
:表示内存地址,例如[eax]
<con>
:表示8、16或32位常数,若其后带有数字,则指定其位数,例如<con8>
表示8位常数
x86中的指令,机器码长度为1B
mov
指令:将第二个操作数(寄存器的内容、内存中的内容或常数值)复制到第一个操作数(寄存器或内存)。但不能用于直接从内存复制到内存
对于Intel格式的指令,第二个操作数是源操作数,第一个操作数是目的操作数
mov <reg>, <reg> # 寄存器到寄存器
mov <reg>, <mem> # 内存到寄存器
mov <mem>, <reg> # 寄存器到内存
mov <reg>, <con> # 常数到寄存器
mov <mem>, <con> # 常数到内存
mov eax, ebx # 将ebx复制到eax
mov byte ptr [var], 5 # 将5保存到var指示的内存地址的一字节中
push
指令:将操作数压入内存的栈,常用于函数调用。ESP是栈顶,压栈前,先将ESP-4(32byte=4B)(栈增长方向和内存地址增长方向相反),然后将操作数压入ESP指示的地址
注意:栈中元素固定为32位!!!
push <reg32>
push <mem>
push<con32>
push eax # eax压栈
push [var] # var值指示的内存地址的4字节值压栈
pop
指令:与push
指令相反,执行出栈工作,先将ESP指示的地址的内容出栈,然后ESP+4
pop edi # 弹出栈顶元素送到寄存器edi中
pop [ebx] # 弹出栈顶元素,送到ebx指示的内存地址的4字节中
add/sub
指令:add
指令将两个操作数相加,结果保存到目的操作数中;sub
指令将两个操作数相减,结果保存到目的操作数中。
对于Intel格式的指令,目的操作数是第一个操作数
add/sub <reg>, <reg> # 寄存器到寄存器
add/sub <reg>, <mem> # 内存到寄存器
add/sub <mem>, <reg> # 寄存器到内存
add/sub <reg>, <con> # 常数,寄存器
add/sub <mem>, <con> # 常数,结果保存在内存中
sub eax, 10 # eax = eax-10
add byte ptr [var], 10 # var指向内存地址中的一字节加上10,存入var指示的内存地址的字节中
inc/dec
指令:分别表示将操作数自加1,自减1
inc/dec <reg> # 寄存器
inc/dec <mem> # 内存
dec eax # eax = eax-1
inc dword ptr [var] # var指示的内存地址的4字节值自加1
imul
指令:带符号乘法,有两种格式:
- 两个操作数相乘,结果保存在源操作数中(Intel格式的第一个操作数)
- 三个操作数,将第二第三操作数相乘,结果保存在第一操作数中,第一个操作数必须是寄存器
imul <reg32>, <reg32>
imul <reg32>, <mem>
imul <reg32>, <reg32>, <con>
imul <reg32>, <mem>, <con>
imul eax. [var] # eax = eax*[var]
imul esi, edi, 25 # esi = edi*25
乘法操作结果可能溢出,此时编译器置溢出标志OF=1,使CPU调用溢出异常处理程序
idiv
指令:带符号整数除法,只有一个操作数,作为除数,被除数为 edx:eax的内容(64bit整数),操作结果有两部分,商送到eax,余数送到edx(结合除法运算)
idiv <reg32>
idiv <mem>
idiv ebx
idiv dword ptr [var]
and/or/xor
指令:分别是逻辑与、或、异或,用于操作数的位运算,结果保存在目的操作数中
and/or/nor <reg>, <reg>
and/or/nor <reg>, <mem>
and/or/nor <mem>, <reg>
and/or/nor <reg>, <con>
and/or/nor <mem>, <con>
and exa, 0fH # eax = eax && 0fH
xor edx, edx # edx = edx ^ edx = 0
not
指令:位翻转指令,即操作数的每一位都翻转,0变1,1变0
not <reg>, <mem>
not <mem>, <reg>
not byte ptr [var] # var指示的内存地址的一字节所有位翻转
neg
指令:取负指令
neg <reg>
neg <mem>
neg eax # eax = -eax
shl/shr
指令:逻辑左移/右移指令,第一个操作数表示进行移位的数,第二个操作数表示移位的位数
shl/shr <reg>, <con8>
shl/shr <mem>, <con8>
shl/shr <reg>, <cl>
shl/shr <mem>, <cl>
shl eax, 1 # eax值左移一位,相当于*2
shr ebx, c1 # ebx右移n位,n为cl中的值,相当于除以2^n
x86处理器维持一个指示当前执行指令的指令指针IP,当一条指令执行后,该指针自动指向下一条指令。
IP寄存器不能直接操作,但可以用控制流指令更新。通常用标签指示程序中的指令地址。在x86汇编代码中,可以在任何指令前加上标签:
mov esi, [ebp+8]
begin: xor ecx, ecx
mov eax, [esi]
此时begin
指示了第二条指令
jmp
指令:控制IP转移到label
所指示的地址
jmp <label>
jmp begin # 转移到begin标记的指令执行
j+condition
指令:条件转移指令,根据CPU状态字中的一系列条件状态转移
je <label> # euqal 相等时转移
jne <label> # not equal
jz <label> # zero 等于0时转移
jg <label> # greater 大于时转移
jge <label> # greater or equal
jl <label> # less 小于时转移
jle <label> # less or equal
cmp eax, ebx
jle done # 如果eax <= ebx,则跳转到done,否则执行下一条指令
cmp/test
指令:cmp
用于比较两个操作数的值,test
指令用于对操作数进行逐位与运算。
这两类指令都不保存操作结果,而是根据操作结果设置CPU状态字中的条件码
cmp/test <reg>, <reg>
cmp/test <reg>, <mem>
cmp/test <mem>, <reg>
cmp/test <reg>, <con>
cmp
和test
经常和j+condition
指令搭配使用
cmp dword ptr [var], 10
jne loop # 相等则顺序执行,否则跳转到loop处
test eax, eax # 测试eax是否为0
jz xxxx # 为0则设置标志ZF。跳转到xxxx处执行
call/ret
指令:分别用于实现子程序的调用及返回
call <label>
ret
call
首先将当前执行指令地址入栈,然后无条件转移到label
指示的指令。ret
指令实现子程序的返回,弹出`栈保存的指令地址,然后无条件地转移到指令地址处执行
4.3.2 过程调用的机器级表示 TODO
4.3.3 选择语句的机器级表示 TODO
4.3.4 循环语句的机器级表示 TODO
4.4 CISC和RISC的基本概念
指令系统朝着两个截然不同的方向发展
- 增强原有指令的功能,设置更加复杂的新指令,实现软件功能的硬化。这类机器称为复杂指令系统计算机(CISC,C-Complex),典型的是x86架构计算机
- 减少指令种类,简化功能,提高指令的执行速度。这类机器称为精简指令系统计算机(RISC),例如ARM架构
4.4.1 CISC
- 指令系统复杂庞大,指令数目多
- 指令长度不固定,格式多,寻址方式多
- 可以访存的指令不受限制
- 各种指令使用频度相差很大
- 各种指令执行时间相差很大
- 控制器大多数采用微程序控制,有些指令太复杂,无法使用硬连线控制
- 难以用优化编译生成高效的目标代码程序
4.4.2 RISC
- 选取使用频率最高的一些简单指令,复杂指令的功能由简单指令的组合实现
- 指令长度固定,格式少,寻址方式少
- 只有Load/Store(取数/存数)指令访存,其余指令的操作都在寄存器之间进行
- CPU中通用寄存器的数量相当多
- RISC一定采用指令流水技术,大部分指令在一个时钟周期内完成
- 以硬布线控制为主,少用微程序控制
- 重视编译优化工作,以减少程序执行时间
4.4.3 CISC和RISC对比
- RISC更能充分利用芯片面积。
- RISC运算速度更高
- RISC便于设计,可以降低成本,提高可靠性
- RISC有利于编译程序代码优化
- RISC的兼容性比CISC差
- 有利于实现指令流水线的是:指令格式规整且长度一致、指令和数据按边界对齐存放、只有Load和Store指令才能对操作数进行存储访问
- (09 408)RISC普遍采用硬布线控制器,大多数指令在一个时钟周期内完成,内部通用寄存器数量相对CISC多,指令数少