学到的知识,很大的一部分会被忘却,而被忘记的知识的影子却保护你避免陷入很多的错觉。——伊顿公学校长威廉·考利
汇编语言是很多相关课程的重要基础,比如:操作系统、接口技术等。它是底层编程语言,是计算机系统提供给用户最快最有效的语言,也是能对硬件直接编程的语言。因此,对空间和时间要求很高的程序,或需要直接控制硬件的程序,必须使用汇编语言进行程序设计。
BCD码(Binary Coded Decimal)是一种二-十进制的编码,它使用4位二进制数表示一位十进制数。最常用的BCD码是8421码,又叫NBCD码(Natural Binary Coded Decimal Code),由于4位二进制数可表示16种状态,只取前10种状态0000-1001来表示十进制数码0-9,从左到右每位二进制数的权分别是8、4、2、1,因此又叫8421码。
例如:十进制数1258对应的BCD码是0001001001011000
;
反之,BCD码1001 1000 0111 0010
对应的十进制数是9872。
压缩BCD码:用一个字节表达两位BCD码,比如 0100 1001
表示49
。
非压缩BCD码:将8位二进制的高4位设置为0,仅用低4位表达一位BCD码。比如00000100 00001001
表示49
。
80X86是美国Intel公司生产的微处理器系列。
微处理器:把运算器和控制器集成在一个芯片上,构成的中央处理器(CPU)。
微机:即微计算机系统,由微处理器配上存储器、输入输出设备和系统软件等构成。各硬件用系统总线连接在一起。
系统总线包括数据总线(DB)、地址总线(AB)和控制总线(CB)三组。
数据总线宽度16位:决定了数据的传输速率。
机器字长为16位:可表示数的范围为0000~FFFFH(2^16 = 64K)
地址总线宽度为20位:寻址空间为2^20 = 1024KB = 1MB。
计算机是在时钟控制下进行工作的,若干个时钟完成一个基本操作,一个基本操作就是一个总线周期,CPU有若干种典型操作,构成相应的总线周期。如存储器的读写总线周期,I/O读写总线周期等。
执行一条指令的时间称为指令周期,指令周期是由若干总线周期构成。
8086/8088的基本总线周期是由4个时钟周期组成,在执行WAIT指令或READY引脚输入的状态为低电平时,都需要在 T3 和 T4 之间插入1个或若干个等待时钟周期 Tw 。
CPU向多路复用总线上发送地址信息,指出要寻址的内存单元地址或I/O端口地址。这期间CPU还要ALE(正向脉冲),在ALE下降沿将内存单元地址或I/O端口地址打入地址锁存器。
CPU从总线上撤销有效地址,使地址总线低16位呈高阻状态,为数据传输做准备。总线的高4位( A19 ~ A16 )输出总线周期的状态信息,用于表示中断允许状态及正在使用的段寄存器名。
A19 ~ A16 上的状态信息不变,地址总线低16位上出现CPU要写出的或准备读入的数据。若外设与内存来不及与总线交换数据,则应通过CPU的READY信号,在 T3 前沿(下降沿)之前向CPU申请插入等待状态 TW ,在 T3 及 TW 前沿查询READY信号,查到高电平则结束等待状态,进入下一状态。否则继续插入等待。
在一个总线周期之后,若不立即进入下一个总线周期,即CPU不与内存或外设交换数据或者指令队列已满,系统总线处于空闲状态,CPU执行总线空闲周期,总线空闲周期一般由一个或多个时钟周期组成。
CPU包含了三个部分:算术逻辑、控制逻辑、寄存器组。
内部设有四个段地址寄存器,一个指令指针寄存器IP,一个6字节指令队列缓冲器,20位地址加法器和总线控制电路。
主要功能:根据执行部件EU的请求,负责从内存单元中预取指令,并将它们送到指令队列缓冲器暂存。即负责完成CPU与存储器或I/O设备之间的数据传送。
执行部件中包含算术逻辑单元(ALU)、通用寄存器、状态标志寄存器、数据暂存寄存器和执行部件的控制电路。
主要功能:从BIU的指令队列中取出指令代码,经指令译码器译码后执行指令所规定的全部功能。执行指令所得结果或执行指令所需的数据,都由EU向BIU发出命令,对存储器或I/O接口进行读/写操作。
名称 | 寄存器英文名 | 寄存器说明 |
---|---|---|
AX | Accumulator | 累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据。 |
BX | Base | 基址寄存器,常用于存放存储单元地址。 计算地址的时候用作基地址寄存器,用于扩展寻址,起变址的作用。 |
CX | Count | 计数寄存器,一般作为循环或串操作等指令中的隐含计数器;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器。 |
DX | Count | 数据寄存器,常用来存放双字数据的高16位,或存放外设端口地址,比如双字的乘除法。 存放操作数和列表数据,在某些I/O操作期间,用来,在乘除运算中有专用。 |
共同特点:
这四个十六位的寄存器可以分为:
寄存器名称 | 寄存器英文名 | 寄存器说明 |
---|---|---|
SP | Stack Pointer | 堆栈指针寄存器。与SS(堆栈段寄存器)配合使用来确定堆栈段栈顶的位置,也就是说SP用于存放栈顶的偏移地址。 |
BP | Base Pointer | 基址指针寄存器,可用作SS的一个相对基址位置(用于存放堆栈段中某一存储单元的偏移地址。) |
说明:指针寄存器和变址寄存器只能按16位进行存取操作,主要用来形成操作数的地址,用于堆栈操作和变址运算中计算操作数的有效地址。
寄存器名称 | 寄存器英文名 | 寄存器说明 |
---|---|---|
SI | Source Index | 源变址寄存器可用来存放相对于DS段之源变址指针,也就是说,源操作数偏址存放在SI中。 |
DI | Destination Index | 目的变址寄存器,可用来存放相对于 ES段的目的变址指针,也就是说,目的操作数偏址存放在DI中。 |
说明:
SI和DI一般与数据段寄存器DS联合使用,用来确定数据段中某一存储单元的地址。这两个变址寄存器有自动增量和自动减量的功能,所以用来变址是十分方便的。
在串处理中,SI和DI作为隐含的源变址和目的变址寄存器,此时SI和DI联用,DI和附加段寄存器ES联用,分别达到了在数据段和附加段寻址的目的。
指针寄存器和变址寄存器只能按16位进行存取操作,主要用来形成操作数的地址,用于堆栈操作和变址运算中计算操作数的有效地址。
指令指针IP是一个16位专用寄存器(指令指针寄存器),它指向当前需要取出的指令字节,当BIU从内存中取出一个指令字节后,IP就自动加上指令长度的值,指向下一个指令字节。
注意:IP指向的是指令地址的段内地址偏移量,又称偏移地址(Offset Address)或有效地址(EA,Effective Address),程序不能直接访问IP。
任意时刻,CPU将CS:IP指向的内存单元中的内容看作指令。
8086有一个18位的标志寄存器FR,在FR中有意义的有9位,其中6位是状态位,3位是控制位。
状态信息是由中央处理机根据计算结果自动设置的。
标志名称 | 说明 |
---|---|
OF | 溢出标志位OF(overflow flag)用于反映有符号数加减运算所得结果是否溢出。 如果运算结果超过当前运算位数所能表示的范围,则称为溢出。溢出时OF=1,否则OF= 0。 注意,这里所讲的溢出,只是对有符号数运算而言,对无符号数没有意义。 |
SF | 符号标志SF(sign flag)用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。正数SF=0,负数SF为1。 注意:SF是对有符号数运算有意义的标志位。 |
ZF | 零标志ZF(zero flag)用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。 |
AF | 辅助进位标志AF(auxiliary flag)记录运算时第3位(字节运算)或第7位(字运算)产生的进位或借位值。例如,执行加法指令时第3位有进位时AF=1,否则AF=0。 |
PF | 奇偶标志PF(parity flag)用于反映运算结果中“1”的个数的奇偶性。当结果操作数中1的个数为偶数时PF=l,否则PF=0。 |
CF | 进位标志CF(carry flag)主要用来反映运算是否产生进位或借位。当最高有效位有进位或借位时CF=1,否则置CF=0。 注意:CF是对无符号数运算有意义的标志位 |
控制标志是系统程序或用户程序根据需要用指令设置的。
标志名称 | 说明 |
---|---|
DF | 方向标志DF(direction flag)位用来决定在串操作指令执行时有关指针寄存器发生调整的方向。当DF位为1时,每次操作后使变址寄存器SI和DI减量;当DF为0时,则使SI和DI增量。 |
IF | 中断允许标志IF(interrupt flag)位用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下: (1)当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求; (2)当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。 总的来说:IF为1时,开中断,否则关中断。 |
TF | 跟踪标志TF(trap flag), 也叫做陷阱标志。该标志可用于程序调试。TF标志没有专门的指令来设置或清楚。 (1)如果TF=1,则CPU处于单步执行指令的工作方式,此时每执行完一条指令,就显示CPU内各个寄存器的当前值及CPU将要执行的下一条指令。 (2)如果TF=0,则处于连续工作模式。 总的来说:TF=1时,每条指令执行完后产生陷井,TF=0时,CPU正常工作不产生陷井。 |
标志 | 1 | 0 |
---|---|---|
OF | OV | NV |
DF | DN | UP |
IF | EI | DI |
SF | NG | PL |
ZF | ZR | NZ |
AF | AC | NA |
PF | PE | PO |
CF | CY | NC |
这是一个存放条件码标志、控制标志和系统标志的寄存器。
为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:
名称 | 全称 | 说明 |
---|---|---|
CS(Code Segment) | 代码段寄存器 | 代码段存放当前正在运行的程序 |
DS(Data Segment) | 数据段寄存器 | 数据段存放当前正在运行程序所有的数据,如果程序使用了串处理指令,则其操作数也会存放在数据段中。 处理串的时候,DS默认为源串。 |
SS(Stack Segment) | 堆栈段寄存器 | 定义了堆栈所在的区域。 |
ES(Extra Segment) | 附加段寄存器 | 这是一个辅助的数据区,也是串处理指令的目的操作数存放区。 处理串的时候,ES默认为目的串。 |
存储器是计算机的记忆部件,用来存放程序和数据。按所在的位置,存储器可以分成主存储器和辅助存储器。
主存储器存放当前正在执行的程序和使用的数据,CPU可以直接存取,它由半导体存储器芯片构成,其成本高,容量小,但速度快。
辅助存储器可用来长期保存大量程序和数据,CPU需要通过I/O接口访问,它由磁盘或光盘构成,其成本低,容量大,但速度较慢。
存储器被划分为若干个存储单元,每个存储单元有一个惟一的存储器地址,从0开始顺序编号,存储单元的地址是无符号数, n位二进制数共能表示2^n个存储单元的地址。
Name | 全称 | 说明 |
---|---|---|
DB | define byte | 定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1 |
BW | define word | 定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2 |
DD | define double(word) | 定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4 |
字地址:一个字存放到存储器要占用连续的两个字节单元。字的低字节(低8位)存放在低地址中,高字节(高8位)存放在高地址中,字单元的地址用低地址表示。
例如:34560H的字单元的内容是1234H,而地址为78780H时字单元的内容是3332H。记作:
(78780H)= 3332H
同一个地址既可以看成字节单元,也可以看作是字单元、双字单元、或者是4字单元的地址,这要根据使用情况而定。字单元地址可以是偶数,也可以是奇数。但是,在8086和80286中,访问存储器(要求取数或者存数)都是以字为单元进行的,也就是说,机器是以偶地址访问存储器的。这样,对于奇地址的字单元,要取一个字需要访问二次存储器,当然,这样做需要花费较多的时间。
存储器有这样一个特性:它的内容取之不尽的。也就是说,从某个单元取出其内容后,该单元仍然保持着原来的内容不变,可以重复取出,只有存入新的信息后,原来保存的内容就自动丢失了。
从读写属性上看分为两类:
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器;每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间;CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
8086CPU的地址线是20位的,这样最大可寻址空间应为220=1MB,其物理地址范围从00000H~FFFFFH。
而8086CPU寄存器都是16位的,仅能表示地址范围 0000H ~ FFFFH(64KB) 。那么,这1MB(2^20)空间如何用16位寄存器表达呢?
根据要求可把1M字节地址空间划成若干逻辑段。每个逻辑段必须满足两个条件:
(1)逻辑段的起始地址(简称段首址)必须是16的倍数;
(2)逻辑段的最大长度为64K(2^6)。
按照这两个条件,1M字节地址空间最多可划分成64K个逻辑段,最少也要划分成16个逻辑段。逻辑段与逻辑段可以相连,也可以不连,还可以重叠。
就是存储器的实际地址,它是指CPU和存储器进行数据交换时所使用的地址(20位)。
是在程序中使用的地址(16位) ,它由段地址和偏移地址两部分组成。逻辑地址的表示形式为“段地址∶偏移地址”。
错误认识:
内存被划分成了一个一个的段,每一个段有一个段地址。
正确认识:
内存并没有分段,段的划分来自于CPU,由于8086CPU用“(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
MOV AX,BX ;将BX寄存器中的16位数送到AX寄存器。
MOV AX,BL ;这句话是错误的
DEC CX ;将计数器CX的内容减1 。
定义:寻址方式是指寻找指令中操作数所在地址的方法。
常用的寻址方式有:立即寻址、直接寻址、寄存器寻址、寄存器间接寻址、变址寻址、基址加变址、隐含寻址等。
寻找指令中所需要操作数存放地址的方式或者程序转移时寻找转移地址的方式称为寻址方式,因而寻址方式有两大类:一类是数据寻址方式,另一类是转移地址寻址方式。
由于80x86指令涉及四种操作数:立即操作数(data)、寄存器操作数(reg)、存储器操作数(mem)和隐含操作数,因此,数据寻址方式又可对应四种寻址方式,即:立即寻址、寄存器寻址、存储器寻址和固定寻址。
操作数直接包含在指令中,它紧跟在指令操作码后面,它作为指令存放在存储器代码段中,这种操作数称为立即数。立即数可以是8位,也可以是16位。
MOV AX,1234H ;(AX)=1234H
立即寻址方式用来表示常数,它常用于给寄存器或内存单元赋初值。需要强调的是,立即寻址只能用于源操作数,不能用于目的操作数,且源操作数的长度应该与目的操作数的长度一致。
立即数可以为8位,也可以是16位;规定立即数只能是整数,不能是小数、变量或者其他类型的数据。
操作数直接存放在由指令指明的寄存器中。在汇编指令中直接书写寄存器名,16位寄存器操作数可以是AX、BX、CX、DX、SI、DI、BP、SP、DS、ES、SS、CS等;8位寄存器操作数只能是AH、AL、BH、BL、CH、CL、DH、DL。
指令指针寄存器IP和标志寄存器FLAGS一般不直接出现在程序中。
此寻址方式由于存取操作数直接从CPU内部寄存器中获得,不需访问存储器,因而指令执行的速度快。
寄存器寻址既可用于源操作数,又可用于目的操作数,应用频率最高。
注意:CS不能作为目的操作数,因为CS:IP控制着程序指令序列的执行顺序,不能在程序中由指令随意改变。
存储器寻址方式的操作数都是存放在除代码段以外的存储区中,一般是数据段、附加段、堆栈段中的存储单元。指令中给出的是存储单元的地址或产生存储单元地址的表达式。
在汇编语言源程序中,存储单元地址是采用逻辑地址的形式表示的,即:段首址:段内偏移地址。段首址存放在某个段寄存器中,段内偏移地址即有效地址EA是由3个地址分量的某种组合求得,这3个地址分量是:位移量、基址(BX,BP)、变址(SI,DI) 。
这3个地址分量的不同组合,使形成有效地址EA的方法不同,相应有以下5种不同的存储器操作数寻址方式:直接寻址、寄存器间接寻址、寄存器相对寻址、基址变址寻址、相对基址变址寻址。
直接寻址是最简单的存储器寻址,操作数的有效地址EA由指令直接给出,只包含位移量。它主要用于存取简单变量。
MOV AX, [2000H]
EA=2000H, 假设(DS)=3000H, 那么PA=32000H
对使用直接寻址方式需说明以下几点:
MOV AX, ES: [2000H]
VALUE DB 10
MOV AH, VALUE
MOV AX, VALUE
MOV AX, WORD PTR VALUE
直接寻址方式适合于处理存储器的单个存储单元。例如,要处理某个存放在存储器里面的变量,可以使用直接寻址方式把变量先取到一个寄存器中,然后在进一步处理。
80x86中,为了使指令字不要太长,规定双操作数指令的两个操作数中,只能有一个使用存储器寻址方式,这就是一个变量常常先要送到寄存器的原因了。
操作数的有效地址只包含基址寄存器或者变址寄存器内容的一种成分。因此,有效地址就在某个寄存器中,而操作数则在存储器中。
操作数的有效地址EA存放在SI、DI、BX或BP之一中,而操作数在存储器中。若用BX、SI或DI间接寻址时,则操作数默认在数据段中,用DS的内容作为段首址,操作数的物理地址为:
若指令中使用BP间接寻址时,则用堆栈段SS的内容作为段首址,操作数的物理地址为: PA=(SS)×16 +(BP)。
MOV AX, [BX] ;PA = 16d x (DS) + (BX)
MOV AX, ES:[BX] ;PA = 16d x (ES) + (BX),在这里,ES称为段跨越前缀
MOV AX, [BP] ;PA = 16d x (SS) + (BP)
注意:
MOV AX, [CX]
就是错误的。MOV DL, [ BX ] ; [BX]指示一个字节单元
MOV DX, [ BX ] ; [BX]指示一个字单元
寄存器相对寻址方式也可以称为直接变址寻址方式。
操作数的有效地址EA是指令中指定的基址寄存器或变址寄存器的内容与指令中给出的位移量之和,即
操作数的物理地址为:
若操作数不在默认段中,则应使用段跨越前缀明确指定。
MOV AX, COUNT[SI]
MOV AX, [COUNT+SI]
操作数的有效地址EA是指令中的基址寄存器的内容+变址寄存器的内容。
指令格式:
MOV AX, [BX][DI]
等价于
MOV AX, [BX+DI]
MOV AX, ES:[BX][SI]
操作数的有效地址EA是指令中的基址寄存器的内容、变址寄存器的内容、位移量三个地址分量之和,即:
相对基址加变址寻址方式有多种等价的书写方式,书写格式:[BX+SI+1000H]、1000H[BX+SI]、1000H[BX][SI]和1000H[SI][BX]
等格式都是正确的,并且其寻址含义也是一致的,但格式:BX[1000H+SI]、SI[1000H+BX]
等是错误的,即所用寄存器不能在”[“,”]”之外,该限制对寄存器相对寻址方式的书写也同样起作用。
MOV AX, MASK [BX] [SI]
或
MOV AX, MASK [BX+SI]
或
MOV AX, [MASK+BX+SI]
这种寻址方式通常用于对二维数组的寻址。例如,存储器中存放着由多个记录组成的文件,则位移量可指向文件之首,基址寄存器指向某个记录,变址寄存器则指向该记录中的一个元素。这种寻址方式也为堆栈处理提供了方便,一般(BP)可指向栈顶,从栈顶到数组的首地址可以使用位移量来表示,变址寄存器可以用来访问数组中的某个元素。
可以出现在[]
中的内容是有限制的,他们必须是下列中的一个(其中idata表示一个立即数):
idata
BX
BP
SI
DI
BX+SI
BX+DI
BP+SI
BP+DI
BX+idata
BP+idata
SI+idata
DI+idata
BX+SI+idata
BX+DI+idata
BP+SI+idata
BP+DI+idata
其中,使用到BP的默认段寄存器为SS,其他未DS
伪操作 | 说明 |
---|---|
.8086 | 选择8086指令系统 |
.286 | 选择80286指令系统 |
.286 P | 选择保护模式下的80286指令系统 |
.386 | 选择80386指令系统 |
.386 P | 选择保护模式下的80386指令系统 |
.486 | 选择80486指令系统 |
.486 P | 选择保护模式下的80486指令系统 |
.586 | 选择80586指令系统 |
.586 P | 选择保护模式下的80586指令系统 |
这类伪操作一般都是放在整个程序的最前面。如果不给出,则汇编程序一般认为其默认值为.8086。
在段定义时,如果定位类型用户未选择,就表示是隐含类型,其隐含类型是PARA。
PARA属定位类型,是对该段起始地址定位。一般,各个逻辑段的首地址在‘节’的整数边界上(每16个存储单元叫做一节),即每个逻辑段的起始地址是16的整数倍。对于PARA—指定定位段的起始地址必须在节的整数边界。
存储器的物理地址是由段地址和偏移地址组合而成的,汇编程序在把源程序转换为目标程序的时候,必须确定标号和变量(代码段和数据段的符号地址)的偏移地址,并且需要把有关信息通过目标模块传送给连接程序,以便连接程序把不同的段和模块连接起来,形成一个可执行的程序。
明确段与寄存器的关系:assume cs:code, ds:data, es:extra
data segment ; 定义数据段
…
data ends
;----------------------------------------
extra segment ; 定义附加段
…
extra ends
;----------------------------------------
code segment ; 定义代码段,其中的段寄存器名必须是CS、ES、DS、SS(对于386及其后继机型还有FS和GS)中的一个。
assume cs:code, ds:data, es:extra
start: ;----------------------程序开始的标号
mov ax, data
mov ds, ax ; 段地址 -> 段寄存器
…
mov ax,4c00h
int 21h
code ends
end start ;------------程序结束标志
说明:
assume
只是说明关联关系,并没有对段寄存器赋值,除了CS(装入程序时由CPU给出),其他段寄存器要在程序中设置。mov ax,4c00h
和int 21h
最后两条指令所实现的功能是程序返回。END [label]
,标号label 指示程序开始执行的起始地址。伪操作是汇编程序对源程序进行汇编时处理的操作,完成处理器选择、存储模式定义、数据定义、存储器分配、指示程序开始结束等功能。
格式:[变量] 助记符 操作数 [ , 操作数 , … ] [ ; 注释]
DATA_BYTE DB 10,4,10H,?
DATA_WORD DW 100,100H,-5,?
ARRAY DB ‘HELLO’
DB ‘AB’
DW ‘AB’
PAR1 DW 100,200
PAR2 DW 300,400
ADDR_TABLE DW PAR1,PAR2
repeat_count DUP(operand,……,operand)
VAR DB 100 DUP (?) ; 这里表示申请100个字单元大小的内存空间,但是不进行初始化
DB 2 DUP (0,2 DUP(1,2),3) ; 把0,1,2,1,2,3按字单元存放2次。
OPER1 DB ?, ?
OPER2 DW ?, ?
……
MOV OPER1, 0 ;字节指令
MOV OPER2, 0 ;字指令
OPER1 DB 1, 2
OPER2 DW 1234H, 5678H
……
MOV AX, OPER1+1 ×
MOV AL, OPER2 × 类型不匹配
MOV AX, WORD PTR OPER1+1
MOV AL, BYTE PTR OPER2
(AX)=3402H (AL)=34H
变量名 LABEL 类型
or 标号 LABEL 类型
其中变量的数据类型可以是BYTE,WORD,DWORD,标号的代码类型可以是NEAR或FAR。
数据定义及存储器分配伪指令格式中的 “变量 “是操作数的符号地址,它是可有可无 的,它的作用与指令语句前的标号相同,区别是变量后面不加冒号。如果语句中有变量,那么汇编程序将操作数的第一个字节的偏移地址赋于这个变量。
BYTE_ARRAY LABEL BYTE
WORD_ARRAY DW 50 DUP (?)
tos LABEL WORD
说明:有时程序中多次出现同一个表达式,为了方便起见,可以使用赋值伪操作给表达式赋予一个名字。、
EQU是赋值伪指令。赋值语句仅在汇编源程序时,作为替代符号用。不产生目标代码,也不占有存储单元。
此后,程序中凡是用到该表达式之处,都可以用表达式名来代替了。可见,EQU的引入提高了程序的可读性,也使其更加易于修改。
格式:表达式名 EQU 表达式
功能:给表达式赋予一个名字,在程序中用表达式名代替该表达式。
ALPHA EQU 9
BETA EQU ALPHA+18
BB EQU [BP+8]
“ = ” 伪操作 (允许重复定义)
……
EMP = 7
……
EMP = EMP+1
……
说明:EQU指令类似于C语言的#define宏,在编译前被转化。
地址计数器 $:保存当前正在汇编的指令的偏移地址
JNE $+6 ; 转向地址是 JNE 的首址 +6
JMP $+2 ; 转向下一条指令
ARRAY DW 1, 2 , $+4 , 3 , 4 , $+4
ORG 伪操作:用来设置当前地址计数器的值。即:指定一个地址,后面的程序或数据从这个地址值开始分配。
SEG1 SEGMENT
ORG 10
VAR1 DW 1234H
ORG 20
VAR2 DW 5678H
ORG $+8; 当前地址加8,
VAR3 DW 1357H
SEG1 ENDS
EVEN伪操作使下一个变量或者指令开始于偶数字节地址。一个字的地址最好从偶地址开始,所以对于字数组为了保证其从偶地址开始,可以在其前面使用EVEN伪操作来达到这一目的。
EVEN ;使下一地址从偶地址开始
A DB ‘morning’
EVEN ;使下一地址从偶地址开始
B DW 2 DUP (?)
ALIGN伪操作为保证双字数组边界从4的倍数开始创造了条件:
ALIGN 4 ; 保证下一个地址是4的倍数
ALIGN 2 ; 与EVEN等价
说明:
汇编程序默认的数是十进制数,因而除非专门指定,汇编程序把程序中出现的数均看作为十进制数。为此,当使用其他基数的时候,需要专门给以标记:
基数 | 标记 |
---|---|
二进制 | B |
十进制 | D |
十六进制 | H |
八进制 | O 或者 Q |
. RADIX 表达式 ; 规定无标记数的基数
MOV BX, 0FFH
MOV BX, 178
.RADIX 16
MOV BX, 0FF ;这里没有标记(不是0FFH),但是因为前面定义了`.RADIX 16`,所以这里仍然表示16进制的数。
MOV BX, 178D
汇编语言源程序中每个语句可以由四项组成,格式如下:
[name] operation operand [; comment]
其中
操作数项由一个或者多个表达式组成,多个操作数项之间一般用逗号分开。
操作数项可以是常数,寄存器,标号,变量或者由表达式组成。表达式是常数,寄存器,标号,变量与一些操作符相结合的序列,可以有数字表达式和地址表达式两种。
算术操作符有+,-,*,/、MOD
。其中MOD是指除法运算后得到的余数。算术操作符可以用于数字或者地址表达式中,但当它用于地址表达式的时候,只有其结果有明确的物理意义的时候才是有效的结果。例如:两个地址相乘和相除是没有意义的。在地址表达式中,可以使用+或者-,但也必须注意其物理意义,例如把两个不同段的地址相加也是没有意义的。经常使用的是地址±数字量,它是有意义的,例如,SUM+1是指SUM字节单元的下一个字节单元的地址(注意:不是指SUM单元的内容加1),而SUM-1则是指SUM字节单元的前一个字节单元的地址。
逻辑操作符有AND,OR,XOR和NOT;移位操作符有SHL和SHR。他们都是按位操作的,只能用于数字表达式中。
名称 | 全称 | 说明 |
---|---|---|
EQ | Equal | 相等 |
NE | Not equal | 不想等 |
LT | Less than | 小于 |
GT | Great than | 大于 |
LE | Less and equal | 小于或等于 |
GE | Great and equal | 大于或等于 |
说明:
它主要有的TYPE,LENGTH,SIZE,OFFEST,SEG等等。这些操作符把一些特征或者存储器地址的一部分作为数值回送。
格式为:TYPE expression
如果该表达式是变量,则汇编程序将回送变量的以字节数表示的类型:DB=1,DW=2,DD=4,DF=6,DQ=8,DT=10。如果表达式是标号,则汇编程序将回送代表该标号类型的数值:NEAR=-1,FAR=-2。如果表达式是常数,则应回送0。
DR2 DW 10H DUP(0,2 DUP(2))
,MOV CL,LENGTH DR2
CL的值为10HDR1 DB 10H,30H
,MOV BL,LENGTH DR1
BL的值为1。汇编调试程序Debug使用
使用DEBUG调试和运行可执行文件
说明:NOP是英语“No Operation”的缩写。NOP无操作数,所以称为“空操作”。
执行NOP指令只使程序计数器PC加1,所以占用一个机器周期。
数据传送指令:MOV、XCHG、LEA、LDS、LES、PUSH、POP、PUSHF、POPF、CBW、CWD、CWDE。
8086CPU的入栈和出栈操作都是以字为单位进行的。
PUSH指令每次只能压入一个字(16位)。
push ax
(1)SP = SP–2;
(2)将AX中的内容送入SS:SP指向的内存单元处。
pop ax
(1)将SS:SP指向的内存单元处的数据送入ax中;
(2)SP = SP+2。
说明:
指令写法:MOV DST,SRC
执行操作:(DST)<-(SRC)
其中DST表示目的操作数,SRC表示源操作数。可以在CPU内部或者在存储器之间传送数据。
说明:
指令格式: XCHG OPR1, OPR2
功能: 将操作数地址中的内容互换。
执行操作: (OPR1) <-> (OPR2)
注意:
功能:将表格中的一个字节内容送到AL累加器中。常用于将一种代码转换为另一种代码。
这条指令根据AL寄存器提供的位移量,将BX指使的字节表格中的代码换存在AL中。(AL)<-((DS)*16+(BX)+(AL))
说明:本指令并不影响标志位。
格式:
XLAT TABLE ;TABLE为表格的起始地址)
XLAT ;(AL)<-((BX)+(AL))
执行指令前要将TABLE先送入BX,将待查字节与在表格中距离表首地址位移量送AL。
指令 | 全称 | 说明 |
---|---|---|
LEA | load effective address | 有效地址送寄存器 |
LDS | load DS with pointer | 指针送寄存器和DS |
LES | load ES with pointer | 指针送寄存器和ES |
LFS | load FS with pointer | 指针送寄存器和FS |
LGS | load GS with pointer | 指针送寄存器和GS |
LSS | load SS with pointer | 指针送寄存器和SS |
功能:有效地址送寄存器。
全称:load effective address
LEA BX,LIST ;------取LIST的偏移地址送BX
MOV BX, OFFSET LIST ;------功能与LEA相同
LEA BX, [BX+SI] ;------取基址变址寻址的有效地址给BX
MOV BX, OFFSET [BX+SI] ;
——× 注意:OFFSET只能与简单的符号地址相连。
算术运算指令会根据运算结果影响状态标志,主要影响6个标志位:ZF、CF、AF、SF、OF和PF。
指令 | 使用 | 说明 |
---|---|---|
ADD | ADD DST,SRC | 功能:加法,将SRC和DST的值相加之后存放在DST中。 执行操作: (DST) <- (SRC) + (DST) |
ADC | ADC DST, SRC | 功能:带进位的加法指令,将SRC和DST的值和进位标志位(CF)相加之后存放在DST中。 执行操作:(DST) <- (SRC) + (DST) + CF |
INC | INC OPR | 功能:加一指令。 执行操作:(OPR) <- (OPR) + 1 |
说明:加法指令都会影响条件标志位,但INC指令不影响CF标志。
INC影响的条件标志位包括:SF,ZF,OF,AF,PF。
在执行 adc 指令的时候加上的 CF 的值是由 adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。
下面的指令和add ax , bx具有相同的结果:
add al,bl
adc ah,bh
看来CPU提供 ADC指令的目的,就是来进行加法的第二步运算的。ADC指令和ADD指令相配合就可以对更大的数据进行加法运算。
注意:有符号的双精度数的溢出,应根据ADC指令的OF位判断,而作低位加法用的ADD指令的溢出是无意义的。
用16位寄存器编写程序:
MOV AX, word ptr d1 ;由于d1是双字类型,必须使用强制类型说明符。
MOV DX, word ptr d1+2 ;(DX,AX)构成一个32位数据
ADD AX, word ptr d2 ;低字相加,有可能会产生“进位”
ADC DX, word ptr d2+2 ;高字相加。
MOV word ptr d1, AX ;低字送给d1的低字
MOV word ptr d1+2, DX ;高字送给d1的高字
说明:
OF位可以用来表示带符号数的溢出,CF位可以用来表示无符号数的溢出。
条件标志(或者称呼为)位中最主要的是CF,ZF,SF,OF四位,分别表示了进位、结果为零,符号和溢出的情况。
执行加法指令的时候,CF位是根据最高有效位是否向最高位的进位来设置的。有进位的时候CF=1,无进位的时候CF=0。
OF位则根据操作数的符号及其变化情况来设置的:若两个操作数的符号相同,而结果的符号与之相反则OF=1,否则OF=0。
溢出位OF既然试试根据数的符号及其变化来设置的,当然它是用来表示带符号数的溢出的,从其设置条件来看结论也是明显的。
CF位可以用来表示无符号数的溢出。一方面,由于无符号数的最高有效位只有数值意义而无符号意义,所以该位产生的进位应该是结果的实际的进位值,但是在有限数位的范围内就说明了结果的溢出情况;另一方面,它所保存的进位值有时候还是有用的。例如。双字长数运算的时候,可以利用进位值把低位字的进位计入高位字中。
指令 | 使用 | 说明 |
---|---|---|
SUB | SUB DST,SRC | 功能:减法,将SRC和DST的值相减之后存放在DST中。 执行操作: (DST) <- (SRC) + (DST) |
SBB | SUB DST, SRC | 功能:带借位减法指令,将SRC和DST的值和进位标志位(CF)相加之后存放在DST中。 执行操作:(DST) <- (SRC) + (DST) - CF CF为进位位的值。 |
DEC | DEC OPR | 功能:加一指令。 执行操作:(OPR) <- (OPR) + 1 |
NEG | NEG OPR | 功能:加一指令。 执行操作:(OPR) <- (OPR) + 1 把操作数按位求反后末尾加1。 |
CMP | CMP OPR1, OPR2 | 功能:加一指令。 执行操作: (OPR1) - (OPR2) 执行减法操作,不保存结果。往往根据比较发生转移。 |
说明:
减法运算的条件码情况和加法类似。CF位说明无符号数相减的溢出,同时它又确实是被减数的最高有效位向高位的借位值。OF位则说明带符号数的溢出。
减法的CF值反映了无符号数运算中的借位情况,因此当作为无符号运算时,若减数>被减数,此时有借位,则CF=1;否则CF=0。或者,也可以简单地用二进制减法的运算中的最高有效位向高位的进位的情况来判别:有进位的时候CF=0,没有进位的时候CF=1。
减法的OF位的设置方法为:若两个数的符号相反,而结果的符号与减数相同则相同,则OF=1;除了上述情况外OF=0。OF=1说明带符号数的减法溢出,结果是错误的。
NEG指令的条件码按照求补后的结果设置,只有当操作数为0的时候,求补运算的结果使得CF=0,其他情况都为CF=1。所以,只有当字运算时对-128求补的时候,以及字运算的时候对-32768求补以及双字运算的时候对-2^31求补的情况下OF=1,其他则均为OF=0。
指令 | 使用 | 说明 |
---|---|---|
MUL | 无符号数乘法指令 | MUL SRC |
IMUL | 带符号数乘法指令 | IMUL SRC |
类型 | 说明 |
---|---|
字节操作数 | (AX) <- (AL) * (SRC) |
字操作数 | (DX, AX) <- (AX) * (SRC) |
注意:
指令 | 使用 | 说明 |
---|---|---|
DIV | 无符号数除法指令 | DIV SRC |
IDIV | 带符号数除法指令 | IDIV SRC |
类型 | 说明 |
---|---|
字节操作数 | (AL) <- (AX) / (SRC) 的商 (AH) <- (AX) / (SRC) 的余数 |
字操作数 | (AX) <- (DX, AX) / (SRC) 的商 (DX) <- (DX, AX) / (SRC) 的余数 |
注意:
指令 | 全称 | 说明 |
---|---|---|
LAHF | load AH with flags | 将标志寄存器中的低八位送到AH中。 |
SAHF | store AH into flags | 将AH寄存器的相应位传送到标志寄存器的低8位。 |
PUSHF/PUSHFD | push the flags or eflags | 标志进栈 |
PUPF/POPFD | pop the flags or eflags | 标志出栈 |
注意:
这组指令中的LAHF和PUSHF/PUSHFD都不影响标志位。SAHF和POPF/POPFD则由装入的值来确定标志位的值,但是POPFD指令不影响VM,RF,IOPF,VIF和VIP的值。
STC----是置进位标志指令,执行的结果是将进位标志CF置1
CLC----是清进位标志指令,其执行结果是置CF标志为0
指令 | 使用 | 说明 |
---|---|---|
CBW | 字节转换为字 | AL符号扩展到AH中,形成AX中的字。即如果(AL)中的最高有效位是0,则(AH)=0;如(AL)的最高有效位为1,则(AH)=0FFH。 执行操作: 若(AL)的最高有效位为0,则(AH)= 00H 若(AL)的最高有效位为1,则(AH)= FFH |
CWD/CWDE | 字转换为双字 | AX符号扩展 -> (DX,AX)双字 执行操作: 若(AX)的最高有效位为0,则(DX)= 0000H 若(AX)的最高有效位为1,则(DX)= FFFFH |
CWQ | 双字转换为4字 | EAX的内容符号扩展到EDX,形成EDX:EAX中的4字。 |
BSWAP | 字节交换指令 | 格式:BASWAP r32。 该指令只能用于486及其后继机型。r32指32位寄存器。 执行的操作:使指令指定的32位寄存器的字节次序变反。具体的操作为:1、4字节互换,2 |
注意:
指令 | 全称 | 说明 |
---|---|---|
AND | and | 逻辑与 格式:AND DST,SRC |
OR | or | 逻辑或 格式:OR DST,SRC |
NOT | not | 逻辑非 格式:NOT DST,SRC |
XOR | exclusive or | 异或 格式:XOR DST,SRC |
TEST | test | 作用:测试 格式:TEST OPR1,OPR2 执行的操作:(OPR1)∧(OPR2) 说明:两个操作数相与的结果不保存,只是根据其特征置条件码。 |
注意:
要求屏蔽0、1两位,可以用AND指令并设置常数0FCH。
MOV AL,0BFH
AND AL,0FCH
这两条指令的运行结果使得(AL)=0BCH。因此,使用AND指令可以使得操作数的某些位被屏蔽。只需要把AND指令的源操作数设置成一个立即数,并把需要屏蔽的位设置为0,这样指令执行的结果就可以把操作数的相应位置0,其他位保持不变。
要求第5位置1,可以使用OR指令。
MOV AL,43H
MOV AL,20H
这两条指令执行了之后,(AL)=63 H。因此,用OR指令可以使得操作数的某些位置1,其他位保持不变。只需要把OR指令的源操作数设置为一个立即数,并把需要置1的位设置为1,就可以达到目的了。
要测试操作数的某些位是否为0,可以使用TEST指令,同样把TEST指令的源操作数设置成一个立即数,其中需要测试的位应该设置为1。
MOV AL,40H
TEST AL,0AFH
这里要求测试第0,1,2,3,5,7位是否为0,根据测试的结果设置条件码为CF=OF=0,SF=0,ZF=1,说明了所需要测试的位均为0。如果在这两条指令之后跟一条件转移指JNZ,结果如果不是0就转移,结果如果是0就顺序往下执行,这样就可以根据测试的情况产生不同的程序分支,转向不同的处理方案了。
要测试操作数的某位是否为1,可以先把该操作数求反,然后使用TEST指令测试。如要测试AL寄存器中第2位是否为1,若为1则转移到EXIT中去执行,可以用下列指令序列:
MOV DL,AL
NOT DL
TEST DL,0000 0100B
JE EXIT
如AL寄存器的内容为0FH,为了避免破坏操作数的原始内容,把它复制到了DL中去测试,执行完TEST指令之后,结果为全0而有ZF=1,说明操作数的第2位为1引起的转移到EXIT去执行。
要是操作数的某些位变反,可以使用XOR指令,只要把源操作数的立即数字段的相应位置设置为1就可以达到目的。如果求第0,1位变反,可以使用下面的指令:
MOV AL,11H
XOR AL,3
则指令执行后,(AL)=12H,达到了第0,1位变反而其他位不变的目的。
XOR指令还可以用来测试某一个操作数是否与另外一个确定的操作数相等。这种操作在检查地址是否匹配的时候是经常使用的。
XOR AX,042EH
JZ MATCH
这两条指令是用来检查AX的内容是否等于042EH,若相等则转移到MATCH去执行匹配的情况需要做的工作,否则执行JZ下面的程序。
386及其后继机型增加了本组指令。
386及其后继机型增加了本组指令。
指令 | 全称 | 说明 |
---|---|---|
SHL | shift logical left | 逻辑左移 |
SAL | shift arithmetic left | 算术左移 |
SHR | shift logical right | 逻辑右移 |
SAR | shift arithmetic right | 算术右移 |
ROL | rotat left | 循环左移 |
ROR | rotat right | 循环右移 |
RCL | rotate left through carry | 带进位循环左移 |
RCR | rotate right through carry | 带进位循环右移 |
SHLD | shift left double | 双精度左移 |
SHRD | shift right double | 双精度右移 |
注意:
SHL OPR, 1
MOV CL, CNT
,SHL OPR, CL
移位指令 | 移位填充方式 |
---|---|
逻辑左移 | 右边统一添0,移出来的那一位放进CF |
算术左移 | 右边统一添0,移出来的那一位放进CF |
逻辑右移 | 左边统一添0,移出来的那一位放进CF |
算术右移 | 左边添加符号位上的数,移出来的那一位放进CF |
示例:1010101010
,其中[]
是添加的位
逻辑左移一位:010101010[0]
算术左移一位:010101010[0]
逻辑右移一位:[0]101010101
算术右移一位:[1]101010101
一般情况下指令都是顺序逐条执行的,但是实际上程序不可能全部顺序执行而经常需要改变程序的执行流程。
注意:
注意:只能使用段内直接寻址的8 位位移量。
1)根据单个条件标志的设置情况转移:
格式 | 全称 | 转移条件 | 说明 |
---|---|---|---|
JZ(JE) OPR | jump if zero,or equal | ZF = 1 | 结果为0(相等)则转移 |
JNZ(JNE) OPR | jump if not zero, or not equal | ZF = 0 | 不为0(不相等)转移 |
JS OPR | jump if sign | SF = 1 | 为负转移 |
JNS OPR | jump if not sign | SF = 0 | 为正转移 |
JO OPR | jump if overflow | OF = 1 | 溢出转移 |
JNO OPR | jump if not overflow | OF = 0 | 不溢出转移 |
JP OPR | jump if parity, or parity even | PF = 1 | 有偶数个1则转移 |
JNP OPR | jump if not parity, or parity odd | PF = 0 | 有奇数个1 则转移 |
JC OPR | jump if carry | CF = 1 | 有进位转移 |
JNC OPR | jump if not cary | CF = 0 | 无进位转移 |
2)比较两个无符号数,并根据比较结果转移的指令(与比较指令CMP 联用)
符号表示 | 格式 | 全称 | 转移条件 | 说明 |
---|---|---|---|---|
< | JB (JNAE,JC) OPR | jump if below | CF = 1 | 有借位,被减数小于减数则转移 |
≥ | JNB (JAE,JNC) OPR | jump if not below | CF = 0 | 没有借位, 被减数大于或等于减数则转移 |
≤ | JNA (JBE) OPR | jump if not above, jump if below of equal |
CF∨ZF = 1 | 被减数小于或等于减数则转移 |
> | JA (JNBE) OPR | jump if above, jump if not below or not equal |
CF ∨ ZF = 0 | 被减数大于减数则转移 |
说明:
cmp指令对有符号数的比较:cmp ah, bh
3)比较两个带符号数,并根据比较结果转移的指令
符号表示 | 格式 | 全称 | 转移条件 | 说明 |
---|---|---|---|---|
< | JL (JNGE) OPR | jump if less | SF∀OF = 1 | 小于,或者不大于或等于则转移。 |
≥ | JNL (JGE) OPR | jump if not less | SF∀OF = 0 | 不小于,或者大于或等于则转移。 |
≤ | JNG (JLE ) OPR | jump if not greater | (SF∀OF)∨ZF = 1 | 不大于或者小于或等于则转移。 |
> | JG (JNLE) OPR | jump if greater | (SF∀OF)∨ZF = 0 | 大于或者不小于或等于则转移。 |
说明:
适用于带符号数的比较
4)测试 CX 的值为 0 则转移的指令
格式 | 全称 | 测试条件 | 说明 |
---|---|---|---|
JCXZ OPR | jump if CX register is zero | (CX)=0 | CX寄存器的内容为零则转移。 |
// 计算0+1+2+3+4+5+6+7+8+9的值
int sum=0;
for(int i=0;i<10;i++)
sum=sum+i;
相当于:
// 计算0+1+2+3+4+5+6+7+8+9的值
mov ax,0; ax相当于sum
mov bx,0; bx相当于i
mov cx,10
;标号s代表一个地址
s: add ax,bx
inc bx
loop s; 判断
指令的格式是:loop 标号
CPU 执行loop指令的时候,要进行两步操作:
① (cx)=(cx)-1;
② 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行,退出循环。
可见,cx中的值影响着loop指令的执行结果。用loop指令来实现循环功能时,cx 中要存放循环次数。
一条循环指令LOOP AGAIN
可以用修改循环计数和判断转移条件的两条指令替代DEC CX
,JNZ AGAIN
。JNZ(或JNE)结果不为零(或不相等)则转移,测试条件为ZF=0。
我们可以总结出用cx和loop 指令相配合实现循环功能的三个要点:
MOV CX,0
S:
ADD AX,BX
LOOP S
以上指令序列执行后ADD AX,BX指令被执行了多少次?
答案是:65536次。
循环 LOOP (loop)
指令的汇编格式:LOOP label
指令的基本功能:
①(CX)←(CX)-1
② 若(CX)≠0
,则(IP)←(IP)当前+位移量
,否则循环结束。
指令的特殊要求:循环指令都是短转移格式的指令,也就是说,位移量是用8位带符号数来表示的,转向地址在相对于当前IP值的-128 ~ +127字节范围之内。
解析:LOOP指令是先执行CX自减的功能,之后才进行循环的。只要CX不为0,循环就不会终止。因此在上面中,第一次执行的时候,CX自减为0FFFFH,这时CX就不为0的,循环不会被终止。由此,我们可以算出总共运行了65536次。
功能 | 格式 | 测试条件 |
---|---|---|
当为0或相等时循环 | LOOPZ / LOOPE 标号 |
ZF=1且(CX)≠0 |
不为0或不相等循环 | LOOPNZ / LOOPNE 标号 |
ZF=0且(CX)≠ 0 |
执行步骤:
(CX) ← (CX) - 1
;注意:
CMP
联合使用可提前退出循环。串处理指令 | 指令 | 串重复前缀 | 设置方向标志指令 |
---|---|---|---|
串传送 | MOVSB / MOVSW | REP | CLD |
存入串 | STOSB / STOSW | REPE / REPZ | STD |
从串取 | LODSB / LODSW | REPNE / REPNZ | |
串比较 | CMPSB / CMPSW | ||
串扫描 | SCASB / SCASW | ||
串输入 | INSB / INSW | ||
串输出 | OUTSB / OUTSW |
说明:
字符串操作指令的实质是对一片连续存储单元进行处理,这片存储单元是由隐含指针DS:SI或ES:DI来指定的。
与 REP
配合工作的MOVS / STOS / LODS / INS / OUTS
。
REP
重复串操作直到计数寄存器CX的内容为0为止。
执行操作:
MOVS 串传送指令:
MOVS DST, SRC
MOVSB ;(字节)
MOVSW ;(字)
MOVSD ;(双字,计数器为ECX,386及后继机型)
例:MOVS ES: BYTE PTR [DI], DS: [SI]
执行操作:
((DI)) ← ((SI))
(SI)←(SI)±1, (DI)←(DI)±1
,字操作: (SI)←(SI)±2, (DI)←(DI)±2
REP MOVS
:将数据段中的整串数据传送到附加段中。源串(数据段)→ 目的串(附加段)
执行 REP MOVS 之前,应先做好:
SI
DI
CX
CLD ( STD )
; -------- 定义数据段
data segment
mess1 db ‘personal_computer’
data ends
; -------- 定义附加段
extra segment
mess2 db 17 dup (?)
extra ends
code segment
assume cs:code,ds:data.es:extra
mov ax, data
mov ds,ax
mov ax, extra
mov es, ax
; ------ 总共5条指令。
lea si, mess1
lea di, mess2
mov cx, 17
cld
rep movsb
…
code ends
反向的指令:
lea si, mess1+16
lea di, mess2+16
mov cx, 17
std
rep movsb
为了在同一段内处理数据,可以在DS和ES中设置同样的地址。
data segment
mess1 db ‘personal_computer’
mess2 db 17 dup (?)
data ends
code segment
mov ax, data
mov ds, ax
mov es, ax
lea si, mess1
lea di, mess2
mov cx, 17
cld
rep movsb
…
code ends
STOS DST
STOSB (字节)
STOSW (字)
执行操作:
字节操作:((DI))←(AL), (DI)←(DI)±1
字操作:((DI))←(AX), (DI)←(DI)±2
例:把 附加段 中mess2开始的 10 个字节缓冲区全部置为 20H
lea di, mess2
mov al, 20H
mov cx, 10
cld
rep stosb
或者:
lea di, mess2
mov ax, 2020H
mov cx, 5
cld
rep stosw
LODS SRC
LODSB ;(字节)
LODSW ;(字)
执行操作:
注意:
INS DST, DX
INSB ; (字节)
INSW ;(字)
执行操作:
字节操作:((DI))←((DX)), (DI)←(DI)±1
字操作:((DI))←((DX)), (DI)←(DI)±2
功能:把端口号在DX的I/O空间的字节、字或双字传送到附加段中的由DI所指向的存储单元中,并根据DF的值和数据类型修改DI的内容。
; 从0F03H端口输入10个字符到mess1字节缓冲区
lea di, mess1
mov dx, 0F03H
mov cx, 10
cld
rep insb
OUTS DX , SRC
OUTSB ;(字节)
OUTSW ;(字)
执行操作:
字节操作:((DX))←((SI)), (SI)←(SI)±1
字操作:((DX))←((SI)), (SI)←(SI)±2
功能:把由SI所指向的存储单元中的字节、字或双字传送到端口号在DX的I/O端口中去,并根据DF的值和数据类型修改SI的内容。
; -------- 把mess1字节缓冲区中的10个字符从0F03H端口输出
lea si, mess1
mov dx, 0F03H
mov cx, 10
cld
rep outsb
与 REPE / REPZ(REPNE / REPNZ)配合工作的
CMPS 和 SCAS
指令 | 说明 |
---|---|
REPE / REPZ | 当相等 /为零时重复串操作 |
REPNE / REPNZ | 当不相等 /不为零时重复串操作 |
执行操作:
(CX)=0
或 ZF=0 (ZF=1)
则退出串操作,否则转2)(CX)←(CX) -1
CMPS / SCAS
CMPS SRC, DST
CMPSB ;(字节)
CMPSW ;(字)
执行操作:
((SI)) - ((DI))
根据比较结果置条件标志位:相等 ZF=1,不等 ZF=02) 字节操作:(SI)←(SI)±1, (DI)←(DI)±1
,字操作: (SI)←(SI)±2, (DI)←(DI)±2
汇编语言中,CMP和CMPS都是比较指令,不同主要有:
1、CMP比较指令是执行两个数的相减操作,包括有符号数。CMPS比较指令是执行两个字符串的相减操作,所有数据认为是无符号数。
2、CMP比较指令必须有两个显式操作数。CMPS比较指令可以有两个显式操作数,也可以使用指令CMPSB或CMPSW分别表示字节串比较或字串比较而隐含操作数。
3、使用CMP比较指令比较连续的数据时,必须由程序改变其中一个操作数。使用CMPS比较指令比较连续的字符时,对由SI寻址的源串中数据与由DI寻址的目的串中数据进行比较,执行完一条比较指令,SI,DI将自动调整.
例:比较两个字符串,找出它们不相匹配的位置。
lea si, mess1
lea di, mess2
mov cx, 5
cld
repe cmpsb
lea si, mess1+4
lea di, mess2+4
mov cx, 5
std
repe cmpsb
SCAS DST
SCASB (字节)
SCASW (字)
执行操作:
字节操作:(AL) - ((DI)), (DI)←(DI)±1
字操作:(AX) - ((DI)), (DI)←(DI)±2
例:从一个字符串中查找一个指定的字符
mess db ‘COMPUTER’
lea di, mess ; -------- (ES:DI)保留串地址
mov al, ‘T’ ; -------- 搜索字符
mov cx, 8 ; -------- 字符长度
cld
repne scasb ; -------- 这里在循环的判断是否(CX)=0或ZF=1。条件成立即中止
执行完后
int i=0;
int main(){
func();
return 0;
}
void func(){
i++;
}
等价于:
ASSUME CS:CODE
CODE SEGMENT
START:
MOV AX,0 ;AX相当于i
CALL FUNC ;调用FUNC子程序
MOV AH,4CH
INT 21H
FUNC PROC NEAR
INC AX ;i++
RET
FUNC ENDP
CODE ENDS
END START
子程序:在许多应用程序中,常常需要多次使用某功能的指令序列。这时,为了减少重复编写程序,节省内存空间,把这一功能的指令序列组成一个相对独立的程序段。在程序运行时,如果需要使用这个给定的功能,就转移到这个独立的程序段,待这个独立的程序段指令序列执行完后,又返回到原来位置继续运行程序。我们把这个相对独立的程序段就叫子程序或过程。
调用程序:编制程序时,按需要转向子程序,称为子程序调用,或称为过程调用。调用子程序的程序称为调用程序或主程序。主、子程序是相对而言的。但子程序一定是受调用程序或主程序调用的。子程序定义的位置可以放在主程序的前面或后面。
过程名 PROC NEAR or FAR
……
RET
过程名 ENDP
说明:
指令格式:CAL DST ;其中DST为过程的目标地址(即过程名)
。
指令功能:
CALL DST
DST给出子程序的入口地址(子程序为near属性),比如:CALL subp
执行操作:
PUSH IP
CALL DST
执行操作:
PUSH IP
CALL FAR PTR DST
DST给出子程序的入口地址(子程序为far属性),比如:CALL far ptr subp
执行操作:
PUSH CS
PUSH IP
CALL FAR PTR DST
DST给出存储单元的内容(转向地址),比如: CALL dword ptr [bx]
执行操作:
PUSH CS
PUSH IP
说明:属于无条件转移指令。可以在段内或段间返回。
RET
执行操作:
POP IP
RET EXP
执行操作:
POP IP
(SP)←(SP)+EXP
RET(F)
执行操作:
POP IP
POP CS
要保护的寄存器:应该是在子程序中将被使用,返回调用程序后仍然需要使用其原有内容的那些寄存器。即保护调用程序和子程序两者在使用上发生冲突的那些寄存器。但在编程时,一时很难弄清哪些是有冲突的寄存器,一种较为简单的方法是把所有的寄存器均加以保护。
一般在子程序中进行寄存器保护较好。即在子程序的开始部分,先进行相关寄存器(主要是在子程序中使用的各寄存器)的保护。然后再进行子程序的处理操作。在执行完子程序后,返回前,先恢复各寄存器内容后,再返回调用程序。
subt proc near
push ax
push bx
push cx
push dx
……
……
pop dx
pop cx
pop bx
pop ax
ret
subt endp
入口参数:子程序需要从主程序获取的参数。使子程序可以对不同数据进行相同功能的处理。
出口参数:是子程序返回给主程序的参数。使子程序可以将不同的结果送至主程序
实现的方法是把子程序所需要的入口参数,由调用程序预先放入指定的寄存器中。在进入子程序后,子程序就可直接对这些寄存器内容进行操作了。同样子程序的运行结果,也可置入寄存器中,把它们作为子程序的出口参数寄存器使用。由于寄存器数目有限,适用于参数较少的情况。
参数的传递方法并不是固定不变的,即它们是可以综合使用的。依实现的需要和情况的不同,可以灵活使用其中一种方式,也可以同时使用几种方式的混合。有的时候还可能并不需要参数传递。
; 十六进制到十进制的转换(通过寄存器传送参数)
; hexidec:接收键盘输入的十六进制数,在屏幕上输出相应的十进制数
hexidec segment ; 1610
assume cs: hexidec
main proc far
start:
push ds
sub ax, ax
push ax
call hexibin ; 16转2
call crlf
call binidec ; 2转10
call crlf
ret
main endp
……
……
……
hexidec ends
; 按位取数
binidec proc near
mov cx, 10000d
call dec_div
mov cx, 1000d
call dec_div
mov cx, 100d
call dec_div
mov cx, 10d
call dec_div
mov cx, 1d
call dec_div
ret
binidec endp
dec_div proc near
mov ax, bx
mov dx, 0
div cx
mov bx, dx
mov dl, al
add dl, 30h
mov ah, 2
int 21h
ret
dec_div endp
; hexibin:接收4位十六进制数的输入
; binidec:输出5位的十进制数值
; 入口参数为BX。
; 出口参数为BX。
hexibin proc near
mov bx, 0
mov cx,4
newchar:
mov ah, 1
int 21h
cmp al, 30h ; 0~9的16进制表示为30~39
jl exit
cmp al, 3ah ; 和10比较,如果小于10的就跳转到add_to
jl add_to
cmp al, 41h ; 和A比较
jl exit
cmp al, 47h ; 和G比较
jge exit
cmp al, 61h ; 和a比较
jl exit
cmp al, 67h ; 和g比较
jge exit
add_to:
push cx
mov cl, 4
shl bx, cl
mov ah, 0
add bx, ax
pop cx
loop newchar
exit:
ret
hexibin endp
; 回车换行
Crlf proc near
push ax
push dx
mov dl, 0dh
mov ah,2
int 21h
mov dl, 0ah
mov ah,2
int 21h
pop dx
pop ax
ret
Crlf endp
end start
宏:源程序中一段有独立功能的程序代码。在使用之前先定义一次,以后就可以多次调用。
宏指令:用户自定义的指令。在编程时,将多次使用的功能用一条宏指令来代替。
汇编语言源程序包含:
C语言中以#define作为标志的编译预处理命令称为宏定义命令。其不带参数的格式为:
#define 标识符 字符串
其中的标识符叫宏名,字符串叫宏体。带参的宏一般形式为:
#define 宏名(参数表) 字符串
如:
#define PI 3.1415926
#define area(r) (3.1415926*(r)*(r))
系统对宏的处理是这样的:当遇到宏名时,就用宏体替换,即所谓的宏替换。这一过程是由预编译程序完成的(不必用户自己操作),而后才将宏替换后的程序交编译程序进行编译。
; 宏定义:
macro_name MACRO [哑元表] ; 形参/虚参
……
…… ; 宏定义体
ENDM
宏调用: (必须先定义后调用)macro_name [实元表] ; 实参
宏展开: 宏定义体->复制到宏指令位置,实参代虚参。
名称 | 优点 | 缺点 |
---|---|---|
子程序 | 模块化,省内存 | 开销大 |
宏 | 参数传送简单,执行效率高 | 占用内存空间大 |
符号 | 使用 | 说明 |
---|---|---|
& |
符号1 & 符号2 | 宏展开时,合并前后两个符号形成一个符号。 &可以作为哑元的前缀。 |
;; |
宏展开的时候,;; 后面的注释不予展开。 |
|
% |
% 表达式 | 汇编程序将%后面的表达式转换为当前基数下的数字,并在展开期间用这个数取代哑元。 |
LOCAL伪操作为每个标号建立唯一的符号(??0000~??FFFF),必须紧跟在MACRO语句之后,中间不允许有任何操作包括注释。
; 宏定义
absol MACRO oper
LOCAL next
cmp oper,0
jge next
neg oper
next:
ENDM
; 宏调用
……
absol var
……
absol bx
……
说明:
反汇编出来内容如下:
宏展开:
……; `absol var`的内容
1 cmp var,0
1 jge ??0000
1 neg var
1 ??0000: ; next的地址
……
……; `absol bx`的内容
1 cmp bx,0
1 jge ??0001
1 neg bx
1 ??0001: ; next的地址
……
从上面可以看出使用了LOCAL伪操作之后,每次调用absol后,next的地址都是不同的。即 LOCAL伪操作为每个标号建立唯一的符号。
名称 | 说明 |
---|---|
.LALL | 在LST清单中列出宏展开后的全部语句(包括注释)。 |
.SALL | 在LST清单中不列出任何宏展开后的语句。 |
.XALL | 缺省的列表方式,只列出宏体中产生目标代码的语句。 |
>EDIT MACRO . MAC
macro1 MACRO [哑元表]
……
ENDM
macro2 MACRO [哑元表]
……
ENDM
……
macroN MACRO [哑元表]
……
ENDM
>EDIT EXP.ASM
include MACRO.MAC
……
macro1 [实元表]
……
macro2 [实元表]
…… ; 删除不用的宏定义`purge macroN`
macroN [实元表]
……
不同外设具有的端口数各不相同,计算机中为每一个端口都赋予一个惟一编号——称为端口地址(或端口号PORT)。 8086CPU采用I/O端口独立编址的方式,采用16位地址最多能管理64K个端口,即端口占64KB地址空间,端口号为0~65535。必须使用专门的I/O指令访问端口。
CPU与I/O接口进行通信是通过接口电路内部的一组寄存器实现的,这些寄存器称为端口,包括:数据端口、状态端口和命令端口。
(只能用AX或AL与端口传送信息)
长格式:(PORT是端口地址(00~FFH))
执行操作:
短格式:
执行操作:(端口号>255时,先送到DX)
长格式:
功能:将寄存器中内容输出到指定端口。
短格式:
in al,60h;
从60h号端口读入一个字节。
执行时与总线相关的操作:
CPU和内存通过端口与外部设备进行通信。CPU在执行主程序过程中,当需要进行I/O操作时,很难保证输入设备已经准备好了数据,或者是输出设备已经处在可以接收数据的状态。因此,一般要在外部设备准备就绪并且I/O接口已经做好数据传送的情况下,才能进行数据传送,这种传送方式称为查询传送方式。
查询过程使CPU很容易与不同速度的外设实现速度配合,使接口电路十分简单,适用于较少数据传输情况下使用。但要占用CPU大量时间去查询I/O设备的状态。
采用中断方式, CPU执行主程序,等待中断的发生。I/O设备与CPU并行操作,进行数据传输的准备工作。当输入设备将数据准备好,或者输出设备空闲时,便通过I/O接口向CPU发申请中断。CPU在每执行完一条指令之后都会检查是否有中断请求,只要满足中断响应条件,CPU就暂停执行当前的程序,转向执行中断处理程序,进行数据传送,等传送完成后,CPU返回到被中断的主程序,继续进行原来的工作。
中断方式:需要保护现场和恢复现场,数据传输由CPU完成。
主要由硬件DMA控制器实现其传送功能,用于一些高速的I/O设备(比如磁盘),能使I/O设备直接与存储器进行成批数据的快速传送。
DMA方式:用DMA控制器来控制存储器和I/O设备之间的数据传送时,并不经过CPU,传输过程中CPU不占用总线,CPU处于原地等待。这样,传输时就不需要保存断点等额外操作了。另外,整个控制数据块传送的过程,包括地址增量和计数器减量的操作,都是由硬件控制完成的,因而大大缩短了数据传送的控制时间,提高了整个系统的处理效率。
I/O 指令是主机与外设进行通信的最基本途径。DOS 功能调用和BIOS例行程序中的输入/输出功能也是由IN和OUT指令完成的。
例:循环测试某状态寄存器的2位是否为1
AGAIN: IN AL, STATUS_PORT; --------状态寄存器的端口地址(00~FFH)
TEST AL, 00000100B
JZ AGAIN
IN AL,DATA_PORT; --------数据寄存器的端口地址(00~FFH)
MOV AL, DATA
OUT DATA_PORT, AL
轮流查询几种I/O设备:
DEV1: IN AL, STAT1
TEST AL, STAT1_BIT
JZ DEV2
CALL FAR PTR PROC1
DEV2: IN AL, STAT2
TEST AL, STAT2_BIT
JZ DEV3
CALL FAR PTR PROC2
DEV3: IN AL, STAT3
TEST AL, STAT3_BIT
JZ DEV1
CALL FAR PTR PROC3
PC机中有一个CMOS RAM芯片,其有如下特征:
mov al, 2
,out 70h, al
in al, 71h
在CMOS RAM中,存放着当前时间:
类型 | 地址 |
---|---|
秒 | 00H |
分 | 02H |
时 | 04H |
星期 | 06H |
日 | 07H |
月 | 08H |
年 | 09H |
这6个信息的长度都为1个字节,存储了用两个 BCD码表示的两位十进制数,高 4 位的BCD码表示十位,低4 位的BCD 码表示个位。
比如:00010100b表示14。
要读取 CMOS RAM的信息,我们首先要向地址端口70h写入要访问的单元的地址:
mov al,8
out 70h,al
然后从数据端口71h中取得指定单元中的数据:in al,71h
中断:使cpu中止正在执行的程序而转去处理特殊事件的操作。
中断源:引起中断的事件。8086/8088CPU最多有256个中断源,这些中断源根据来自CPU的内部还是外部分为两大类:内部中断源和外部中断源。
外中断(硬中断):
所谓不可屏蔽中断是指该中断请求不能通过软件的方式对其屏蔽,一旦出现NMI中断请求,CPU必须立即响应。
内中断(软中断10H
):
(图中引线端标示的数字为分配的终端类型号N(0-255))
8259A外部有28个引脚。有9片8259A可构成64级中断源。
(从外设发出中断请求到CPU响应中断,有两个控制条件起决定性作用):
IF = 1
的时候允许中断 ( STI 开中断)IF = 0
的时候禁止中断 ( CLI 关中断)= 0
的时候允许I/O设备请求中断 ,= 1
的时候禁止I/O设备请求中断。DATAS SEGMENT
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
MOV AL,0
MOV AH,35H
INT 21H
PUSH ES
PUSH BX ;保存原向量
PUSH DS
MOV AX,SEG FUNCTION
MOV DS,AX
MOV DX,OFFSET FUNCTION
MOV AL,0
MOV AH,25H
INT 21H ;设置新的向量
POP DS
;--------------------主程序部分
……
;------------------
POP DX
POP DS
MOV AL,0
MOV AH,25H
INT 21H ;恢复原向量
MOV AH,4CH
INT 21H
;--------------中断处理程序
FUNCTION PROC NEAR
……
FUNCTION ENDP
CODES ENDS
END START
(4×N)→ IP
,(4×N+2)→ CS
格式: int n ; n为中断类型码
。
功能:是引发n号中断过程。
CPU 执行int n
过程如下:
IF = 0,TF = 0
;(IP) = (n*4)
,(CS) = (n*4+2)
。 或者这么理解:
INT 21H
指令执行功能调用AH | 功能 | 入口参数 | 出口参数 |
---|---|---|---|
4CH | 返回DOS | 无 | 无 |
1 | 键盘输入一个字符到AL中 | 无 | AL=字符 |
2 | 输出DL寄存器的字符到显示器 | DL(存放一个字符) | 无 |
9 | 输出一个以“$”结尾的字符串到显示器 | DS:字符串所在的段地址 DX:字符串首地址 |
无 |
0AH | 从键盘输入一个字符串到指定缓冲区 | DS:缓冲区所在的段地址 DX:缓冲区首地址 |
缓冲区相应位置 |
可见,INT
指令的最终功能和call指令相似,都是调用一段程序。一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。
我们在编程的时候,可以用int指令调用这些子程序,而在子程序中安排iret指令返回。我们将这样的中断处理子程序简称为中断例程。
IRET
指令的执行过程相当于:
pop ip
pop cs
pop flags
中断处理程序的编写与子程序类似,先保护现场,再完成功能,然后恢复现场,最后用IRET指令返回,返回地址是中断发生时紧接着的下一条指令。
中断处理子程序:
保存寄存器内容,如允许中断嵌套,则开中断 ( STI )
中断处理功能
关中断(CLI)
送中断结束命令( EOI )给中断命令寄存器
恢复寄存器内容
IRET中断返回
主程序:
1、设置中断向量
2、设置 CPU 的中断允许位IF
3、设置设备的中断屏蔽位
注意:程序员在编程的时候可以调用系统设置好的中断例程,也可以自己编写中断处理程序。中断类型号0、1、3、4是固定的内部中断,向量2是非屏蔽中断,向量5~31是保留给系统使用的中断,向量32~255则是用户可用的中断。
编写一个中断处理程序,要求在主程序运行期间,每隔 10秒显示一次字符串‘ bell ’。
.model small
.stack
.data
cnt dw 182
mes db 'bell',0ah,0dh,'$'
.code
start:
mov ax, @data
mov ds, ax
mov al, 1ch
mov ah, 35h
int 21h ;取向量1ch
push es
push bx ;保存原向量
push ds
mov dx, offset ring
mov ax, seg ring
mov ds, ax
mov al, 1ch
mov ah, 25h
int 21h ;设置新向量
pop ds
in al, 21h;中断屏蔽寄存器
and al, 11111110b
out 21h, al ;增加定时器中断
sti ;开中断
BIOS是固化在PC机内存地址0FE000开始的8KBROM中的基本输入输出系统的例行程序,它为PC系列的不同微处理器提供了兼容的系统加电自检、引导装入、主要I/O设备的处理程序以及接口控制等功能模块,一般以中断处理程序的形式存在。BIOS可以处理所有的系统中断,如键盘、显示器、磁盘、打印、日期与时间等。BIOS是模块化的结构形式,每个功能模块的入口地址都在中断向量表中,对这些中断调用是通过软中断指令INT来实现的。
DOS是IBM PC机的磁盘操作系统,由软盘或硬盘提供。它的两个DOS模块IBMBIO.COM和IBMDOS。COM使BIOS使用起来更方便。其中模块IBMBIO.COM是一个输入输出设备处理程序,提供DOS到BIOS的低级接口,模块IBMDOS。COM包括一个文件管理程序和一些处理程序,把信息传送给IBMBIO.COM,形成BIOS调用。因为DOS模块提供了更多更必要的测试,使DOS操作比使用相应功能的BIOS操作更简易,而且DOS对硬件的依赖性更少些。
一般来说,中断例程中包含多个子程序,内部用AH传递子程序的编号来决定执行哪个子程序。
AH | 功能 | 返回参数 |
---|---|---|
0 | 从键盘读一字符 | AL=字符码,AH=扫描码 |
1 | 读键盘状态并检查是否有字符输入 | 如按下ZF=0,AL=字符码,AH=扫描码,否则ZF=1,缓冲区空 |
2 | 取键盘状态字节 | AH=00,AL=键盘状态字节(KB_FLAG) |
比如指令序列:
MOV AH, 0
INT 16H ;等待按键输入然后取得扫描码和字符码
MOV BX,AX ;用BX传递参数
CALL BINIHEX ;调用子程序将BX转16进制并显示
AH | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
1 | 从键盘输入一个字符并回显在屏幕上 | AL = 字符 |
|
6 | 读键盘字符,不回显 | DL = 0FFH |
若有字符可取,AL= 字符,ZF=0 若无字符可取, AL=0,ZF=1 |
7 | 从键盘输入一个字符,不回显 | AL = 字符 |
|
A | 输入字符到缓冲区 | DS:DX = 缓冲区首址 |
(DX+1)= 实际输入字符数 |
B | 检验键盘状态 | AL=0 表示有输入 ,AL=FF 表示无输入 |
限制的最多数 +2
,max DB 11, ? , 11 dup (?)
,缓冲区必须定义为字节类型,不能定义为字类型。
将屏幕划分为 m列和n行 (m × n),在每个网格位置上显示像素,一个字符是一个像素。在这种显示方式下,显示缓冲存储区中存放的是字符的ASCII码和对应的显示属性,每个字符占用两个字节的空间。
将屏幕划分为 m×n的点阵,在每个点的位置显示像素,一个点是一个像素。显示缓冲存储区中存放的是“像素”点的信息,它的值为“0”或者“1”,为“0”就不在屏幕上打点,为“1”则在屏幕上打点。
AH | 功能 | 调用参数 |
---|---|---|
2 | 显示一个字符(检验Ctrl-Break) | DL = 字符 光标跟随字符移动 |
6 | 显示一个字符(不检验Ctrl-Break) | DL = 字符 光标跟随字符移动 |
9 | 显示字符串 | DS:DX=串地址 串必须以$结束,光标跟随串移动 |
MDA显存的起始地址为B000:0000,CGA、EGA、VGA的是B800:0000。 1屏幕的字符数据称为1页数据。据显存大小,可存储若干页的字符象素。
例: 16KB 显存能存储:(1000B=1KB)
25×80方式,4页( 0 ~ 3 ), 80×25×2×4 =16000
25×40方式,8页( 0 ~ 7 ), 40×25×2×8 =16000
对CGA、EGA、VGA的80列显示方式,0页显存中的起始地址是B800:0000, 1页B800:1000, 2页B800:2000,3页B800:3000。屏幕上某一字符位置在显存中的偏移地址计算公式:
Char_offset=Page_offset+((row*width)+column)*byte
字符偏移地址=页偏移地址+((行号 * 行宽)+列号)* 2
功能号 AH=0,1, 2, 3, 5, 6, 7, 8, 9, 0AH, 0EH 13H
AL = 显示方式值
AL显示方式值 | 显示点阵大小 | 显示方式 |
---|---|---|
00 | 40×25 | 黑白文本方式 |
01 | 40×25 | 彩色文本方式 |
02 | 80×25 | 黑白文本方式 |
03 | 80×25 | 彩色文本方式 |
04 | 320×320 | 彩色图形方式 |
AH = 00H
10H
5.实现功能 将显示方式设置为指定的形式
例: 将显示方式设置为 25×80彩色文本方式
MOV AL, 03H
MOV AH, 00
INT 10H
;隐藏光标
mov ch,20h
mov cl,00h
mov ah,1
int 10h
Int 10h
的功能02
:。
DH和DL寄存器中为光标位置的行列号,BH中为页号(单色显示器页号为0 )。
例:
; -------- 设置光标的位置,光标在第5行第6列(4,5)。
mov dh,4 ; DH是行号
mov dl,5 ; DL是列号
mov bh,0 ; BH是页号
mov ah,2 ; 2号功能
int 10h
功能03 :BH
中指定页号。把光标位置的行号回送给DH
,列号回送给DL
,光标大小的参数填入CH
和CL
。
mov ah,3
mov bh,0
int 10h ;返回参数dh:dl=行:列
功能06(07):使屏幕内容上卷(或下卷)指定的行。需要7个参数。
例:清除屏幕
mov ah, 6 ; ah=功能号
mov al, 0 ; al=指定行数(为0全屏幕空白)
mov bh, 70h ; bh=卷入行属性(白底黑字 )
; -------- 定义窗口属性
mov ch, 0 ; ch=左上角行号
mov cl, 0 ;cl= 左上角列号
mov dh, 24 ; dh=右下角行号
mov dl, 79 ;dl= 右下角列号
; -------- 定义窗口属性
int 10h ; BIOS调用类型10h
功能9(0a
): 把一个字符送到显示屏幕,可直接在cx中设定显示次数,调用结束后光标返回它的初始位置。(0a以正常属性显示)
功能8:读取当前光标位置的字符和属性,入口参数bh=显示页号
,返回ah/al=字符/属性
。
例: 在品红背景下,显示5个浅绿色闪烁的星号。(闪烁很快几乎觉察不到)
MOV AH,09 ; 光标位置下显示
MOV AL,'*' ; 显示字符
MOV BH,0 ; 显示页0
MOV BL,0DAH ; 1 101 1010
MOV CX,05 ; 显示次数
INT 10H ; BIOS调用
13H
:显示字符串。DATAS SEGMENT
CLOCK DB 0,0,':',0,0,':',0,0,'$'
DATAS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS
START:
MOV AX,DATAS
MOV DS,AX
RESTART:
LEA BX,CLOCK
MOV AL,4
CALL GETTIME
MOV AL,2
CALL GETTIME
MOV AL,0
CALL GETTIME
;------ 设置光标位置
MOV DH,10 ;行
MOV DL,30 ;列
MOV BH,0
MOV AH,2
INT 10H
;------ 隐藏光标
MOV CH,20H
MOV CL,00H
MOV AH,1
INT 10H
;------ 输出时间
LEA DX,CLOCK
MOV AH,9
INT 21H
; ------ 检测键盘输入。
IN AL,60H
CMP AL,1
JNZ RESTART ;按下ESC退出,不断更新时间
MOV AH,4CH
INT 21H
; ------ 获取时间 入口参数:AL
GETTIME PROC
; ------ 读取数据
OUT 70H,AL ; 设定读数地址
IN AL,71H ; 取数
; ------ 左移四位得到十位数值
MOV AH,0 ;清零
MOV CL,4
SHL AX,CL
SHR AL,CL ; 得到个位数值
ADD AH,30H ; 转换成ASCII
ADD AL,30H ; 转换成ASCII
MOV CLOCK[BX],AH
MOV CLOCK[BX+1],AL
ADD BX,3 ; 跳过三个字符->':'
RET
GETTIME ENDP
CODES ENDS
END START