Skip to main content

指令系统

tip

指令(机器指令)是指示计算机执行某种操作的命令。一台计算机中所有指令的集合构成指令系统,或称指令集。

指令基本格式

指令是一串而二进制代码,通常包括 操作码字段地址码字段

根据长度可以分为半字长指令、单字长指令或双字长指令。

根据操作数地址码的数量可以分为零地址指令、一地址指令、二地址指令和三地址指令等。

info

这些指令可能会有某些 隐含操作,如对于零地址指令其将两个操作数从栈顶弹出再将结果入栈等。

若在一个指令系统中所有操作码的长度相同,则成为 定长指令,其执行识别 速度快,控制简单。

扩展操作码指令格式

为了在字长有限的情况下增加指令种类,可以采用 可变长度操作码,但会增加指令译码和分析的难度。

最常见的变长操作码方法就是 扩展操作码,具体方法为短码占用一定的前缀,而长码的前缀不能和短码重复,这样译码时可以根据前缀判断是短码还是长码。

一般对使用频率高的指令分配短的操作码,使用频率低的指令分配长的操作码,后面会有分配方法。

指令的操作类型

按照功能可以分为一下几类:

  1. 数据传送,通常有寄存器间的传送(MOV)、内存读取到寄存器(LOAD)、寄存器写入内存(STORE)等;
  2. 算术和逻辑运算,加(ADD)、减(SUB)、乘(MUL)、除(DIV)、比较(CMP)、加一(INC)、减一(DEC)、与(AND)、或(OR)、非(NOT)、异或(XOR)等;
  3. 移位操作,算术移位、逻辑移位、循环移位等;
  4. 转移操作,无条件转移(JMP)、条件转移(BRANCH)、调用(CALL)、返回(RET)、陷阱(TRAP)等;
  5. 输入输出操作,用于完成 CPU 和外部设备之间的交换数据或传送控制命令及状态信息。

指令寻址方式

指令中地址码字段并不是操作数的真实地址,这种地址被称为 形式地址(A),通过寻址才能找到其在存储器中的真实地址,被称为 有效地址(EA).

(A) 表示地址为 A 的数值,即该地址中所存放的数据内容。

指令寻址

指令寻址有两种方式:

  1. 顺序寻址,即程序计数器加一,自动形成下一条指令的地址;
  2. 跳跃寻址,通过转移类指令实现,跳跃地址分为 绝对地址(由标记符直接得到)相对地址(相对于当前指令的偏移量),跳跃的结果是当前指令修改 PC 值,因此下条指令还是由程序计数器给出。

数据寻址

隐含寻址

默认操作数的地址为特定的寄存器,如累加寄存器 ACC,优点是有利于缩短指令字长,缺点是需要增加存储操作数或隐含地址的硬件。

立即(数)寻址

地址码表示的不是操作数地址而是 操作数本身

直接寻址

EA=AEA=A,地址码就是有效地址,优点是简单,指令执行阶段仅访问主存一次,缺点是寻址范围有限,取决于 AA 的位数。

间接寻址

EA=(A)EA=(A),有效地址是形式地址的中所存的数,可以有多次间接寻址(需要根据主存内容最高位来定,若是 1 表示仍然需要再进行间接寻址,直到最高位为 0)。

间接寻址的优点是可以扩大寻址范围,但是要多次访问主存,由于访问速度满,不太常用,一般提及扩大寻址范围常用 寄存器间接寻址

寄存器寻址

操作数直接存放在寄存器中,优点是执行阶段无需访存,速度快,支持向量/矩阵运算,且指令字短,但是寄存器价格昂贵,数量少。

寄存器间接寻址

此时寄存器中存放的是操作数在主存当中的地址,需要一次访问。

相对寻址

EA=(PC)+AEA=(PC)+A,以程序计数器 PC 的内容为基准,加上形式地址得到最终的有效地址,广泛应用于 转移指令

相对寻址中形式地址使用补码表示,可正可负。

基址寻址

CPU 中基址寄存器(BR)的内容加上形式地址形成有效地址,EA=(BR)+AEA=(BR)+A,其中基质寄存器可以是专用寄存器也可以是通用寄存器。

基址寄存器是面向操作系统的,其内容由操作系统或管理程序确定,主要用于解决程序逻辑空间与存储器物理空间的无关性。程序执行过程中,基址寄存器 中的内容作为基地址 保持不变,形式地址可变,当使用通用寄存器作为基址寄存器时,用户可以指定哪个通用寄存器作为基址寄存器,但是其内容仍然由操作系统决定。

优点:寻址范围大,用户不必考虑程序存于主存的哪个位置,有利于多道程序设计,可用于编制浮动程序(即程序可以从主存中的一个区域移动到其他区域)。

缺点: 偏移量的位数较短。

变址寻址

CPU 中变址寄存器(IX)的内容加上形式地址形成有效地址,EA=(IX)+AEA=(IX)+A,同样可以用专用寄存器和通用寄存器。

变址寄存器是面向用户的,程序执行过程中,变址寄存器内容可变,形式地址保持不变(充当基地址)。

tip

可以应用于数组处理的过程,如将基地址设为数组起点,变址寄存器存放偏移量用于索引。

优点:寻址范围大,方便处理数组问题。

堆栈寻址

info

堆栈是存储器(或专用寄存器组)中一块特定的、按后进先出原则管理的存储区,其中读写单元的地址由堆栈寄存器(SP)给出。

堆栈可以分为硬堆栈(寄存器堆栈)与软堆栈(主存堆栈),前者成本高,容量小,后者更划算和常用。

采用堆栈结构的计算机中,大部分指令都表现为无操作数指令的形式,因为其往往隐含使用了 SP.

程序的机器级代码表示

相关寄存器

x86 处理器中有 8 个 32 位通用寄存器,如下表:

31-2423-1615-87-0
AHAL
BHBL
CHCL
DHDL
ESIESIESIESI
EDIEDIEDIEDI
EBPEBPEBPEBP
ESPESPESPESP

按照行的顺序进行解释:

  1. 累加器 AX,占据 16 位,并且可以分为高位 AH 和低位 AL,整个 32 位被称为 EAX,这里的 E 表示拓展 Extended 的意思,高 16 位和低 16 位也可以分开使用,下同;
  2. 基地址寄存器;
  3. 计数寄存器;
  4. 数据寄存器;
  5. 变址寄存器,长度 32 为,下同;
  6. 变址寄存器;
  7. 堆栈基指针,和堆栈相关的指针一般不能随意使用,而其他寄存器都是可以的,下同;
  8. 堆栈顶指针。

汇编指令格式

一般有两种汇编格式:AT&T 格式和 Intel 格式,主要区别如下:

  1. AT&T 的指令只能使用小写字母,而 Intel 指令对大小写不敏感;
  2. AT&T 中第一个为源操作数,第二个为目的操作数,Intel 则相反;
  3. AT&T 中寄存器前面需要加 %,立即数前需要加 $,Intel 中则都不需要;
  4. AT&T 使用 () 进行寻址,Intel 中使用 []
  5. 在处理复杂寻址时,AT&T 中的内存操作数为 disp(base,index,sacle),分别表示偏移量、基址寄存器、变址寄存器和比例因子,如 8(%edx, %eax, 2),表操作数为 M[R[edx] + R[eax]*2 + 8],其对应的 intel 格式为 [edx + eax * 2 + 8]
  6. 在指定数据长度时,AT&T 在操作码后紧跟一个字符,b 表示 bytew 表示 wordl 表示 long(双字),Intel 则表明 byte ptrword ptrdword ptr.
caution

由于 32326464 位体系结构都有 1616 位扩展而来,因此字指的是 1616 位。

常用指令

汇编指令可以分为 数据传送指令逻辑计算指令控制流指令

下面介绍用于操作数的标记:

  1. <reg>,表示寄存器,后面跟数字表示其位数,下同;
  2. <mem>,表示内存地址;
  3. <con>,表示常数;

下面以 Intel 格式为例进行介绍。

数据传送指令

名称格式功能备注
movmov <reg>|<mem> <reg>|<mem>|<con>将第二个操作数复制到第一个操作数不能直接用于从内存复制到内存
pushpush <reg32>|<mem>|<con32>将操作数压入内存的栈中,常用于函数调用。栈元素固定为 32 位
poppop <reg> | [<var>]将栈顶弹出送入寄存器或指定的内存地址中
caution

x86x86 架构中,栈是从高地址向低地址增长的,栈顶 ESPESP 的增长方向与栈实际增长方向相反,若入栈,ESPESP 应该减去 443232 位)(字节)。

算术和逻辑运算指令

名称格式功能备注
add/subadd/sub <reg>|<mem> <reg>|<mem>|<con>将两个操作数相加/减,结果存入第一个操作数中,减法为第一个操作数减第二个同样不能直接对内存中的两个操作数进行运算
inc/decinc <reg>|<mem>对操作数自增/减
imulimul <reg32> <reg32>``<mem>带符号整数乘法,将两个操作数相乘,结果存在第一个操作数第一个操作数必须为寄存器,乘法结果溢出则 OF=1
imul <reg32> <reg32>|<mem> <con>将后面两个操作数相乘存入第一个第三个操作数必须为常数?
idividiv <reg32>|<mem>带符号整数除法,只有一个操作数作为除数,被除数在 edx: eax 中,商被送入 eax, 余数被送入 edx被除数是 64 位整数
and/or/xorand/or/xor <reg>|<mem> <reg>|<mem>|<con>位操作,结果送入第一个操作数同样不能直接对两个内存中的操作数运算
notnot <reg>|<mem>按位取反
negneg <reg>|<mem>取负
shl/shrshl/shr <reg>|<mem> <cl>|<con8>逻辑左/右移,第二个操作数表示移位的位数

控制流指令

名称格式功能备注
jmpjmp <label>控制 IP 跳转到指定的 label 所指示的地址
jconditionjcondition <label>条件转移指令,根据 CPU 状态字的一系列状态转移
je相等时跳转
jne不相等时跳转
jz上个运算结果为 0 时跳转
jg大于时跳转
jge大于等于时跳转
jl小于时跳转
jle小于等于时跳转
cmp/testcmp/test <reg>|<mem> <reg>|<mem>|<con>cmp 用于比较两个操作数的值,test 用于两数进行逐位运算不保留操作结果,仅根据运算结果设置 CPU 状态字中的条件码,常和 jcomdition 搭配使用
callcall <label>调用子程序(过程、函数等)执行
retret子程序返回

过程调用的机器级表示

tip

callcallretret 指令主要用于过程调用,属于一种无条件转移指令,过程调用可以认为是高级语言中的函数调用。

过程转移指令的执行步骤如下所示,其中调用者为 PP,被调用过程为 QQ

  1. PP入口参数(实参)放在 QQ 能访问到的地方;
  2. PP返回地址 保存到特定的地址,然后将控制转移到 QQ(call 指令);
  3. QQ 保存 PP 的现场(通用寄存器的内容),同时为 QQ 中的非静态局部变量分配空间;
  4. 执行 QQ
  5. QQ 恢复 PP 的现场,将返回结果放到 PP 能访问的地方,同时释放局部变量;
  6. QQ 取出返回地址,将控制交换 PP(ret 指令)。

上述步骤中所需要的入口参数、返回地址、现场、局部变量等,都存储在一个专门的区域中——

EAXEAXECXECXEDXEDX 是调用者保存寄存器,其保存和恢复的过程由 PP 控制,PP 调用 QQ 时,QQ 可以直接使用这些寄存器,同样,EBXEBXESIESIEDIEDI 是被调用者保存寄存器,QQ 必须先将它们的值保存到栈中才能使用,并在返回前恢复。

每一个过程所占据的栈区被称为 栈帧,帧指针寄存器 EBPEBP 指示栈帧的起始位置(栈底),栈指针寄存器 ESPESP 指示栈顶,栈从高地址向低地址增长,因此入栈是 ESPESP--.

GCCGCC 中为了保证数据严格对齐规定了,每个函数栈帧的大小必须是 1616 字节。

caution

这里所说的栈从高地址从低地址增长主要原因到底是什么,并没有找到具体明确的解释,说法很多,可以归于两类:

  1. 由于数组的分配的是从低地址向高地址(大端小端不考虑了吗?),而栈从高地址向低地址,可以方便的找到数据的指针;
  2. 是人为规定的,堆是从低地址到高地址(二者建立在共享栈上?),两者可以提高利用率;某些汇编可以认为规定方向。

选择语句的机器级表示

条件码(标志位):标志位寄存器描述了最近的算术或逻辑运算的属性,可以通过这些状态进行条件跳转

标志位作用详细
CF进(借)标志最近无符号帧数加(减)运算后的进(借)位情况,有则为 1
ZF零标志最近操作的匀速啊结果时不时为 0, ,为 0 则为 1
SF符号标志最近的带符号数运算的结果的符号,为负则为 1
OF溢出标志最近的带符号数运算结果是否溢出,溢出则为 1

也有两个只设置条件码而不改变其他任何寄存器的指令——cmpcmptesttest,分别和 subsubandand 的行为一致,但是只改变标志位,不会更新到寄存器中。

结合前文提到的 控制流指令 ,即可根据标志位实现对应的跳转。

循环语句的机器级表示

循环同样可以通过对应的跳转指令实现,不再赘述。