0%

【知识总结】 第四章-指令系统

基本概念

  • 程序:由一系列有序的指令构成
  • 指令:指示计算机硬件完成指定的基本操作的命令
  • 指令系统
    • 又叫指令集,是一台计算机所有指令的集合
    • 位于软件硬件交界面上
    • 计算机的主要属性,指出计算机有哪些基本的硬件功能
  • 指令系统应具备的特征
    • 完备性:功能齐全
    • 高效性:编写的程序占据空间小,执行速度快
    • 规整性
      • 对称性:所有寄存器和存储单元可同等对待;所有指令可使用各种寻址方式
      • 匀齐性:可以支持各自数据类型
      • 一致性:指令格式和数据格式一致
    • 兼容性:系列机各种机型有相同的基本机构和共同的基本指令集

指令格式

基本格式

  • 操作码+地址码
  • 操作码指出操作的类型
  • 地址码给出被操作的信息的地址
  • 指令长度指的是一条指令的二进制代码长度
    • 取决于操作码长度、地址码长度、地址码个数
    • 可能大于、等于或小于机器字长,如双字长指令、字长指令、半字长指令
    • 指令系统的所有指令长度相等,称为定长指令字结构,执行快,结构简单
    • 指令系统的指令长度随指令而异,称为变长指令字结构,一般是字节的整数倍(考虑主存按字节编址)
  • 根据操作数地址码的个数,指令分为
    • 零地址指令
      • 不需要操作数的指令。如空指令、停机指令、关中断指令
      • 涉及堆栈的运算指令。
    • 一地址指令:
      • 单操作数指令。如自增、自减、求反、求补,形式为
      • 隐含的双操作数指令。一般另一个操作数由ACC(累加器)提供,运算结果也存到ACC中,形式为
    • 二地址指令:比如常用的逻辑运算,算术运算,形式为
    • 三地址指令:比如常用的逻辑运算,算术运算,形式为
    • 四地址指令:形式为是下一条指令的地址

定长操作码指令格式

  • 在指令高位分配固定长度的若干位表示操作码
  • 位操作码字段可以表示个指令
  • 定长操作码简化计算机硬件设计,提高指令译码识别速度,当计算机字节为32位和更长时,这是常规用法

拓展操作码指令格式

  • 当指令字长有限时,为了丰富指令种类,可以采用可变长度操作码
  • 拓展操作码是最常用的可变长操作码,操作码长度根据地址码的减少而增加
    • 一般全1留作拓展操作码使用
    • 比如0000 - 1110是4位操作码;11110000 - 11111110是8位操作码;111111110000 - 111111111110是12位操作码
  • 除了拓展操作码,还有其他的拓展方法,比如哈夫曼编码的思想,给高频指令短的操作码
  • 拓展编码必须是前缀码,即不存在短码是长码的前缀,且各指令操作码不重复

指令的操作类型

  • 数据传送
    • 寄存器之间的传送 MOV
    • 从内存读数据到CPU寄存器 LOAD
    • 从CPU寄存器写数据到内存 STORE
  • 算术和逻辑运算
    • 算术运算:ADD(加)、SUB(减)、CMP(比较)、MUL(乘)、DIV(除)、INC(自增1)、DEC(自减1)
    • 逻辑运算:AND(与)、OR(或)、NOT(非)、XOR(异或)
  • 移位操作
    • 算术移位
    • 逻辑移位
    • 循环移位
  • 转移操作
    • 无条件转移 JMP:任意条件都会转移
    • 条件转移 BRANCH:满足条件才会转移
    • 调用 CALL:调用指令需要保存下一条指令的地址,方便调用结束后返回。
    • 返回 RET:完成调用后根据之前保存的地址返回
    • 陷阱 TRAP
  • 输入输出操作:用于CPU和外部设备交换数据、传送控制命令和状态信息

寻址方式

有效地址的概念

  • 指令中的地址码字段并不代表操作数真实地址,而是形式地址
  • 形式地址需要结合寻址方式,算出操作数在存储器中的真实地址,即有效地址EA
    • 若考虑虚拟存储机制,有效地址本质上是段内偏移量,即线性地址等于段基地址加有效地址。
    • 本章内容除非特殊说明,默认先不考虑虚拟存储,即段基地址从0开始,且不采用分页机制,此时有效地址就是真实的物理地址

指令寻址和数据寻址

  • 指令寻址
    • 含义:寻找下一条需要执行的指令地址
    • 分为
      • 顺序寻址:程序计数器PC加1
      • 跳跃寻址:通过本条转移指令算出下一条指令地址,可能跳跃到绝对地址(根据标记符),也可能跳跃到相对地址(距当前的指令偏移量),算出后修改程序计数器PC
  • 数据寻址
    • 含义:根据指令中操作数的形式地址得到其有效地址
    • 数据寻址方式很多,通常在指令中设置一个字段(即寻址特征),用以指明寻址方式的类型
    • 指令的格式为:操作码、寻址特征、形式地址A

常见寻址方式

  • 隐含寻址
    • 不显式给出所有操作数地址,指令中隐含操作数的地址
    • 比如累加器ACC作为第二个操作数的地址,结果也存放到ACC中
    • 优点是缩短指令长度;缺点是需要增加存储隐含地址的硬件
    • 访存0次
  • 立即(数)寻址
    • 地址字段指出的是操作数本身,称为立即数
    • 数据用补码形式存放
    • 优点是不需要访问内存;缺点是立即数的位数收到指令长度限制
    • 访存0次
  • 直接寻址
    • 地址字段就是操作数的真实地址
    • 优点是寻址简单,只需要访问一次内存;缺点是寻址范围受指令位数限制,地址也不容易修改
    • 访存1次
  • 间接寻址
    • 指令中给出操作数真实地址所存放位置的真实地址
    • 间接寻址可以是一次,也可以是多次
      • 如果内存寻址得到的内容第一位是1,表示多次间接寻址,需要继续寻址
      • 如果内存寻址得到的内容第一位是0,表示得到的就是操作数的地址
    • 优点是扩大了寻址范围,EA位数大于A的位数;缺点是需要多次访存(至少2次)
    • 这种寻址方式不常用,通常使用寄存器间接寻址扩大寻址范围
    • 访存至少2次
  • 寄存器寻址
    • 指令中给出操作数所在的寄存器编号
    • 优点是不访问主存,且地址码很短(因为寄存器不多);缺点是寄存器价格贵,个数有限
    • 访存0次
  • 寄存器间接寻址
    • 指令中给出操作数真实地址所存放的寄存器编号
    • 特点是比间接寻址快,但仍需要访问主存
    • 访存1次
  • 相对寻址
    • 指令中给出的是偏移地址(可正可负),基地址在PC中
    • 操作数地址不是固定的,广泛应用于转移指令
    • 注意
      • 转移指令取出后,PC会立刻更新到下一行指令的位置,此后再计算相对偏移量。
      • 比如转移指令2个字节,转移指令地址为X,则执行完后,
    • 访存1次
  • 基址寻址
    • 指令给出偏移地址,基地址在基址寄存器BR中
    • 面向操作系统的寻址方式,基址寄存器由操作系统管理,用户程序运行时通常BR不可变
    • 该寻址方法扩大了寻址范围,用户不需要考虑编程的地址范围;缺点是偏移量的位数短
    • 访存1次
  • 变址寻址
    • 指令给出基地址,偏移地址在变址寄存器IX中
    • 面向用户的寻址方式,变址寄存器用户可以更改,指令中的A一般不变
    • 该寻址方法扩大了寻址范围,常用于数组(A为数组地址,IX存放元素偏移量)、循环;缺点是偏移量的位数短
    • 访存1次
  • 堆栈寻址
    • 堆栈是存储器(或专用寄存器组)中特定的按后进先出原则管理的存储区
    • 该存储的读写一般通过栈顶指针寄存器SP
    • 分为
      • 硬堆栈:寄存器堆栈,成本高,不适合大容量堆栈
      • 软堆栈:主存中划分一块区域作为堆栈,划算且常用
    • 本寻址方式一般指令中都无操作数,操作数隐含在堆栈中,在读写堆栈的单元前后会相应对SP内容进行增减

x86汇编指令入门

相关寄存器

  • 8个32位的通用寄存器,分别为
    • EAX 累加器(Accumulator)
    • EBX 基地址寄存器(Base Register)
    • ECX 计数寄存器(Count Register)
    • EDX 数据寄存器(Data Register)
    • ESI、EDI 变址寄存器(Index Register)
    • EBP 堆栈基指针(Base Pointer)
    • ESP 堆栈顶指针(Stack Pointer)
  • 功能上作为程序计数器PC的寄存器:IP或EIP,只能使用控制指令修改
  • 字母表示可以大写也可以小写,第一个字母E表示Extended(拓展的)
  • 为了兼容性,EAX、EBX、ECX、EDX的低两个字节可以单独使用,以EAX为例
    • EAX低2字节称为AX
    • AX的高字节和低字节分别称为AH和AL
  • 除了EBP和ESP,其他寄存器的使用实际上是任意的

寻址模式

  • 如果是两个地址参数,第一个为目的地址,第二个为源地址
  • 中括号内是地址,整体表示取括号中地址对应的存储空间
  • 计算地址最多只能用2个32位寄存器和1个32位有符号常数相加

内存分配

  • 汇编语言中声明内存大小,显示的使用
    • DB:Data Byte,单字节
    • DW:Data Word,双字节
    • DD:Double Word,四字节
  • 对于常数的分配空间大小,可以使用标识符
    • BYTE PTR:常数以单字节形式
    • WORD PTR:常数以双字节形式
    • DWORD PTR:常数以四字节形式
  • 指令可以有后缀指明空间大小,以传送指令mov为例
    • movb:传送单字节
    • movw:传送双字节
    • movl:传送四字节
    • movq:传送八字节

常用指令

  • 以<reg>、<mem>、<con>分别表示操作数为寄存器、内存、常数的情况。如果reg和con后带数字则表示指定了位数。
  • 以<label>表示标签,放在指令的前面,表示指令的地址
  • 数据传送指令
    • mov指令:将第二个操作数复制到第一个操作数。要求目的操作数不为常数,且不能从内存到内存
      • mov <reg>,<reg>
      • mov <reg>,<mem>
      • mov <reg>,<con>
      • mov <mem>,<reg>
      • mov <mem>,<con>
    • push指令:ESP值减4后把操作数压入栈中,可以看出栈增长方向是从大地址到小地址
      • push <reg32>
      • push <mem>
      • push <con32>
    • pop指令:把操作数弹出栈后ESP加4
      • pop <reg32>
      • pop <mem>
  • 算术和逻辑计算指令
    • add/sub指令:第一个操作数加上/减去第二个操作数,结果保存在第一个操作数位置
      • add/sub <reg>,<reg>
      • add/sub <reg>,<mem>
      • add/sub <reg>,<con>
      • add/sub <mem>,<reg>
      • add/sub <mem>,<con>
    • inc/dec指令:操作数自增1/自减1
      • inc/dec <reg>
      • inc/dec <mem>
    • imul指令:带符号整数乘法指令。第一个(目的)操作数必须是寄存器;可以有一个或两个源操作数;一个源操作数时不使用常数,其与目的操作数的积放到目的操作数;两个源操作数时使用常数,把积放到目的操作数;如果溢出则OF=1
      • imul <reg32>,<reg32>
      • imul <reg32>,<mem>
      • imul <reg32>,<reg32>,<con>
      • imul <reg32>,<mem>,<con>
    • idiv指令:带符号除法指令。一个操作数表示除数。被除数为EDX:EAX。结果商存入EAX,余数存入EDX。
      • idiv <reg32>
      • idiv <mem>
    • and/or/xor指令:逻辑与/或/异或,结果放在第一个操作数
      • and/or/xor <reg>,<reg>
      • and/or/xor <reg>,<mem>
      • and/or/xor <reg>,<con>
      • and/or/xor <mem>,<reg>
      • and/or/xor <mem>,<con>
    • not指令:取反(位翻转)指令
      • not <reg>
      • not <mem>
    • neg指令:取负指令
      • neg <reg>
      • neg <mem>
    • shl/shr指令:逻辑移位指令,结果存放在第一个操作数,第二个操作数为移位的位数
      • shl/shr <reg>,<con8>
      • shl/shr <mem>,<con8>
      • shl/shr <reg>,<cl>
      • shl/shr <mem>,<cl>
  • 控制流指令
    • jmp指令:控制IP转移到label指示的地址执行
      • jmp <label>
    • cmp指令:第一个操作数和第二个操作数比较,根据结果设置处理机状态字条件码
      • cmp <reg>,<reg>
      • cmp <reg>,<mem>
      • cmp <reg>,<con>
      • cmp <mem>,<reg>
    • jcondition指令:根据处理机状态字进行条件转移
      • je <label> 等于时跳转
      • jne <label> 不等于时跳转
      • jz <label> 等于0时跳转
      • jg <label> 有符号大于时跳转
      • jge <label> 有符号大于等于时跳转
      • jl <label> 有符号小于时跳转
      • jle <label> 有符号小于等于时跳转
      • ja <label> 无符号大于时跳转
      • jae <label> 无符号大于等于时跳转
      • jb <label> 无符号小于时跳转
      • jbe <label> 无符号小于等于时跳转
    • call/ret指令:用于函数的调用和返回。call把当前指令地址入栈后,无条件转移到标签处;ret从栈中弹出之前保存的地址,无条件转移回之前的地址位置
      • call <label>
      • ret

数据对齐和大小端存放

详见第二章笔记中,“数据的存储和排列”小节

CISC和RISC的基本概念

  • 复杂指令系统计算机(CISC)
    • 增强原有指令的功能,设置更复杂的新指令,使软件功能硬件化实现
    • 比如X86架构计算机
  • 精简指令系统计算机(RISC)
    • 减少指令的种类、简化指令的功能,使指令的速度提高
    • 比如ARM、MIPS架构计算机

复杂指令系统计算机

CISC的特点如下:

  • 指令系统:复杂庞大
  • 指令数目:一般在200条以上
  • 指令字长:不固定。指令格式多,寻址方式多
  • 可访存的指令:不受限制
  • 各指令使用频度:差别大,遵循28定律(20%的指令使用频率80%,80%的指令很少使用)
  • 各指令执行时间:差别大,大部分需要多个时钟周期
  • CPU中通用寄存器数量:较少
  • 控制器控制方式:大多数采用微程序控制。有些指令很复杂,无法用硬连线控制。
  • 目标代码优化:难以用优化编译生成高效的目标代码程序
  • 指令流水线技术:可以通过一定方式实现
  • 软件兼容性:比较好,高档机可保护低档机全部指令并加以扩充

精简指令系统计算机

RISC的特点如下:

  • 指令系统:选取使用频率高的简单指令,复杂的指令由简单指令组合实现
  • 指令数目:一般在100条以下
  • 指令字长:固定。指令格式少,寻址方式少
  • 可访存的指令:只有LOAD/STORE(取数存数)允许访存。其余指令的操作在寄存器之间进行
  • 各指令使用频度:都经常使用
  • 各指令执行时间:大部分指令在一个时钟周期内完成
  • CPU中通用寄存器数量:非常多
  • 控制器控制方式:以硬布线控制(组合逻辑控制)为主,基本不用微程序控制
  • 目标代码优化:特别重视编译优化工作,以减少程序执行时间
  • 指令流水线技术:一定采用
  • 软件兼容性:大多数RISC不能和老机器兼容,但因为实用性强,是未来的发展方向

高级语言程序与机器代码之间的对应

编译器、汇编器和链接器的基本概念

详见第一章笔记“高级语言程序与机器语言程序转换”小节

过程(函数)调用的机器级表示

假设P调用Q,则步骤如下

  • P保存现场:当需要保留调用者保存寄存器(包括EAX、ECX、EDX)的值,进行此步骤
  • P压参数:把调用参数按从右到左的顺序压入栈中(如果寄存器数量充足,也可能把部分参数用寄存器保存)
  • P执行CALL指令
    • 存旧PC:把返回地址(调用指令后一条指令的位置)压栈
    • 更新PC:修改PC至跳转处,此后Q过程开始执行
  • Q准备阶段
    • 存原栈底:将EBP(P的栈底)压栈(此时ESP为栈顶,所指位置存放P的栈底)
    • 更新栈底:更新EBP为ESP位置(即Q的栈底中存放P的栈底)
    • 更新栈顶:修改ESP,为自己分配栈空间(一般减去大小为16字节的倍数,以便对齐)
    • 保存现场:如果需要用被调用者保存寄存器(包括EBX、ESI、EDI),进行此步骤
  • Q过程体阶段
    • 局部变量空间分配:一般按低地址到高地址的顺序使用栈空间(和栈增长方向相反)
    • 通常按小端存放,考虑对齐
    • 结束时设置返回值(通常是放到EAX寄存器中)
  • Q结束阶段
    • Q恢复P的现场
    • Q执行leave指令
      • 恢复栈顶:修改ESP等于EBP,以释放Q的栈空间
      • 恢复栈底:弹出Q的EBP所指向P的EBP的值给EBP,即恢复P的栈空间
    • Q执行ret指令
      • 恢复PC:弹出此时ESP所指向的返回地址给PC,以返回P执行
  • P恢复现场
  • P继续执行CALL指令的下一条指令

注:关于一个过程P的栈帧 + P栈帧头:P过程EBP指向空间(存放P过程的调用者的旧EBP) + P栈帧尾:下一个过程Q的EBP指向空间(存放的是P的EBP)的前一个空间(存放的是P的返回地址) + 栈帧是周期性的,P的栈帧尾后面就是Q的栈帧头

选择结构语句的机器级表示

条件码

  • 即标志位寄存器,包括
    • CF 进位标志:用于无符号数
    • ZF 零标志:最近运算是否为0
    • SF 符号标志:最近运算结果的符号
    • OF 溢出标志:用于带符号数
  • cmp和sub对条件码的行为一致
  • test和and对条件码行为一致
  • jcondition指令结合条件码的ZF和SF可以实现跳转

if语句

  • 可以利用if-goto语句分析从高级语言到汇编的过程
  • 如果if条件不满足,则goto跳转
  • 然后把if-goto转换到对应的汇编中,使用cmp/test、jcondition Label的方式

switch语句

  • 相比于if语句多次条件判断来跳转,switch是多路选择,一次直接跳到某个条件处的语句执行
  • 需要用到跳转表
    • 段属性为只读,即.section .rodata
    • 跳转地址在4字节边界上,即align 4
    • 跳转表的头设置一个标记Label,头之后的每一行(项)都是一个标签Labeli,i=0,1,...,7
    • 给出一个例子
      • 设switch输入10、12、14、15、17分别对应情况L2、L3、L4、L1、L3,其他输入对应情况L5
      • 跳转表Label标签后面的每一行分别表示情况10、11、12、...、17需要跳到的标记Labeli,即L2、L5、L3、L5、L4、L1、L5、L3
      • 汇编代码中,首先把判断的输入减10,记为t。
      • 比较t和7的关系(条件码设置按无符号减法)。
      • 如果大于7(根据无符号数运算,这里包括t是负数的情况),跳转到L5
      • 否则根据t作为索引查跳转表对应表项标签Labelt,跳转到跳转表此项对应的标签位置执行即可

循环结构语句的机器级表示

  • 高级语言转汇编可以借助if-goto作为中间代码,方便分析转换
  • 循环结构有三种情况
    • do-while循环:一次goto即可
    • while循环:相当于在do-while前先判断条件一次。需要两个goto
    • for循环:相当于while语句前先做一个初始化语句,可以先转成while循环。需要两个goto。
  • 最后把if-goto转换到对应的汇编中,使用cmp/test、jcondition Label的方式