- **1.源程序执行过程**
- **2.源程序组成**
- **3.[BX]寄存器**
- **4.loop指令**
- **5.多个段的使用**
- **6.寻址方式**
- **7.转移指令**
- **offset操作符**
- 无条件转移:**jmp指令**
- 条件转移 :
- **8.call和ret指令**
- **9.标志寄存器**
- **10.内中断**
- **11.int指令**
- **12.端口**
- **13.外中断**
- **14.直接定址表**
- **15.键盘输入与磁盘读写**
- **16.指令系统**
编写->编译连接->执行
对源程序进行编译连接 1.使用汇编语言编译程序(MASM.EXE)对源程序文件中的源程序进行编译,产生目标文件【.obj文件】
2.再用连接程序(LINK.EXE)对目标文件进行连接,生成可在操作系统中直接运行的可执行文件【.EXE文件】。
可执行文件包含两部分内容 1.程序(从源程序的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
2.相关的描述信息(比如:程序有多大、要占多少内存空间等)
执行可执行文件中的程序 1.在操作系统中,执行可执行文件中的程序
2.操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序
2.源程序组成源程序由 汇编指令+伪指令+宏指令 组成
伪指令:编译器处理
1.没有对应的机器码的指令,不能由CPU直接执行
2.伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作
汇编指令:编译为机器码
assume cs:codesg ;假设代码段的名称为codesg
codesg segment ;定义一个codesg段
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends ;codesg段结束
end ;是个伪指令,程序的结束标记
segment和ends: 定义一个段
1.segment和ends是一对成对使用的伪指令
2.编写汇编程序【必须】使用到的指令
3.segment和ends的功能是定义一个段
segment:说明一个段开始
ends:说明一个段结束
4.一个段必须有一个名称来标识,使用格式为
段名 segment
段名 ends
5.一个汇编程序由多个段组成
这些段用来存放【代码、数据、或当作栈空间】来使用
一个有意义的汇编程序至少要有一个段,这个段用来存放代码。
end:结束标识符
1.end是一个汇编程序的结束标记
2.编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译
3.如果程序写完了,要在结尾处加上伪指令end
否则,编译器无法知道程序在何处结束
4.【切记】不要把end和ends搞混了
end:汇编程序的结束标记
ends:与segment成对出现
assume: 寄存器和段的关联假设
1.它假设某一段寄存器和程序中的某一个用segment...ends定义的段相关联
2.通过assume说明这种关联,在需要的情况下,
编译程序可以将段寄存器和某一具体的段相联系
程序和源程序
1.我们将源程序文件中的所有内容称为【源程序】
2.将源程序中最终由计算机执行处理的指令或数据称为【程序】
3.程序最先以汇编指令的形式,存储在源程序中
然后经过编译、连接后转变为机器码,存储在可执行文件中
3.[BX]寄存器
[BX]和[0]类似,[0]表示内存单元的偏移地址是0
在使用[0]一定要清楚什么时候可以使用(Debug 和masm编译器区别) 一般使用[BX]来表示地址偏移量(因为我们一般使用masm) 默认它的段地址在DS寄存器中 也可以使用段前缀来解决该差异
我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器,比如
mov ax,ds:[0] ;内存空间中偏移地址为0对于的数据
mov ax,ds:[bx]
这里的ds就叫做【段前缀】
;Debug 将内存单元中偏移地址为0对应数据赋值给al
assume cs:codesg
codesg segment
start: mov ax,2000H
mov ds,ax
;如果这个是在Debug编写的,则是将内存单元中偏移地址为0对应数据赋值给al
;如果用masm编译器,则默认将0赋值给al
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
mov ax,4C00H
int 21H
codesg ends
end start
;masm(正常编辑) 将内存单元中偏移地址为0对应数据赋值给al
assume cs:codesg
codesg segment
start: mov ax,2000H
mov ds,ax
mov bx,0
mov al,[bx]
mov bl,[1]
mov cl,[2]
mov dl,[3]
mov ax,4C00H
int 21H
codesg ends
end start
4.loop指令
该指令处理循环问题 格式:
标号:
循环处理程序
loop 标号 ;执行到这里跳到标号的位置执行
执行loop指令时,要进行两个步骤的操作:
(1)(cx)=(cx)-1(CX中存放循环次数)
(2)判断cx是不是为零,不为零的话转到标号出执行程序。若为零,则向下执行
loop指令实现循环,cx中存放循环的次数
标号:在汇编语言中,标号代表一个地址,标号标识了一个地址
描述性符号:() (ax)表示ax中的内容,(al)表示al中的内容 ((ds)* 16 + (bx))表示“ds中的内容×16 + bx中的内容”得到的物理地址中的内容
例题:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中
问题
1.12个8位数据加在一起,结果可能会超出8位(越界),故要用16位寄存器存放结果
2.将一个8位的数据加入到16位寄存器中,类型不匹配,8位的数据不能与16位相加
解决办法:把原来8位的数据,先通过通用寄存器ax,将它们转化成16位的
assume cs:codeseg
codeseg segment
start:
mov ax,0ffffH
;初始化ds:bx 指向ffff:0内存单元
mov ds,ax
mov bx,0
mov dx,0 ;初始化累加寄存器dx
mov cx,12 ;初始化循环计数寄存器cx
s:mov al,[bx] ;将偏移地址为[bx]对应的数据赋值给al
mov ah,0
add dx,ax ;间接向dx中加上((ds)*16+(bx))单元中的数值
inc bx ;自加1 ds:bx指向下一个单元
loop s
;程序结束
mov ax,4c00H
int 21h
codeseg ends
end start ;end 后面的标号代表程序的入口地址
注:1.在汇编程序中,数据不能以字母开头,如果要输入像FFFFH这样的数,则要在前面添加一个0,即0ffffH
2.在debug程序中引入G命令和P命令 1)G命令 G命令如果后面不带参数,则一直执行程序,直到程序结束 G命令后面如果带参数,则执行到ip为那个参数地址停止 2)P命令 T命令相当于单步进入(step into) P命令相当于单步通过(step over)
安全的空间 8086模式中,随意向一段内存空间写入内容是很危险的 在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用[0:200~0:2FF]的256个字节的空间。所以,我们使用这段空间是安全的
5.多个段的使用dw:定义字型数据(define word)16字节 在数据段中使用dw定义数据,则数据在数据段中 在代码段中使用dw定义数据,则数据在代码段中 堆栈段也是一样(类似还有db 定义字节型数据)
例题:计算0123H、0456H,0abxH、0defH、0fesH、0cbaH、0987H这8个数据的和,结果存放在ax中
assume cs:codesg
codesg segment
dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
;dw,define word,定义字型数据,db定义字节型数据
;由于数据在代码段中,所以段地址是CS
;dw定义的数据在最开始的地方,所以偏移地址是0开始
start:mov bx,0 ;第一条指令
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00H
int 21H
codesg ends
end start
例题:利用栈编程将定义的数据逆序(联想栈的特性)存放
约定:
用来存放数据的段,我们将其命名为“data”
用来存放代码的段,我们将其命名为“code”
用来作栈空间的段,我们将其命名为“stack”
assume cs:codesg,ds:data,ss:stack ;在源程序中味定义三个段的名称
;数据段
data segment
dw 0123H,0564H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
data ends
;栈段
stack segment
dw 0,0,0,0,0,0,0,0 ;定义8个字型空数据,后面当作栈来使用
stack ends
;代码段
code segment
start:
;栈空间初始化
mov ax,stack
mov ss,ax
mov sp,16 ;设置栈顶ss:sp指向stack:16
;数据段初始化
mov ax,data ;ds指向data段
mov ds,ax ;ds:bx指向data段中的第一个单元
mov bx,0
s: push cs:[bx]
add bx,2 ;偏移地址加2个字节单元
loop s ;以上代码段0~16个单元中的8个字型数据一次入栈
;依次出栈8个执行数据到代码段0~16单元中
s1: pop cs:[bx]
add bx,2
loop s1
mov ax,4c00h
int 21h
codesg ends
end start
分析: 在源程序中用伪指令 “assume cs:code,ds:data,ss:stack”将cs、ds和ss分别和code、data、stack段相连, 这样做了之后,CPU是都就会将cs指向code,ds指向data,ss指向stack吗? 不能,伪指令CPU看不懂,伪指令是给编译器看的,需在在code段中给DS,CS、SS设置相应的值才能让CPU识别出数据段、代码段、堆栈段
注: 1.如果段中的数据占N个字节,则程序加载后,这段实际占有的空间为:N%16==0?N:16×(N/16+1);因为一个段最小占用16字节,即有16个字节只有这个段可以访问到
6.寻址方式add指令 逻辑与指令,按位进行与运算
mov al,01100011B
and al,00111011B
; al=00100011B
作用:将操作对象的相应位设为0,其他位不变
and al,10111111B;将al第六位设为0
and al,01111111B;将al第七位设为0
or指令 逻辑或指令,按位进行或运算
mov al,01100011B
or al,00111011B
; al=01111011B
作用:将操作对象的相应位设为1,其他位不变
or al,01000000B;将al第六位设为1
or al,10000000B;将al第七位设为1
字符数据 在汇编中,可以使用’×××’的方式指明数据是以字符的形式给出的,编译器会将它们转化为相应的ASCII码
1.db 'unIX' ;相当于:db 75H,6EH,49H,58H
;'u'、'n'、'I'、'X'的ASCII码分别为75H,6EH,49H,58H
2.mov al,'a' ;相当于:mov al,61H
;'a'的ASCII码为61H
ASCII码中,大写字母和小写字母之间的规律
小写字母=大写字母+32 (十进制)
小写字母=大写字母+20H (十六进制)
大写字母从41H(65)开始排,小写字母从61H(97)开始排
大小写转换(以按位与,或角度思考)
A 01000001 a 01100001
B 01000010 b 01100010
特点:大写字母ASCII码第五位为0,小写字母ASCII码第五位为1
所以,我们只要对第五位进行相应的与操作,或操作
内存偏移:
[bx+idata] [bx+idata]表示的是一个内存单元,它的偏移地址为bx+idata (idata为常量) 注:bx可以看成是一个变量 (bx自增 可以对数组(l连续内存空间进行操作)
[0+bx]和[5+bx]的方式在同一个循环中定位这两个字符串中的字符
[5+bx] = 5[bx] = [bx].5
SI和DI寄存器 和bx功能相近,充当BX的扩充,但是不能分成两个8位寄存器来使用。[SI]段地址默认也是在DS中
一般ds:si指向要复制的原始空间,ds:di指向复制的目的空间 注:注意si、di是16位寄存器,循环中自增时,应该+2
[bx+si]和[bx+di]
[bx+si]表示一个内存单元,它的偏移地址为bx中的数值加上si中的数值, 它的偏移地址在ds中,可以写成 [bx][si]
[bx+si+idata]和[bx+di+idata] [bx+si+常量]表示一个内存单元,偏移地址为bx的值+si的值+常数
指令mov ax,[bx+si+idata]也可以写成如下形式
1.mov ax,200[bx+si]
2.mov ax,200[bx][si]
3.mov ax,[bx].200[si]
编程将数据段中每个单词改为大写字母
assume cs:codesg,ds:datasg,ss:stacksg
stacksg segment
dw 0,0,0,0,0,0,0,0 ;定义一个段,用作栈段,容量为16个字节
stacksg ends
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
codesg segment
start:
mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0 ;用bx来遍历行
mov cx,4
s0: push cx ;将外层cx存入栈中,可以用一段安全的内存存储
mov si,0 ;用si遍历列
mov cx,3
s:mov al,[bx+si]
and al,11011111B ;大小写转换
mov [bx+si],al
inc si
loop s
add bx,16
pop cx ;内层循环后从栈中弹出cx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
注意:外层cx和内层cx的存储问题 数据暂存问题内存或者寄存器可以解决,但是为了使程序结构清晰便于阅读,应该使用栈
一般用[bx+idata+si]的方式来访问结构体,用idata定位结构体中的某一数据项,用si定位数组项中的每个元素。 例如:[bx].idata、[bx].idata[si]。
数据处理
bx、si、di、bp 只有bx、si、di、bp四个寄存器用在[ ]中进行内存单元寻址。在[ ]中,组合只能以这四种形式:bx和si、bx和di、bp和si、bp和di
在[…]中使用寄存器bp,若指令没有显性给出段地址,段地址就默认在ss中 在[…]中使用寄存器bx,若指令没有显性给出段地址,段地址就默认在ds中
正确组合:
mov ax,[bx]
mov ax,[si]
mov ax,[di]
mov ax,[bp]
mov ax,[bx+si]
mov ax,[bp+si]
mov ax,[bx+si+idata]
mov ax,[bp+si+idata]
注:idata是立即数
指令处理数据长度 8086CPU的指令,可以处理两种尺寸的数据,byte和word 所以一般在机器指令中要指明,指令进行的是字操作还是字节操作
1.通过寄存器指明要处理的数据尺寸 2.操作符word ptr或者byte ptr指明内存单元的长度(在没有寄存器名存在的情况下) 3. pop、push指令,一定是字型数据
mov byte ptr ds:[0],1 ;字节
mov word ptr ds:[0],1 ;字
在没有寄存器参与的内存单元访问指令中,用word ptr或者byte ptr 显性地指明所要访问的内存单元的长度,是非常有必须要的,否则,CPU无法得知所要访问的单元是字单元,还是字节单元
div指令 div(divide)是除法指令,可用乘法模拟 格式为:
div reg(寄存器)
div 内存单元
除数是寄存器或内存单元的内容
规则: 1.除数:8位或16位,在寄存器或内存单元中 2.被除数:默认放在AX或DX和AX中
除数与被除数的相互关系:
除数 被除数
8位 16位(AX)
16位 32位(DX+AX)
结果: 8 16
商 AL AX
余数 AH DX
div byte ptr ds:[0] ;被除数(ax)是16位,除数是ds:[0]的内容(8位)
商: (al)=(ax) / ((ds)*16+0)
余数: (ah)=(ax) / ((ds)*16+0)
div word ptr es:[0] ;被除数是32位,除数是es:[0]的内容(16位)
商: (ax)=[(dx)*10000H+(ax)] / ((es)*16+0)
余数: (dx)=[(dx)*10000H+(ax)] / ((es)*16+0)
高16位存在dx,低16位存在ax
除法指令计算100001/100
;被除数100001大于2^16=65535(FFFF),不能用ax来存放,要用dx和ax两个寄存器联
合存放。除数小于255,可用一个8位寄存器存放,但是被除数是32位的,除数应为16
位,所以要用一个16位寄存器来存放除数
;100001的十六进制为186A1H,100001的高16位(1)存放在dx,低16位(86AH)存放在
ax中
mov dx,1
mov ax,86A1H
mov bx,100
div bx
;执行后ax内容等于03E8H(即1000),dx的值等于1(余数)
伪指令dd db定义字节型数据,dw定于字型数据,dd 定于 dword(double word双字型数据)
data segment
db 1;第一个数据为01h,在data:0处,占1个字节
dw 1;第二个数据为0001h,在data:1处,占1个字
dd 1;第三个数据为00000001h,在data:3处,占2个字
data ends
伪指令dup
dup是一个操作符,在汇编语言中,同db、dw、dd等一样,也是由编译器识别处理的符号,dup和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复
dup的使用格式:
db 重复的次数 dup(重复的字节型数据)
dw 重复的次数 dup(重复的字型数据)
dd 重复的次数 dup(重复的双字型数据)
db 3 dup(0) ;定义了3个字节,他们的值都是0,等同于db 0,0,0
db 3 dup(0,1,2)
;定义了9个直接,它们是0、1、2、0、1、2、0、1、2,相当于db 0、1、2、0、1、2、0、1、2
assume cs:codesg,ds:data,es:table
data segment
;表示21年的21个字符串
;起始地址0,终止地址21*4-1:83(53h) 0-53h
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;表示21年公司总收入的21个双字型数据
;起始地址21*4:84,终止地址21*4+21*4-1:167 54h-0A7h
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;表示21年公司雇员人数的21个字型数据
;起止地址21*8:168,终止地址21*8+21*2-1:209 0A8h-0D1H
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
table segment
db 21 dup('year summ ne ?? ')
table ends
start:
;初始化阶段
mov ax,data
mov ds,ax
mov ax,table;data已经被占用
mov es,ax
mov bx,0
mov si,0
mov di,0
mov cx,21
;存放年份,每一个bx就是一个字节
S:
mov al,[bx]
mov es:[di],al
mov al,[bx+1]
mov es:[di+1],al
mov al,[bx+2]
mov es:[di+2],al
mov al,[bx+3]
mov es:[di+3],al
;存放公司的总收入
mov ax,[bx+54H];第一个年收入是dd数据类型,段地址为54H
mov dx,[bx+54H]
mov es:[di+5H],ax
mov es:[di+7H],dx
;存放公司的人数
mov ax,[si+0A8H];第一个人数的数据段地址为0A8H
mov es:[di+0A8H],ax
;计算人均收入并存放
mov ax,[bx+54H]
mov dx,[bx+56H];这两句诗初始化被除数
div word ptr,ds:[si+0A8H];除以人数
mov es:[di+0dH],ax;将商放入指定位置
;为下一次循环时存放数据做准备
add bx,4;bx确定年份和收入
add si,2;si确定人数
;add di,16;di确定的是每行的列数
loop S
mov ax,4c00h
int 21h
codesg ends
end start
7.转移指令
可以修改IP,或者同时修改CS和IP的指令统称为转移指令,以控制CPU执行内存中某处代码的指令就是转移指令
转移指令分为以下几类:
1.无条件跳转指令(如:jmp)
2.条件跳转指令
3.循环指令(如:loop)
4.过程,就像C语言中的函数
5.中断
offset操作符
操作符offset在汇编语言中由编译器处理,它的功能是取标号的偏移地址
assume cs:codesg
codesg segment
start: mov ax,offset start;相当于 mov ax,偏移地址0,段地址是从0开始
s: mov ax,offset s;相当于 mov ax,3,标记的是代码段中的第二条指令,第一条指令长度为3个字节,则s的偏移地址为3
codesg ends
end start
无条件转移:jmp指令
jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP
jmp需要两种信息:
1、转移的目的地址
2、转移的距离(段间转移、段内转移、段内近转移)
1)段内短转移: jmp short 标号 对IP的修改范围是-128~127,一个字节的空间,即向前转移最多128字节,向后最多127字节。short 表明指令进行的是短转移,标号指明了指令要转移的目的地,转移指令结束后CS:IP指向标号处的指令
注:一般汇编指令中的立即数(idata)会出现在对应的机器指令中。而jmp指令的机器指令并不包含目的地址,包含的是相对于当前IP的转移位移,CPU并不需要目的地址就可以实现对IP的修改
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
分析:
jmp short s 指令的读取和执行过程:
1、CS:IP指向jmp short s 的机器码;
2、读取指令码进入指令缓冲器
3、 改变IP,(IP)=(IP)+所读取指令的长度,IP指向下一个指令;
4、CPU执行指令缓冲器中的指令;
5、执行后CS:IP继续指向下一个指令
jmp short 标号的功能:(IP)=(IP)+8位位移。
1、8位为=标号处的地址-jmp指令后的第一个字节的地址;
2、short 指明此处的位移为8位;
3、8位位移的范围为-128~127,用补码表示。
4、8位位移由编译程序在编译时算出的
2)指令实现段内近转移: jmp near ptr 标号 功能: (IP)=(IP)+16位位移 jmp short 标号是8位的位移,而jmp near ptr 标号是16位位移 near ptr 指明此处的位移为16位,16位位移由编译程序在编译时算出的
3)段间转移: jmp far ptr (远转移) jmp far ptr 标号的功能:
far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP
(CS)=标号所在段的段地址
(IP)=标号所在段总的偏移地址
assume cs:codesg
codesg segment
start:mov ax,0
mov bx,0
jmp far ptr s
db 256 dup(0) ;一段长空间
s:add ax,1
inc ax
codesg ends
end start
注:与前面不同的是 机器码中包含了转移的目的地址
4)jmp 16位寄存器 功能是16位寄存器赋值给IP,实现段内的近(短)转移
5)jmp word ptr 内存单元地址 功能:16位只能实现段内转移,从内存单元地址处开始存放一个字(转移的目的偏移地址),内存单元地址可用寻址方式的格式给出
mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]
;相当于 jmp ax,执行后(IP)=0123h
mov ax,0123H
mov [bx],ax
jmp word ptr [bx]
;执行后(IP)=0123h
6)jmp dword ptr 内存单元地址(段间转移) 功能:从内存单元地址处开始存放两个字型数据,高地址是转移的目的段地址,低地址处是转移的目的偏移地址。 (ip)=(内存单元地址) ;双字中的低位字是给ip的 (cs)=(内存单元地址+2) ;双字中的高位字是给cs的 内存单元地址可用寻址方式的任一格式给出
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]
mov ax,0123H
mov [dx],ax
mov word ptr [bx+2],0
jmp dword ptr [bx]
;执行后 (CS)=0,(IP)=0123H CS:IP指向0000:0123
注:不能直接向内存单元中加入立即数,要通过寄存器,把立即数加进去
条件转移 :jcxz指令** 指令格式:jcxz 标号 如果cx的值为0,则转移到标号处执行,不为0则向下执行**(条件)**
注:所有的有条件跳转指令都是短转移,对应的机器码中包含转移的位移,而不是目的地址。对ip的修改范围都为:-128~127
loop指令 loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移而不包含目的地址 执行过程: 1)cx先自减1 2)当cx的值不为0时,(IP)=(IP)+8位位移,8位位移=标号处的地址-loop指令后的第一个字节的地址。 3)8位位移的范围是-128~127,用补码表。 4)8位位移由编译器在编译时算出
小结: 根据位移进行转移和根据目标地址进行转移 根据位移进行转移:
jmp 16位寄存器
jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号
根据目标地址进行转移
jmp far ptr
jmp word ptr
jmp dword ptr
注: 1.根据位移进行转移对IP的修改时根据转移目的地址和转移起始地址自检的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是目的地址的位移距离。方便了程序段在内存中的浮动分配,没有固定目的地址的限制,更灵活 2.根据位移进行转移的指令,它们的转移范围受到了转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译时编译器会报错
8.call和ret指令call和ret指令都是转移指令,它们都修改IP或者同时修改CS和IP,经常被共用来实现程序的设计
ret和retf指令
ret指令用栈中的数据来修改IP的内容,从而实现近转移
CPU执行ret指令时,进行下面两步操作:
1.(ip)=((ss)*16+(sp)) ;ip的值修改为栈顶的内容
2.(sp)=(sp)+2 ;栈顶移动
CPU执行ret指令相当于进行 POP IP
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移
CPU执行retf指令时,进行下面四步操作
1.(ip)=((ss)*16+(sp)) ;ip的内容修改为栈顶的内容
2.(sp)=(sp)+2 ;栈顶移动
3.(cs)=((ss)*16+(sp)) ;cs的内容修改为栈顶移动之后,栈顶的内容
4.(sp)=(sp)+2 ;栈顶移动
栈顶的两个字,低位字修改为ip,高位字修改为cs
CPU执行retf指令相当于进行 POP IP和POP CS
call指令 call指令经常跟ret指令配合使用 CPU执行call指令,进行两步操作:
1.将当前的ip或cs和ip压入栈中
2.转移
call 标号 将当前的IP压入栈后转到目标处执行指令
执行过程:
1.(sp)=(sp)-2 ;栈顶移动
2.((ss)*16+(sp))=(ip) ;当前ip内容压栈
3.(ip)=(ip)+16位位移 ;跳转到标号处
等价于:
push ip
jmp near ptr 标号
call far ptr 标号 实现的是段间转移
执行这种格式的call指令时CPU的操作
1.(sp)=(sp)-2 ;栈顶移动
2.((ss)×16+(sp))=(cs) ;先把cs压栈
3.(sp)=(sp)-2 ;栈顶移动
4.((ss)×16+(sp))=(ip) ;然后把ss压栈
等价于:
push cs
push ip
jmp far ptr 标号
call 16位寄存器
执行这种指令时,在CPU中的操作
1.(sp)=(sp)-2
2.((ss)×16+(sp))=(ip)
3.(ip)=(16位寄存器)
等价于:
push ip
jmp 16位寄存器
call word ptr 内存单元地址 转移地址在内存中 (段内跳转)
等价于:
push ip
jmp word ptr 内存单元地址
call dword ptr 内存单元地址 转移地址在内存中 (段间跳转)
等价于:
push cs ;cs存放在高位
push ip ;ip存放在低位
jmp dword ptr 内存单元地址
mov sp,10h
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[0],0
call dword ptr ds:[0]
;执行后IP的值等于0123H,SP的值等于0CH,CS的值等于0
call和ret的配合使用 具有一定功能的程序段称为子程序 用call转去执行(将下一条指令的地址存入栈中,push 入栈) 在子程序后面使用ret(将栈中的数据弹出赋值给IP,pop,返回)实现返回
assume cs:code
code segment
start:
mov ax,1
mov cx,3
call s ;相当于调用子程序s
mov bx,ax ;bx=8
mov ax,4c00h
int 21h
s:
add ax,ax
loop s ;最终ax值为8
ret ;返回到程序调用处
code ends
end start
mul指令 mul指令时乘法指令,相乘的两个数要么都是8位的,要么都是16位的
8位:AL中和8位寄存器或内存字节单元中(AL中的内容作为被乘数0,结果放在AX
16位:AX和16位寄存器或内存字单元(AX中的内容作为被乘数)
结果放在DX(高位)和AX(低位)中
格式:
mul 寄存器
mul 内存单元(byte ptr或 word ptr指明是字还是字节)
寄存器和内存单元中的内容为另一个乘数
mul word ptr [bx+si+idata]
;(ax)=(ax) * ((ds)*16+(bx)+(si)+idata) 低16位
;(dx)=(ax) * ((ds)*16+(bx)+(si)+idata) 高16位
计算
;计算100*10,两个数都小于255,可以做8位乘法
mov ax,100
mov bx,10
mull bl
;结果(ax)=1000(03E8H)
;计算100*1000,1000都大于255,要做16位乘法
mov ax,100;高位自动补零
mov bx,10000
mull bx
;结果(ax)=4240H,(dx)=000FH,000F 4240H=1000000
计算data段中第一组数据的3次方,结果保存在后面一组dword单元中
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 8 dup (0)
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0;ds:si指向第一组word单元
mov di,16;ds:di指向第二组dword单元
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax ;低位存储
mov [di+2],dx
add si,2;ds:di指向下一个word单元
add di,4;ds:di指向下一个dword单元
loop s
mov ax,4c00h
int 21h
cube:mov ax,bx;实现n的3次方 ,返回的值在ax中
mul bx
mul bx
ret
code ends
end start
大小写转换
assume cs:code
data segment
db'conversation'
data ends
start:
mov ax,data
mov ds,ax
mov si,0;ds:si指向字符串(批量数据)所在空间的首地址
mov cx,12;cx存放字符串的长度
call capital
mov ax,4c00h
int 21h
capital: ;实现转换的程序
and byte ptr [si],11011111B ;转换
inc si
loop capital
ret
code ends
显示字符串 想要在dos界面上显示内容,只要向显示缓冲区写入数据即可
assume cs:code
data segment
db 'welcome to masm!',0
data ends
code segment
start:
mov dh,8;行号
mov dl,3;列号
mov cl,2;颜色属性
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str:;子程序
push cx
push si
mov al,0A0h;每行有80*2=160个字节=0a0h
dec dh;行号在显存中下标从0开始,所以减1
mul dh;相当于从第(n-1)*0a0h个byte单元开始
mov bx,ax;定位好的位置偏移地址存放在bx里(行)
mov al,2;每个字符占2个字节
mul dl;定位列,结果ax存放的是定位好的列的位置
sub ax,2;列号在显存中下标从0开始,又因为是偶字节存放字符,所以减2
add bx,ax;此时bx中存放的是行与列的偏移地址
mov ax,0B800h;显存开始的地方
mov es,ax;es中存放的是显存的第0页的起始地段地址
mov di,0;di指向显存的偏移地址,确定指向下一个要处理的字符的位置
mov al,cl;cl存放颜色参数,下边cl要用来临时存放要处理的字符
mov ch,0;下边cx存放的是每次准备处理的字符
s:
mov cl,ds:[si];指向'welcome to masm ',0
jcxz ok;cl为0时跳转
mov es:[bx+di],cl;偶地址存放字符
mov es:[bx+di+1],al;奇地址存放字符的颜色属性
inc si
add di,2;指向了下个字符
jmp short s ;无条件跳转,jcxz是离开的关键跳
ok:
pop si
pop cx
ret;定义结束
code ends
end start
9.标志寄存器
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW) 作用:
1、用来存储相关指令的某些执行结果;
2、用来为CPU执行相关指令提供行为依据;
3、用来控制CPU的相关工作方式
flag寄存器是按位起作用的,每一位都有专门的含义,记录特定的信息,与其他寄存器不一样 flag的1、3、4、12、13、14、15位共7位在8086CPU中没有使用,不具有任何含义,而0、2、4、6、7、8、9、10、11位共9位都具有特殊的含义
ZF(zero flag)标志 零标志位,它记录相关指令执行后,结果为0,ZF=1(记录下是0这样的肯定信息),结果不为0,ZF=0(表示结果非0)
mov ax,1
sub ax,1
指令执行后,结果为0,则ZF=1
mov ax,2
sub ax,1
指令执行后,结果不为0,则ZF=0
注:在8086CPU中,add、sub、mul、div、inc、or、and等它们大多都是运算(逻辑运算或是算术运算)指令,是影响标志寄存器的,而mov、push、pop等传送指令对标志寄存器一般没有影响,因为不会产生结果
PF标志 flag的第2位是PF,奇偶标志位,记录指令执行后结果所有的二进制位中1的个数。为偶数,PF=1,为奇数PF=0
SF(sign flag)标志 flag的第7位是SF符号标志位,记录指令执行后结果为负则SF=1,结果为正,SF=0 注:sf标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负,sf标志把所有数当作有符号数 如果把数据当作无符号数运算,sf的值则没有意义,虽然相关指令会影响它的值
mov al,10000001B
add al,1
;执行指令后al的值是10000010B,无符号数130,有符号数-126 sf=1
CF(carry flag)标志 flag的第0位是CF,进位标志位。一般情况下,在进行无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位值或从更高位的借位值。对于位数为N的无符号数,其对应的二进制信息的最高位为N-1位的最高有效位,假想存在第N位 两个8位的数据运算可能产生进位或者借位,由于这个进位值在8位数中无法保存,CPU在运算时,不会丢弃进位值,而是记录在一个特殊的寄存器的某一位上,CF位来记录这个进位值
mov al,98h
add al,al;执行后(al)=30h,cf=1,cf记录了从最高有效位向更高位的进位值
add al,al;执行后(al)=60h,cf=0,cf记录了从更高有效位向更高位的进位值
mov al,97h
sub al,98h;执行后(al)=ffh,cf=1,cf记录了向更高位的借位值
sub al,al;执行后(al)=0,cf=0,cf记录了向更高位的借位值
OF(overflow flag)标志 **对有符号数而言,**如果运算结果超出了机器所能表达的范围(对于8位有符号数,机器所能表达的范围是-128~127)将产生溢出
注:1.这里所讲的溢出,只是对有符号数运算而言,就像进位只是相对于无符号数而言 2.一定要注意 cf 和 of 的区别 当需要把机器码看成有符号数则使用of 当需要把机器码看成无符号数则使用cf CPU用CF位来记录无符号数运算是否产生了进位,用OF位来记录有符号数是否产生了溢出。用SF位来记录结果的符号
mov al,98d
add al,99d
;对于无符号数运算,98+99没有进位,CF=0
;对于有符号数运算,98+99发生溢出,OF=1
补充:在debug中,对应标志的符号
adc指令 adc是带有进位加法指令,利用了CF位上记录的进位值 格式: adc操作对象1,操作对象2,功能:操作对象1=操作对象1+操作对象2+CF 注:adc指令前面的指令决定在执行adc指令的时候加上的CF的值的含义,关键在于所加上的CF的值是被什么指令设置的。如果CF的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值
mov ax,1
add ax,ax
adc ax,3
;执行后(ax)=5,相当于执行(ax)+3+CF=2+3+0=5
mov al,98H
add al,al
adc al,3
;执行后 (ax)=34H,相当于执行(ax)+3+CF=30H+3+1=34H
计算
计算1EF000H+201000H,结果存放在AX(高16位)和BX(低16位)中
mov ax,001EH
mov bx,0F000H ;先计算低位(add),再计算高位(adc)
add bx,1000H
adc ax,0020H
计算1EF0001000H+2010001EF0H,结果存放在AX(高16位)、BX(次16位)中和cx(低16位)
mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
adc bx,1000H
adc ax,0020H
sbb指令 sbb是带借位减法指令,利用了CF位上记录的借位值。 格式: sbb 操作对象1,操作对象2,功能是:操作对象1=操作对象1-操作对象2-CF
计算003E1000H-00202000H,结果放在ax、bx中
mov bx,1000H
mov ax,003EH ;低位相减,看是否产生接错位,高位进行sbb
sub bx,2000H
sbb ax,0020H
cmp指令 cmp是比较指令,功能上相当于减法指令,只是不保存结果。 格式:cmp 操作对象1,操作对象2. 功能:计算操作对象1 - 操作对象2 但不保存结果,仅仅是根据计算结果对标志寄存器进行设置
cmp指令运算执行后通过做减法将对标志寄存器产生影响,其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果
cmp ax,ax
;执行后结果为0,ZF=1,PF=1,SF=0,CF=0,OF=0
mov ax,8
mov bx,3
cmp ax,bx
;执行后ax、bx的值不变,ZF=0,PF=1,SF=0,CF=0,OF=0
注意:单纯地考察SF的值不可能知道结果的正负。因为SF记录的只是可以在计算机中存放的相应位数的结果的正负,一般sf 和 sf结合起来
cmp既可以对无符号数进行比较,也可以对有符号数进行比较
cmp 操作数1,操作数2 ;操作数1、操作数2都是有符号数
1.of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负
of=0,sf=1 则 操作数1比操作数2小
of=0,sf=0 则 操作数1比操作数2大
2.of=1,说明有溢出,逻辑上真正结果的正负与实际结果的正负相反
of=1,sf=1 则 操作数1比操作数2大
of=1,sf=0 则 操作数1比操作数2小
检测比较结果的条件转移指令 与cmp相配使用,根据cmp指令的比较结果(cmp指令执行后相关标志位的值)进行工作的指令。
cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种: 1.根据无符号数的比较结果进行转移的条件转移指令,它们检测ZF、CF的值; 2.根据有符号数的比较结果进行转移的条件转移指令,它们检测SF、OF、ZF的值
如果ah的值等于bh则ah的值等于ah的值加ah的值,否则ah的值等于ah的值加上bh的值
cmp ah,bh
je s; ZF=1则跳转
add ah,bh
jmp short ok
s:
add ah,ah
ok:ret
统计data段中数值为8的字节的个数,用ax保存统计结果
一:
assume cs:code
data segment
db 8,11,8,1,8,5,63,38
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0;ds:bx指向第一个字节
mov ax,0;初始化累加器
mov cx,0;由于不知道循环次数,就设置位0,减一后达到最大
s:
cmp byte ptr [bx],8;和8进行比较
jne next;如果不相等转到next,继续循环
inc ax;如果相等就计数值加1
next:
inc bx
loop s;执行后:(ax)=3
mov ax,4c00h
int 21h
code ends
end segment
二:
assume cs:code
data segment
db 8,11,8,1,8,5,63,38
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0;ds:bx指向第一个字节
mov ax,0;初始化累加器
mov cx,0
s:
cmp byte ptr [bx],8;和8进行比较
je ok;如果不相等转到ok,继续循环
jmp short next;如果不想等就转到next,继续循环
ok:
inc ax;如果相等就计数值加1
next:
inc bx
loop s;执行后:(ax)=3
mov ax,4c00h
int 21h
code ends
end segment
DF(direction flag)标志 flag的第10位是DF,方向标志位,在串处理指令中,控制每次操作后si(一般指向原始偏移地址)、di(一般指向目标偏移地址)的增减 DF=0:每次操作后si、di递增 DF=1:每次操作后so、di递减
串传送指令
movsb(mov string byte)串传送指令 作用:以字节为单位传送, 将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器DF位的值将si和di递增1或递减1
1.((es)*16+(di))=((ds)*16+(si))
2.如果df=0,则:(si)=(si)+1
(di)=(di)+1
如果df=1,则:(si)=(si)-1
(di)=(di)-1
movsw 作用:以字为单位传送, 将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器DF位的值将si和di递增2或递减2
movsb和movsw进行的是串传送操作中的一个步骤,一般和rep配合使用 格式:rep movsb rep的作用是根据cx 的值,重复执行后面的串传送指令。由于每次执行一次movsb指令si和di都会递增或递减指向后一个单元或前个单元,则rep movsb就可以循环实现(cx)个字符的传送
cld指令和std指令 cld指令:将标志寄存器的df置为0【c:clear df】 std指令:将标志寄存器的df置为1【s:set df】
pushf和popf pushf:将标志寄存器的值压栈 popf:从栈中弹出数据,送入标志寄存器中 pushf和popf为直接访问标志寄存器提供了一种方法
用串传送指令将data段总的第一个字符串复制到它后面的空间中
assume cs:code
data segment
db'welcome to masm!'
db 16 dup(0)
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0;指向data:0
mov es,ax
mov di,16;指向data:16
mov cx,16;rep循环16次
cld;设置DF=0,正向传送
rep movsb
mov ax,4c00h
int 21h
code ends
end start
10.内中断
中断作用:CPU在运行过程中对外部事件发出的中断请求及时地进行处理,处理完成后,又立即返回断点,继续进行CPU原来的工作 引起中断的原因:发出中断请求的来源叫作中断源
根据中断源的不同,可以把中断分为:软件中断和硬件中断两大类
而硬件中断又可以分为外部中断和内部中断两类
中断的产生:
1.外部中断一般是指计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断
外部中断是可以屏蔽的中断,利用中断控制器可以屏蔽这些外部设备的中断请求
2.内部中断是指因硬件出错(如突然掉电、奇偶校验错等)或运算出错(除数为零、运算溢出、单步中断)所引起的中断
内部中断是不可屏蔽的中断
3.软件中断其实并不是真正的中断,他只是可被调用执行的一般程序
DOS的系统功能调用(int 21h)都是软件中断
CPU为了处理并发的中断请求,规定了中断的优先权,优先权由高到低的顺序是:
1.除法错、溢出中断、软件中断
2.不可屏蔽中断
3.可屏蔽中断
4.单步中断(debug)
中断向量表
中断向量表在内存中保存,其中存放着256个(8位中断类型码)中断源所对应的中断处理程序的入口 对于8086PC机,中断向量表指定放在内存地址0处 从0:0-0:03ffh的1024个字节【256*4,物理地址使用段地址和偏移地址存放,需要4个字节】中存放着中断向量表
中断过程 用中断类型码找到中断向量,并用它设置cs和ip,这个工作时由CPU的硬件自动完成的,CPU硬件完成这个工作的过程被称为中断过程
8086CPU的中断过程
1.(从中断信息中)取得中断类型码
2.标志寄存器的值入栈(保护标志位)
3.设置标志寄存器的第8位TF和第9位IF设置为0(后面讲解本步的目的)
4.cs内容入栈
5.ip内容入栈
6.从内存地址为中断类型码*4和中断类型码*4+2的两个子单元中
读取中断处理程序的入口地址设置cs和ip
对应汇编语言描述中断过程:
1.取得中断类型码N
2.pushf
3.TF=0,IF=0
4.push cs
5.push ip
6.(ip)=(N*4),(cs)=(N*4+2)
中断处理程序
CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系 使得CPU根据中断信息可以找到要执行的处理程序。
中断信息中包含有表示中断的类型码。中断类型码的作用就是用来定位中断处理程序的。
CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址,即中断类型码是中断向量在中断向量表中的索引
由于CPU随时都可能检测到中断信息,所以,CPU随时都可能执行中断处理程序,中断处理程序必须一直存储在内存某段空间中
中断处理程序的编写方法和子程序的比较类似
1.保存用到的寄存器
2.处理中断
3.恢复用到的寄存器
4.用iret指令返回
iret指令的功能用汇编语法执行:
pop ip
pop cs
popf
iret通常和硬件自动完成的中断过程配合使用
iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序
子程序与中断的区别
两者实现机制不同:
中断程序是固定的(如果操作系统允许,程序员可以修改);
而子程序是程序员动态编写的。
call func,根据func直接找到子程序入口,根据需求修改(IP)和(CS);
而int 21h需要计算地址,通过中断向量表找到中断处理程序入口地址。
11.int指令
int格式:int n ; n为中断类型码,它的功能是引发中断过程
CPU执行int n指令,相当于引发一个n号中断的中断过程,执行过程
1.取中断类型码
2.标志寄存器入栈,if=0,tf=0
3.cs,ip入栈
4.从此处转去执行n号中断的中断处理过程
注:可以在程序中使用int指令调用任何一个中断的中断处理程序, 也可以自己编写一些中断处理程序供别人使用
编写、安装中断7ch的中断例程
1.编写实现求平方功能的程序; 2.安装程序,安装在0:200处 3.设置中断向量表,将程序的入口地址保存在7ch表项中,使其成为中断7ch的中断例程。
用中断求求2*3456^2
assume cs:code
code segment
start:mov ax,3456 ;(ax)=3456
int 7ch ;调用中断7ch的中断例程,计算ax中的数据的平方
add ax,ax
adc dx,dx ;dx:ax存放结果,将结果乘以2
mov ax,4c00h
int 21h
code ends
end start
安装程序:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset sqr ;设置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov cx,offset sqrend-offset sqr ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb ;将处理程序复制到内存中
;设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
sqr: mul ax
iret
sqrend: nop
code ends
end start
BIOS和DOS中断例程
1.开机后,CPU一加电,初始化(cs)=0ffffh,ip=0,自动从ffff:0单元开始执行程序
ffff:0处有一条跳转指令,CPU执行该指令后,转去执行bios中的硬件系统的检测和初始化程序。
2.初始化程序将建立bios所支持的中断向量,即将bios提供的中断例程的入口地址登
记在中断向量表中。
3.硬件系统检测和初始化完成后,调用19h进行操作系统的引导。从此将计算机交由
操作系统控制。
4.DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应
的中断向量
BIOS中断例程的应用 1.int 10h中断例程是bios提供的中断例程,其中包含了多个和屏幕输出相关的子程序 一般来说,一个供程序员调用的中断例程中,往往包括多个子程序,中断例程内部用传递进来的参数来决定执行哪个子程序
int 10h中断例程的设置光标位置功能
mov ah,2 ;置光标
mov bh,0 ;第0页
mov dh,5 ;dh中放行号
mov dl,15 ;dl中放列号
int 10h
(ah)=2表示调用第10h号中断例程的2号子程序,功能为设置光标位置,可以提供光标
所在的行号(80*25子模式下:0-24)、列号(80*25子模式下:0-79)和页号作为参数。
bh中页号的含义:内存地址空间中,B8000H~BFFFFH共32KB的空间,为80*25彩色字符
模式的显示缓冲区。一屏的内容在显示缓冲区中共占4000个字节。
显示缓冲区分为8页,每页4KB,显示器可以显示任意一页的内容,一般情况下,显示第
0页的内容,也就是说,通常情况下,B8000H~B8F9FH中的4000个字节的内容将出现在
显示器上。
int 10h中断例程的在光标位置显示字符功能
mov ah,9 ;在光标位置显示字符
mov al,'a' ;字符
mov bl,7 ;颜色属性
mov bh,0 ;第0页
mov cx,3 ;字符重复个数
int 10h
(ah)=9表示调用的第10h号的中断例程的9号子程序,功能为在光标位置显示字符,可以
提供要显示的字符,颜色属性,页号字符重复个数作为参数
bl中的颜色属性的格式:
7 6 5 4 3 2 1 0
含义 BL R G B I R G B
闪烁 背景 高亮 前景
在屏幕的第5行12列显示3个红底高亮闪烁绿色的’a’:
assume cs:code
code segment
mov ah,2 ;设置光标
mov bh,0 ;第0页
mov dh,5 ;dh中放行号
mov dl,12 ;dl中放列号
int 10h
mov ah,9 ;在光标位置显示字符
mov al,'a' ;字符
mov bl,11001010b ;颜色属性
mov bh,0 ;第0页
mov cx,3 ;字符重复个数
int 10h
mov ax,4c00h
int 21h
code ends
end
bios和dos提供的中断例程,都用ah来传递内部子程序的编号
DOS中断例程应用 int 21h中断例程是dos提供的中断例程,其中包含了dos提供给程序员造编程时调用的子程序
前面一直使用的是int 21h中断例程的4ch号功能,即程序返回功能:
mov ah,4ch ;程序返回
mov al,0 ;返回值
int 21h
(ah)=4ch表示调用第21h号中断例程的4ch号子程序,功能为程序返回,可以提供返回
值作为参数
int 21h中断例程在光标位置显示字符串的功能:
ds:dx 指向字符串 ;要显示的字符串需要'$'作为结束符
mov ah,9 ;功能号9,表示在光标位置显示字符串
int 21h
(ah)=9表示调用第21h号中断例程的9号子程序,功能为在光标位置显示字符串,可以
提供要显示字符串的地址作为参数
在屏幕的第5行13列显示字符串"Welcome to masm!"
assume cs:code
data segment
db 'Welcome to masm','$'
data ends
code segment
start:mov ah,2 ;置光标
mov bh,0 ;第0页
mov dh,5 ;dh中放行号
mov dl,12 ;dl中放列号
int 10h
mov ax,data
mov ds,ax
mov dx,0 ;ds:dx指向字符串的首地址data:0
mov ah,9
int 21h
mov ax,4c00h
int 21h
code ends
end start
12.端口
各种存储器都和CPU的地址线、控制线、数据线相连。CPU将其看作一种连续的逻辑存储器,这些称之为内存地址空间
CPU可以直接读写以下的3个地方的数据:
CPU内部的寄存器
内存单元
端口
端口的读写
在访问端口的时候,CPU通过段口地址来定位端口。因为端口所在的芯片和CPU通过 总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。
对端口的读写不能用mov、push、pop等内存读写指令。端口的读写指令只有两条in(端口读取数据)和out(往端口写入数据)
CPU执行内存访问指令和端口访问指令
访问内存:
mov ax,ds:[8]
1.CPU通过地址线将地址信息8发出
2.CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据
3.存储器将8号单元中的数据通过数据线送入CPU
访问端口:
in al,60h; 从60h号端口读入一个字节
1.CPU通过地址线将地址信息60h发出
2.CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据
3.端口所在的芯片将60h端口中的数据通过数据线送入CPU
对0-255以内的端口进行读写
in al,20h ;从20h端口读一个字节
out 20h,al ;往20h端口写一个字节
对256-65535的端口进行读写时,端口号放在【dx】中
mov dx,3f8h ;将端口号3f8送入dx
in al,dx ;从3f8h端口读一个字节
out dx,al ;从3f8h端口写一个字节
注:在in和out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据,访问8位端口时用al,访问16位端口时用ax
CMOS RAM芯片
特点: 1.包含一个实时钟和一个有128个存储单元的RAM存储器
2.该芯片靠电池供电。因此,关机后其内部的实时钟仍可以正常工作,RAM中的信息不丢失
3.128字节的RAM中,内部实时钟占用0-0dh单元来保存时间信息,其余大部分分单元用于 保存系统配置信息,供系统启动时bios程序读取 bios也提供了相关的程序,使我们可以在开机的时候配置CMOS RAM中的系统信息
4.该芯片内部有两个端口,端口地址为70h和71h。CPU通过这两个端口读写CMOS RAM。
5.70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM单元中读取的数据或要写入到其中的数据
6.CMOS RAM中存储的时间信息 在CMOS RAM中存放着当前时间 秒:00h 分:02h 时:04h 日:07h 月:08h 年:09h 这6个信息的长度都为1个字节,这些数据以BCD码的方式存放,一个字节可以表示两个BCD码
CMOS RAM存储时间信息的单元中存储了用两个BCD码表示的两个十进制数,高4位的BCD码表示十位,低四位的BCD码表示个位
BCD码值=十进制数码值,则BCD吗值+30h=十进制数对应的ASCII码
在屏幕中间显示当前的月份:
1.从CMOS RAM的8号单元读取当前月份的BCD码
要读取CMOS RAM的信息,首先要向地址端口70h写入要访问的单元的地址
mov al,8
out 70h,al
然后从数据端口71h中取得指定单元中的数据:
int al,71h
2.将用BCD码表示的月份以十进制的形式显示在屏幕上
从CMOS RAM的8号单元读出的一个字节中,包含了用两个BCD码表示的两位十进制数,高4位的BCD码表示十位,低4位的BCD码表示个位
1)将从CMOS RAM的8号单元中读出的一个字节,分为两个表示BCD码值的数据
mov ah,al ;al中为从CMOS RAM的8号单元中读出的数据
mov cl,4
shr ah,cl ;ah中为月份的十位数码值
and al,00001111b ;al中为月份的个位数码值
2)显示(ah)+30h 和(al)+30h对应的ASCII码的字符
assume cs:code
code segment
start: mov al,8
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
add ah,30h
add al,30h
mov bx,0b800h
mov es,bx
mov byte ptr es:[160*12+40*2],ah ;显示月份的十位数码
mov byte ptr es:[160*12+40*2+2],al ;显示月份的个位数码
mov ax,4c00h
int 21h
code ends
end start
shl和shr指令
shl和shr是逻辑移位指令 逻辑右移一位,相当于执行X=X/2 逻辑左移一位,相当于执行X=X*2
shl逻辑左移指令,功能为:
1.将一个寄存器或内存单元中的数据向左移位
2.将最后移出的移位写入cf中
3.最低位用0补充
mov al,01001000b
shl al,1 ;将al中的数据左移一位
执行后(al)=100100000b,cf=0.
如果移动位数大于1时,必须将移动位数放在cl中
shr是逻辑右移指令,它的功能为:
将一个寄存器或内存单元中的数据向右移位;
将最后移出的一位写入CF中;
最高位用0补充。
13.外中断
CPU与外部设备
1.在PC系统的接口卡和主板上,装有各种接口芯片,这些外设接口芯片的内部装有若干寄存器,CPU将这些寄存器当做【端口】访问
2.外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的【端口】中
3.CPU向外设的输出也是要先送入【端口】中,再由相关芯片送入到外设
4.CPU可以向外设输出控制命令,这些控制命令也是先送到【端口】中,然后相关芯片根据命令进行相关工作
CPU与外部设备的交流是通过【端口】进行的 CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入
外中断信息
外中断源分为两类:
1.可屏蔽中断
2.不可屏蔽中断
可屏蔽中断是CPU可以不响应的外中断
不可屏蔽中断是CPU必须相应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完
当前指令后,立即响应,引发中断过程
CPU是否响应可屏蔽中断取决于标志寄存器的IF位的设置
当CPU检测到可屏蔽中断信息时:
1.若IF=1,则CPU在执行完当前指令后相应中断,引发中断过程
2.若IF=0,则不响应可屏蔽中断
设置IF的指令:
sti ;用于设置IF=1
cli ;用于设置IF=0
可屏蔽中断所引发的中断过程,除在第一步的实现上与内中断有所不同外,基本上和内中断的中断过程相同
因为可屏蔽中断信息来自于CPU外部,中断类型码是通过数据总线送入CPU的,而内中断的中断码是在CPU内部产生的
进入中断的时候,IF设置为0的原因:
在进入中断处理程序后,禁止其他的可屏蔽中断
如果中断处理程序中需要处理可屏蔽中断,可以用指令将IF设置为1
不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码
不可屏蔽中断的中断过程
1.标志寄存器入栈,IF=0,TF=0
2.CS,IP入栈
3.(IP)=(8),(CS)=(0AH) ;固定地址
不可屏蔽中断是系统中有必须处理的紧急情况发生时用来通知CPU的中断信息
几乎所有外中断,都是可屏蔽中断。当外设有需要处理的事件发生时,相关芯片向CPU发出可屏蔽中断信息。
PC机键盘的处理
键盘输入的处理过程:
1.键盘输入
2.引发9号中断
3.执行int 9中断例程
具体:
1.读出60H端口中的扫描码
2.如果是字符键的扫描码,将该扫描码对应的字符码(即:ASCII码)送入内存中的
BIOS键盘缓冲区
3.如果是控制键和切换键的扫描码,则将其转变为状态字节,写入内存中存储状态字节
的单元
4.键盘的输入到达60H端口时,相关的芯片会向CPU发出中断类型码为9的可屏蔽中断信
息。
5.CPU检测到中断信息后,如果IF=1,则相应中断,同时将IF设置为0(不让其他可屏
蔽中断进行干扰),引发中断过程,转去执行int9中断例程
键盘中断:
1.键盘上每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一触键的开关状
态进行扫描
2.按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明按下的键在键盘上
的位置,扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60H
3.松开控下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置,松开
按键时,产生的扫描码也被送入60H端口中。
按下一个键,产生的扫描码称为通码,松开一个键产生的扫描码称为断码 扫描码长度为一个字节,通码的第七位为0,断码的第七位为1 即:断码=通码+80H
BIOS键盘缓冲区是系统启动后,BIOS用于存放int9中断例程所接受的键盘输入的内存区
该内存区可以存储15个键盘输入,int9中断例程除了接收扫描码外,还要产生和扫描码对应的字符码 所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高字节存放扫描码,低字节存放字符码
0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态键盘状态字节各位记录的信息如下:
在代码段中使用标号来标记指令、数据、段的起始地址,这种标号只可以表示内存单元的地址
标号1
a : db 1,2,3,4,5,6,7,8
b : dw 0
注: 这种加有“:”的地址标号,只能在代码段中使用,不能在其他段中使用
另一种标号(数据标号)不但可以表示内存单元的地址,还表示了内存单元的长度 即:表示在此标号处的单元,是一个字节单元,还是字单元还是双字单元
a db 1,2,3,4,5,6,7,8 ;标号a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元
b dw 0 ;标号b描述了地址code:8,和从这个地址开始,以后的内存单元都是字单元
与第一个区别后面没有‘:’
此种标号既可以标记地址,也可以表示此标号处的单元
使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据
这种标号此后称为数据标号,它标记了存储数据的单元的地址和长度
15.键盘输入与磁盘读写
16.指令系统