程序控制类指令笼统的来讲就是和CS和IP寄存器直接打交道,总共分为四类:转移类指令、循环控制类指令、过程调用指令和中断控制指令。
转移类指令
无条件转移指令
JMP指令的操作是无条件地使程序转移到特定的目标地址,并从该地址开始执行新的程序段,其中寻找目标地址的方法有两种:间接寻址和直接寻址,又由于程序逻辑分段(每个逻辑段至少有一个代码段),因此无条件转移指令JMP一共有四种类型:
操作数类型 | 转移目标地址 |
8位/16位符号地址 | 本逻辑段内 |
16位寄存器操作数或者16位存储器操作数 | 本逻辑段内 |
32位符号地址 | 本逻辑段外 |
32位存储器操作数 | 本逻辑段外 |
注意:
1)段内转移只修改IP寄存器的值,段间转移修改CS和IP寄存器中的值;
2)段内寻址和段间寻址的实质:
寻址类型 | CS代码段寄存器 | IP指令指针寄存器 |
段内寻址 | 不改变 | 原有地址+偏移地址 |
段间寻址 | 原有地址+偏移地址 | 原有地址+偏移地址 |
3)不同位数的偏移地址可执行的偏移范围不同:
偏移地址的位数 | 最大偏移地址 | 最小偏移地址 |
8位符号地址 | +127 | -128 |
16符号地址 | +32767 | -32768 |
4)“段内偏移地址+原IP寄存器中的地址”必须非负,并且不可以超过64K(代码段最大长度)。
1. 无条件段内直接转移:JMP [NEAR] label
无条件转移类指令在操作数前可添加运算符NEAR,该运算符显式地规定了label所在位置为本逻辑段内,但是我们也可以省略到NEAR运算符,这样系统默认进行段内寻址。
段内寻址的符号地址也被称为近地址标号。
2. 无条件段内间接转移
我们在前面章节学到的寻址方式中,间接寻址是“存储器操作数”和“寄存器操作数(间址寄存器:基址寄存器(BX、BP)、变址寄存器(SI、DI))”,由于我们只需要修改IP寄存器中指向的地址,因此我们只需要16位操作数即可,间接寻址的两种寻址方式均满足我们的要求。
注意:使用间址寄存器进行间接寻址时,一定要注意正确的寻址方式:
存储器操作数的寻址格式 | |
寄存器间接寻址 | JMP [基址寄存器] |
寄存器基址变址寻址 | JMP [基址寄存器+变址寄存器] |
寄存器基址变址相对寻址 | JMP [基址寄存器+变址寄存器+数值偏移量] |
当16位操作数为寄存器操作数时,IP寄存器修改原理如下所示:
3. 段间无条件直接转移指令
此时,我们JMP的操作数是32位的符号地址,这32位的符号地址可以拆分为高16位(CS的偏移地址)和低16位(IP的偏移地址),这类32位的符号地址也被称之为“远地址标号”。
段间无条件直接转移指令格式:JMP FAR label,其中FAR是一个运算符,表征着label是一个32位的符号地址。
4. 段间无条件间接转移指令
由于此时需要32位的地址,寄存器操作数显然不可以胜任,此时操作数类型只能是存储器操作数,指令执行原理如下所示:
条件转移类指令
条件转移类指令有两个共性:
1. 受标志位影响;
2. 操作数寻址方式均为直接寻址,并且均为近地址标号,即跳转地址范围为+128~-127。
常用的条件转移类指令如下所示:
1. 有符号数跳转指令:
条件转移类指令助记符 | 跳转条件 | 备注 |
JG/JNLE label | SF=OF且ZF=0 | 有符号数-大于/小于等于 |
JGE/JNL label | SF=OF或ZF=1 | 有符号数-大于等于/小于 |
JL/JNGE label | SF!=OF且ZF=0 | 有符号数-小于/大于等于 |
JLE/JNG label | SF!=OF或ZF=1 | 有符号数-小于等于/大于 |
JO label | OF=1 | 溢出 |
JNO label | OF=0 | 不溢出 |
JS label | SF=1 | 有符号数-结果为负值 |
JNS label | SF=0 | 有符号数-结果为正值 |
2. 无符号数跳转指令:
条件转移类指令助记符 | 跳转条件 | 备注 |
JA/JNBE label | CF=0且ZF=0 | 无符号数-大于/不小于等于 |
JAE/JNB label | CF=0或ZF=1 | 无符号数-大于等于/不小于 |
JB/JNAE label | CF=1且ZF=0 | 无符号数-小于/不大于等于 |
JBE/JNA label | CF=1或ZF=1 | 无符号数-小于等于/不大于 |
JC label | CF=1 | 无符号数-进位 |
JNC label | CF=0 | 无符号数-不进位 |
3. 通用条件转移类指令:
条件转移类指令助记符 | 跳转条件 | 备注 |
JCXZ label | CX=0 | 循环结束 |
JE/JZ label | ZF=1 | 结果为零 |
JNE/JNZ label | ZF=0 | 结果非零或不相等 |
JP/JPE label | PF=1 | 1的个数为偶数个 |
JNP/JPO label | PF=0 | 1的个数为奇数个 |
条件转移类指令使用示例:
控制循环类指令
循环控制类指令顾名思义是在循环程序中用来控制循环的,其控制转向的目标地址是以当前IP寄存器中存放的地址为中心的-128~+127字节范围内。循环次数必须在调用控制循环类指令之前放入CX计数寄存器中。与转移类指令类似,这里也有无条件循环类指令和有条件循环类指令,如下所示:
循环类指令分类 | 循环条件 | 退出循环的条件 |
无条件循环类指令LOOP | CX=0 | 计数器数值为0 |
有条件循环类指令LOOPZ | CX=0且ZF=1 | “计数器数值为0”或“结果为0” |
有条件循环类指令LOOPNZ | CX=0且ZF=0 | “计数器数值为0”或“结果不为0” |
注意:
1. 这些控制循环类指令的符号地址均为近地址标号;
2. 每循环一次LOOP指令对CX计数寄存器中的数据进行-1操作。
有条件循环类指令LOOPZ的使用示例:
过程调用与返回
在编程过程当中,为了节省内存单元,往往将程序中常用到的具有相同功能的部分独立出来,编写成一个模块,称之为子程序(或者子过程)。程序执行中,主程序在需要时可随时调用这些子程序,子过程执行完之后,有返回到主程序继续执行,有时还可以进行多级调用(函数的嵌套使用)。为此8086指令集提供了调用指令CALL和返回指令RET。
在调用CALL指令跳转至子程序中时,需要经历的操作如下所示:
1. 在子程序中会被使用得到的寄存器,这样的寄存器中保存着现程序下运算的数据;
2. CALL指令所在的地址的下一行代码的地址被称为返回地址,需要将其保存起来;
3. CALL指令为控制类指令,因此需要修改IP(或IP和CS)中的地址实现程序的跳转。
RET指令可以实现子程序的返回,RET指令的调用实现了以下功能:
1. 将堆栈中保存的数据弹出至指定寄存器中;
2. CPU将堆栈顶部保留的返回地址弹出到IP(或IP和CS)中,这样即可返回到CALL的下一条指令,继续执行主程序。
CALL不仅可以在本逻辑段内调用子程序还可以跨逻辑段调用,并且子程序的入口地址提供方式也有直接和间接两种类型,因此CALL指令可以分为四种:
CALL指令的类型 | 入口地址存储位置 | 子程序位置 |
段内间接寻址 | 内存或者寄存器 | 本逻辑段内 |
段外间接寻址 | 内存或者寄存器 | 本逻辑段外 |
段内间接寻址 | 符号地址 | 本逻辑段内 |
段外间接寻址 | 符号地址 | 本逻辑段外 |
1. 段内直接寻址:CALL NEAR label
NEAR和FAR用于标识label这个符号地址到底是近过程地址还是远过程地址,在段内调用时,NEAR可以省略,若label地址存放在内存中,16位的就是近地址标号,32位的就是远地址标号。CALL指令的执行流程如下所示:
2. 段内间接调用:CALL OPEN1
操作数寻址方式 | IP的偏移地址 |
存储器操作数 | [基址寄存器+1]和[基址寄存器+2] |
寄存器操作数 | 通用寄存器 |
段内间接调用中,OPEN1可以是内存中的2个字节,也可以是16位的寄存器。
3. 段间直接调用:CALL FAR label
CS的偏移地址:label的高16位,IP的偏移地址:label的低16位。
如上图所示,段间直接调用会先将现有的CS和IP寄存器中的数值、本进程中的运算数据压栈,然后会修改CS和IP寄存器中的值,最终程序跳转到入口地址处。
4. 段间间接调用:CALL OPEN1
CS和IP的偏移地址获取途径如下所示:
操作数类型 | CS的偏移地址 | IP的偏移地址 |
存储器操作数 | [基址寄存器]和[基址寄存器+1] | [基址寄存器+2]和[基址寄存器+3] |
如上图所示,段间直接调用会先将现有的CS和IP寄存器中的数值、本进程中的运算数据压栈,然后会修改CS和IP寄存器中的值,最终程序跳转到入口地址处。
中断指令
所谓中断,是指在程序运行期间因某种随机或异常的事件,要求CPU暂停中止正在运行的程序转去执行一组专门的终端服务程序来处理这些事件,处理完后又返回到原被中止处继续执行原程序的过程。
引起中断的事件叫做中断源,中断和过程调用最大的区别就是触发子程序执行的时机不同:
中断子程序调用的时机 | 外部/内部随机触发 |
过程子程序调用的时机 | 使用CALL指令 |
8086/8088中断系统分为“CPU外部中断(硬件中断)”和“CPU内部中断(软件中断)”,CPU外部中断是用来处理外设和CPU之间的通信(例如:timer定时器中断,IIC传输完毕中断),CPU内部中断是用来处理运算异常及中断指令引起的中断(运算溢出、运算归零)。
FLAGS标志寄存器中与中断有关的两个标志位:
TF陷阱标志位 | 是否进行单步调试 |
IF中断允许标志位 | 是否接收外部中断信号 |
我们后面会学到系统BIOS,这里面包括操作计算机中各种硬件资源的中断向量码,中断向量码是8位二进制数,要注意中断向量码并不是中断入口地址!
中断指令调用格式:INT N,这里N是一个8位立即数,取值范围0~255。由于中断子程序的存储地址在逻辑段外部,因此需要使用远程地址调用,即修改CS和IP寄存器,这一点很像段间间接调用。INT指令执行流程如下所示:
CALL和INT指令的区别:
属性 | CALL指令 | INT指令 |
是否影响标志位 | 不影响任何标志位 | 复位TF和IF标志位 |
段间寻址方式 | 直接/间接 | 只有间接 |
子程序调用时机 | CALL指令被调用时 | 随机中断触发 |
中断返回指令:IRET
中断返回指令的调用没有操作数,中断返回指令的调用原理: