目录
如有不正确的理解,请告知本人学习学习。谢谢!
问题:
1、为什么在定义栈段时,里面的数据通常都是0?
2、为什么在定义了栈段的程序中,程序加载后,在内存中处于栈段的位置中的数据又不是原先在栈段中定义的数据?(但在数据段中,却是原来数据段中定义的数据)
3、为什么在80X25彩色字符模式显示缓冲区中数据在没有通过程序改变的情况下会自动的变化。
4、书157页检测点13.1的第一题:我们用7ch中断例程实现loop的功能,则7ch中断例程所能进行的最大转移位移是多少?
5、在书299页实验16编写安装一个新的int7ch中断例程的时候,为什么程序的最后中要加上一条OGR 200H指令,为什么前几次编写的int7ch,int9等其他中断是不用加这条指令。
汇编语言学习笔记
说明:笔记取自王爽老师的教程
1.1、若有N根地址总线就有能够确定2的N次方个存储地址,因此也就能确定2的N次方个存储单元,所以也就能够确定2的N次方个字节,由此便能够根据地址总线算出存储容量。(微型计算机的一个存储单元可以存储一个(Byte)字节)。
1.2、用十六进制给存储器各个个字节编辑地址从0000到ffff,则该存储器的容量是多少?那么十六位二进制不就是两个字节吗?
答:该存储器的容量为2^16=64KB
十六位二进制的存储空间是两个字节,这与它表示地址范围是两概念,象我们平时说的0~9999有10000不同的地址,而最大的数9999也只有4位数。
2.1分别是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW、其中8个通用寄存器,每个寄存器可以存放16位二进制数据,分为高位和低位(AH、AL,BH、BL..........)。
2.2、80x86cpu中有4个段寄存器,分别为CS(code segment)、DS(data segment)、SS(stack segment)、ES(extra segment),CS(代码段寄存器)用来存放指令的段地址,通常与IP(指令指针寄存器)一起使用,IP寄存器用来存放指令的偏移地址,并且指向下一条指令的偏移地址。通常格式为(CS:IP)----通过JMP指令可以修改CS:IP的值。
2.3、2AE3:3通过(段地址 x 16 + 偏移地址)公式可得2AE3*16+3=2AE30+3=2AE33。
3:0B16通过(段地址 x 16 + 偏移地址)公式可得
3*16+0B16=0030+0B16=0B46。
2.4、DS寄存器通常用来存放要访问的段地址。
2.5、mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0。
3、怎样根据位地址判断字节地址?
答:将位地址除于8,取商丢掉余数(余数表示该字节的第几位),将商加#20H即是它的字节地址。如40H/8=08H,08H+20H=28H,则28H就是它的字节地址。位地址最大只到07FH,所以位地址没有80H。可以定义位地址的寄存器是:20H-2FH。
4.1、用debug的r命令可以查看、改变cpu寄存器的内容(通过r 寄存器名,然后按enter键,出现“:”作为输入提示,输入数据即可实现对cpu寄存器的内容的改写)
(eg格式: -r 或
-r ip
ip 0100
:200
)。
4.2、用debug的d命令查看内存中的内容(通过输入d 段地址:偏移地址进行查看)
(eg格式:-d 或
-d 1000:0 或
-d 1000:0 d (查看地址为10000H到1000D的内容)
)。
4.3、用debug的e命令改写内存中的内容
(eg格式: -e 1000:0 0 1 2 3 4 5 6 7 8 9 或
-e 1000:0 1 ‘a’ 2 ‘b’ 3 ‘c’ 或
-e 1000:10
1000:0010 6d.0 61.1 72.2 6b.1c
(6d.0是将原本内存单元中为6d的数据换成0)
)。
4.4、用debug的u命令查看内存中的机器指令翻译成汇编指令
(eg格式: -u 1000:0
)。
4.5、用debug的t命令执行一条机器指令
(eg格式: -t (每按下一下t就执行一条指令)
)。
4.6、用debug的a命令以汇编指令的格式在内存中写入一条机器指令
(eg格式: -a 1000:0
1000:0000 mov ax,1
1000:0003 mov ax,2
1000:0006 mov ax,3
1000:0009 add ax,bx
1000:000b add ax,cx
1000:000d add ax,ax
1000:000f
)。
4.7、用debug的p命令有个作用,其一:当程序执行到inc 21时,用p命令放回到debug中,表示程序正常结束,并显示program terminated normally;其二:当运行循环程序时,由于我们已经跟踪过程序的循环过程,但又不想一直跟着循环过程(如果循环10000次,那我们不得跟踪到天黑),所以这里的我们希望程序将循环一次执行(这里循环并不是只循环一次,而是希望循环能够在后天全部运行完,我们只需要看到最后的结果),就可以用p指令来达到目的。(用法:当再次遇到loop指令时,使用p指令来执行,debug就会自动重复指令循环中的指令,直到(cx)=0为止。
4.8、用debug的g命令可以从我们自己想要的地址单元出继续跟踪程序的运行,利用我们不想用t指令一步一步跟踪程序,而是想直接跳到后几步,则可以用-g XXXXh来进行跟踪(‘X’表是任意的字符)
5.1、8086cpu不支持将数据直接送入到段寄存器的操作,需要用到通用寄存器进行中转,如:不支持:mov ds 00ffh,必须要mov ax,00ffh.
mov ds,ax(通过通用寄存器中转,但不一定是ax寄存器,具体的视情况而定)
5.2、8086cpu不支持将段寄存器数据直接送入到段寄存器中的操作,需要用通用寄存器进行中转。
5.3、在汇编源程序中,数据不能以字母来头,所以对于像ffffh这样的数据前面就要加上0而变成0ffffh,因此像ffffh这样的数据就是不合法的。
6.1、入栈:cpu先执行sp=sp-2,再执行push ax指令将ax中的数据送入到ss:sp指向的内存单元处。
6.2、出栈:cpu先执行pop ax指令将ss:sp指向的内存单元处的数据送入到ax中,再sp=sp+2,
6.3、当用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先出栈。
6.4、一般来说,在需要暂存数据的时候,我们都应该使用栈。
6.5、栈最大的好处就是能够很好的解决寄存器冲突或者寄存器不足的问题。
7.1、对于数据段,将它的短地址放在DS中,用mov,add,sub等访问内存单元的指令时,cpu就将我们定义的数据段中的内容当做数据来访问。
7.2、对于代码段,将它的短地址放在CS中,将段中的第一条指令的偏移地址放在IP中,cpu就将执行我们定义的代码段中的指令。
7.3、对于栈段,将它的短地址放在SS中,将栈顶单元的偏移地址放在SP中,这样cpu在需要进行栈操作的时候,比如push,pop指令等,就将我们定义的栈段当做栈空间来用。
7.4、可见,不管我们如何安排,cpu将内存中的某段内容当作代码,是因为CS:IP指向了那里;cpu将某段内存当作栈,是因为SS:SP指向了那里。我们一定要清楚,什么是我们的安排,以及如何让cpu按我们的安排行事,要非常清楚cpu的工作机理,才能在控制cpu按照我们的安排运行的时候做到游刃有余。
7.5、8086cpu的入栈与出栈操作都是以字为单位进行的。
7.6、8086cpu可以用dw开辟段空间:
对于描述dw的作用:可以说用来定义数据,也可以用它开辟内存空间,比如:
dw 0123h,0456h,0789h,0abch,0defh,0fdeh,0cbah,0987h
可以说,定义了8个字型数据,也可以说,开辟了8个字的内存的空间,这段空间中的每一个字单元中的数据依次是:0123h,0456h,0789h,0abch,0defh,0fdeh,0cbah,0987h。因为他们最终的效果是一样的。
7.7、在8088/8086 系统中每个段最小是16字节最大是64K字节。
7.8、在mov al,[bx]指令中,其中al默认的段地址为ds。
DEBUG的命令T在执行修改寄存器SS的指令时,下一条指令也紧接着被执行(如:mov ss,ax
mov sp,10)
当mov ss,ax 执行后时,后一条指令mov sp 10也紧接着被行,在用debug调试时,只会显示sp的值的变化,但看不到mov sp 10指令想其他指令一样清晰的执行。
Debug和汇编编译器masm对形如mov ax [0] 这类的指令在解释上是不同的,我们在debug中和源程序中写入同样形式的指令:mov al,[0]、mov bl,[1]、mov cl,[2]、mov dl,[3],但debug和编译器对这些指令中的[idata]却有不同的解释,debug将它解释为[idata]是一个内存单元,idata是内存单元的偏移地址;而编译器将[idata]解释为idata.
Dos方式下,一般情况,0:200-0:2ff空间中没有系统或其他程序的数据或代码,所以被认为是安全的。
11、8086cpu的工作过程:(一定要理解并谨记)
(1)从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器。
(2)IP=IP+所读取的指令的长度,从而指向下一条指令。
(3)执行指令,转到步骤(1),重复这个过程。
简单总结一句话:当执行当前指令前,会先将下一条指令的机器码加载到指令缓冲区(也就是说IP指令将会指向下一条指令的地址)
在有多个段组成的程序中,例如有data段,stack段,code段等的程序中,段名就相当于一个标号,它代表了段地址,并且通过指令”mov ax data/stack/code“将data段/stack段/code段的段地址送入ax中。
一个段中的数据的段地址可由段名称代表,但偏移地址就要看它在段中的具体位置了。
灵活的定位内存的地址:
(1)、[bx+idata]:mov ax,[bx+200]
数学化的描述为:(ax) = ((ds)*16+(bx)+200)
改指令也可以改写成如下格式:
Mov ax,[200+bx]
Mov ax,200[bx]
Mov ax,[bx].200
(2)、si和di是8086cpu中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。既可以单独的使用,也可以与bx配合的来使用。格式:
Mov ax [bx+200+si]
Mov ax [200+bx+si]
Mov ax 200[bx][si]
Mov ax [bx].200[si]
Mov ax [bx][si].200
有一条有si和di搭配的指令是错误的:
mov ax,[si+di],不能这样搭配。
还有一条bx与bp搭配的指令是错误的:
mov ax,[bx+bp],不能这样搭配。
灵活的理解:
(3)、如果我们比较一下前而用到的几种定位内存地
址的方法(可称为寻址方式),就可以发现有
以下几种方式:
n (3-1)[iata] 用一个常量来表示地址,可用于直接定
位一个内存单元;
n (3-2)[bx]用一个变量来表示内存地址,可用于间接
定位一个内存单元;
n (3-3)[bx+idata] 用一个变量和常量表示地址,可在
一个起始地址的基础上用变量间接定位一个内存单
元;
n (3-4)[bx+si]用两个变量表示地址;
n (3-5)[bx+si+idata] 用两个变量和一个常量表示地
址。
(4)、我们进行一下总结:
我们将使用两个描述性的符号 reg来表示一个寄存器,用sreg表示一个段寄存器。
n reg的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
n sreg的集合包括:ds、ss、cs、es。
n(5-1)在8086CPU 中,只有这4个寄存器(bx、bp、si、di)可以用在“[…]” 中来进行内存单元的寻址。
(5-2)在“[…]” 中,这4个寄存器(bx、bp、si、di)可以单个出现,或只能以四种组合出现:bx和si、bx和di、bp和si、bp和di
(5-3)只要在[…]中使用寄存器bp,而指令中没有显性的给出段地址,段地址就默认在ss中。比如:
n mov ax,[bp] 含义: (ax)=((ss)*16+(bp))
n mov ax,[bp+idata] 含义:(ax)=((ss)*16+(bp)+idata)
n mov ax,[bp+si] 含义:(ax)=((ss)*16+(bp)+(si))
n mov ax,[bp+si+idata] 含义:(ax)=((ss)*16+(bp)+(si)+idata
对于add、sub、mov指令,可以有以下几种形式:
add 寄存器,数据 比如:add ax,8
add 寄存器,寄存器 比如:add ax,bx
add 寄存器,内存单元 比如:add ax,[0]
add 内存单元,寄存器 比如:add [0],ax
sub 寄存器,数据 比如:sub ax,9
sub 寄存器,寄存器 比如:sub ax,bx
sub 寄存器,内存单元 比如:add ax,[0]
sub 内存单元,寄存器 比如:sub [0],ax
这里主要讲的是:这里的指令支持直接到内存里面的操作,像add 内存单元,寄存器 比如:add [0],ax
理解了这些,对于直接定址表一章中的在段中使用标号后,引用其标号的偏移地址与段地址。也能更好的理解利用键盘对字符串的输入的程序(书304面),
div是除法指令,使用div作除法的时候:
n 除数:8位或16位,在寄存器或内存单元中
n 被除数:(默认)放在AX 或 DX和AX中
n 结果:运算 8位 16位
商 AL AX
余数 AH DX
说明:在做除法运算时,如果除数为8位,被除数则为16位,并且被除数默认在AX中存放,如果除数为16位,则被除数则为32位,被除数默认存放在DX和AX中,DX存放高16位,AX存放低16。
但是特别要注意的是:做8位除法的时候,除法指令应该这样写:
div al 而不能这样写div ax 因为如果写成第二种,则指令会默认成16位的除法,这样就会导致最后除法运算得到的余数和商放在不是我们想要的地方,还有就是要避免除法溢出(这个后面将会讲到)
mul是乘法指令,使用 mul 做乘法的时候:
n (1)相乘的两个数:要么都是8位,要么都是16位。
8 位: AL中和 8位寄存器或内存字节单元中;
16 位: AX中和 16 位寄存器或内存字单元中。
(2)结果
8位:AX中;
16位:DX(高位)和AX(低位)中
db 3 dup (0)
定义了3个字节,它们的值都是0,
相当于 db 0,0,0
(1)jmp指令
jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP;
jmp指令要给出两种信息:
n 转移的目的地址
n 转移的距离(段间转移、段内短转移,段内近转移)
(2)依据位移进行转移的jmp指令:
jmp short 标号(转到标号处执行指令)
一定要理解什么是根据位移进行转移,建议根据实验8,分析一个奇怪的程序来深刻理解,关于这个实验的非常好的讲解可以查看链接http://zhidao.baidu.com/link?url=jc3OUbu0Xeb5us1CEQ946FXr-au56_-ElMTItSjtETFk1e1u2lbeicpT-FzeQgbonM4gqZBOvUlE1E1eT7zdLK,这里讲的非常好。
这种格式的 jmp 指令实现的是段内短转移,它对IP的修改范围为 -128~127,也就是说,它向前转移时可以最多越过128个字节,向后转移可以最多越过127个字节。
指令“jmp short 标号”的功能为(IP)=(IP)+8位位移。
n (2-1)8位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
n (2-2)short指明此处的位移为8位位移;
n (2-3)8位位移的范围为-128~127,用补码表示(如果你对补码还不了解,请阅读附注2)
n (2-4)8位位移由编译程序在编译时算出。
jmp near ptr 标号
它实现的时段内近转移。
指令“jmp near ptr 标号”的说明:
n (2-1)16位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
n (2-2)near ptr指明此处的位移为16位位移,进行的是段内近转移;
n (2-3)16位位移的范围为-32769~32767,用补码表示;
n (2-4)16位位移由编译程序在编译时算出。
(3)、依据目的地址进行转移的jmp指令:
前面讲的jmp指令,其对应的机器码中并没有转移的目的地址,而是
相对于当前IP的转移位移。
指令 “jmp far ptr 标号”实现的是段间转移,又称为远转移
指令 “jmp far ptr 标号” 功能如下:
n (CS)=标号所在段的段地址;
n (IP)=标号所在段中的偏移地址。
n far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
(4)、 转移地址在内存中的jmp指令
(4-1)转移地址在内存中的jmp指令有两种格式:
jmp word ptr 内存单元地址(段内转移)
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
内存单元地址可用寻址方式的任一格式给出。
(4-2)转移地址在内存中的jmp指令的第二种格式:
jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。
(CS)=(内存单元地址+2)
(IP)=(内存单元地址)
内存单元地址可用寻址方式的任一格式给出。
jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。
指令格式:jcxz 标号(如果(cx)=0,则转移到标号处执行)
当(cx)=0时,什么也不做(程序向下执行)。
loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。
n 指令格式:loop 标号
((cx))=(cx)-1,如果(cx)≠0,转移到标号处执行
当(cx)=0,什么也不做(程序向下执行)
ret指令用栈中的数据,修改IP的内容,从而实现近转移
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移;
可以看出,如果我们用汇编语法来解释ret和retf指令,则:
n CPU执行ret指令时,相当于进行:
pop IP
n CPU执行retf指令时,相当于进行:
pop IP
pop CS
CPU执行call指令,进行两步操作:
n (1)将当前的 IP 或 CS和IP 压入栈中;
n (2)转移。
n call 指令不能实现短转移,除此之外,call
指令实现转移的方法和 jmp 指令的原理相
同
8.1依据位移进行转移的call指令
call 标号(将当前的 IP 压栈后,转到标号处执行指令)
n CPU执行此种格式的call指令时,进行如下的操作:
n (1) (sp) = (sp) – 2
((ss)*16+(sp)) = (IP)
n (2) (IP) = (IP) + 16位位移
指令格式:call 标号
n 16位位移=“标号”处的地址-call指令后的第一个字节的地址;
n 16位位移的范围为 -32768~32767,用补码表示;
n 16位位移由编译程序在编译时算出
CPU 执行指令“call 标号”时,相当于进行:
push IP
jmp near ptr 标号
8.2、转移的目的地址在指令中的call指令
指令“call far ptr 标号”实现的是段间转移。
CPU执行“call far ptr 标号”这种格式的call指令时的操作:
(1) (sp) = (sp) – 2
((ss) ×16+(sp)) = (CS)
(sp) = (sp) – 2
((ss) ×16+(sp)) = (IP)
(2) (CS) = 标号所在的段地址
(IP) = 标号所在的偏移地址
CPU 执行指令 “call far ptr 标号”时,相当于进行:
push CS
push IP
jmp far ptr 标号
8.3、转移地址在寄存器中的call指令
call 16位寄存器
n 功能:
n (sp) = (sp)–2
n ((ss)*16+(sp))=(IP)
n (IP)=(16位寄存器)
汇编语法解释此种格式的 call 指令,CPU执行call 16位reg时,相当于进行:
push IP
jmp 16位寄存器
8.3、 转移地址在内存中的call指令
(1)call word ptr 内存单元地址
n 汇编语法解释:
push IP
jmp word ptr 内存单元地址
(2)call dword ptr 内存单元地址
n 汇编语法解释:
push CS
push IP
jmp dword ptr 内存单元地址
8.4、 call 和 ret 的配合使用
子程序的框架:
标号:
指令
ret
具有子程序的源程序的框架:
assume cs:code
code segment
main: :
:
call sub1 ;调用子程序sub1
:
:
mov ax,4c00h
int 21h
sub1: : ;子程序sub1开始
:
call sub2 ;调用子程序sub2
:
:
ret ;子程序返回
sub2: : ;子程序sub2开始
:
:
ret ;子程序返回
code ends
end main
adc是带进位加法指令 ,它利用了CF位上记录的进位值。
n 格式: adc 操作对象1,操作对象2
n 功能:操作对象1=操作对象1+操作对象2+CF
n 比如:adc ax,bx 实现的功能是:
(ax)=(ax)+(bx)+CF
Adc指令的意义所在:
adc指令和add指令相配合就可以对更大的数据进行加法运算。
sbb是带错位减法指令,它利用了CF位上记录的借位值。
n 格式:sbb 操作对象1,操作对象2
n 功能:操作对象1=操作对象1–操作对象2–CF
n 比如:sbb ax,bx
实现功能:(ax)=(ax)–(bx)–CF
sbb和adc是基于同样的思想设计的两条指令,在应用思路上和adc类似。
cmp 是比较指令,功能相当于减法指令,只是不保存结果。
cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp指令
n 格式:cmp 操作对象1,操作对象2
n 功能:计算操作对象1–操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置
cmp一般和有条件转移指令配合起作用:
下面是常用的无符号数的比较结果进行转移的条件转移指令:
指令 含义 检测的相关标志位
je 等于则转移 zf=1
jne 不等于则转移 zf=0
jb 低于则转移 cf=1
jnb 不低于则转移 cf=0
ja 高于则转移 cf=0且zf=0
jna 不高于则转移 cf=1或zf=1
这些指令比较常用,它们都很好记忆,它们的第一个字母都是j,表示jump;后面的:
n e:表示equal;
n ne:表示not equal;
n b:表示below;
n nb:表示not below;
n a:表示above;
n na:表示not above。
我们可以直接考虑cmp和je等指令配合使用时,表现出来的逻辑含义。
n 它们在联合使用的时候表现出来的功能有些像高级语言中的IF语句。
n flag的第10位是DF,方向标志位。在串处理指令中,控制每次操作后si,di的增减。
n DF = 0:每次操作后si,di递增;
n DF = 1:每次操作后si,di递减。
格式1: movsb
n 功能:(以字节为单位传送)
n (1) ((es)×16 + (di)) = ((ds) ×16 + (si))
n (2) 如果DF = 0则: (si) = (si) + 1
(di) = (di) + 1
如果DF = 0则:(si) = (si) - 1
(di) = (di) - 1
我们可以用汇编语法描述movsb的功能
如下:
mov es:[di],byte ptr ds:[si];8086 并不支持这样的指令,这里只是个描述。
n 如果DF=0: inc si
inc di
n 如果DF=1: dec si
dec di
可以看出,movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:di中,然后根据标志寄存器DF位的值,将 si和di递增或递减。
当然,也可以传送一个字, movsw指令
格式2:movsw
n 功能:(以字为单位传送)将 ds:si指向的内存字单元中word送入es:di中,然后根据标志寄存器DF位的值,将si和di递增2或递减2。
我们可以用汇编语法描述movsw的功能如下:
mov es:[di],word ptr ds:[si];8086 并不支持这样的指令,这里只是个描述。
n 如果DF=0: add si,2
add di,2
n 如果DF=1: sub si,2
sub di,2
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都和rep配合使用,格式如下:
n rep movsb
用汇编语法来描述rep movsb的功能就是:
s : movsb
loop s
n rep movsw
用汇编语法来描述rep movsw的功能就是:
s : movsw
loop s
8086CPU提供下而两条指令对DF位进行设置:
n cld指令:将标志寄存器的DF位置0
n std指令:将标志寄存器的DF位置1
pushf :将标志寄存器的值压栈;
n popf :从栈中弹出数据,送入标志寄存器中。
n pushf和popf,为直接访问标志寄存器提供了一种方法。
指令系统总结
我们对8086CPU 的指令系统进行一下总结。读者若要详细了解8086 指令系统中的各个指令的用法,可以查看有关的指令手册。
n 8086CPU 提供以下几大类指令
1、数据传送指令
比如:
mov、push、pop、pushf、popf、xchg等都是数据传送指令,这些指令实现寄存器和内存、寄存器和寄存器之间的单个数据传送。
2、算术运算指令
比如:
add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa等都是算术运算指令,这些指令实现寄存器和内存中的数据的算数运算。它们的执行结果影响标志寄存器的:sf、zf、of、cf、pf、af位。
3、逻辑指令
比如:
and、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr 等都是逻辑指令。除了not指令外,它们的执行结果都影响标志寄存器的相关标志位。
4、转移指令
可以修改IP ,或同时修改CS 和IP 的指令统称为转移指令。转移指令分为以下几类:
(1)无条件转移指令,比如:jmp;
(2)条件转移指令,比如:jcxz、je、jb、ja、jnb、jna等;
(3)循环指令,比如:loop;
(4)过程,比如:call、ret、retf;
(5)中断,比如int、iret。
5、处理机控制指令
这些指令对标志寄存器或其他处理机状态进行设置,比如:cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等都是处理机控制指令。
6、串处理指令
这些指令对内存中的批量数据进行处理比如:movsb、movsw、cmps、scas、lods、stos等。若要使用这些指令方便地进行批量数据的处理,则需要和rep、repe、repne等前缀指令配合使用。
1、8086CPU的flag寄存器的结构:
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
|
|
OF |
DF |
IF |
TF |
SF |
ZF |
|
AF |
|
PF |
|
CF |
flag的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11位都具
有特殊的含义。
(1)、ZF标志
flag的第6位是ZF,零标志位。它记录相关指令执行后,
结果为0 ,ZF = 1
结果不为0,ZF = 0
(2)、PF标志
n flag的第2位是PF,奇偶标志位。它记录指令执行后,结果的所有二进制位中1的个数:
n 为偶数,PF = 1;
n 为奇数,PF = 0。
(3)、SF标志
n flag的第7位是SF,符号标志位。它记录指令执行后,
n 结果为负,SF = 1;
n 结果为正,SF = 0。
(4)、CF标志
n flag的第0位是CF,进位标志位。
n 一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
(5)、OF标志
n 由于在进行有符号数运算时,可能发生溢出而造成结果的错误。则CPU需要对指令执行后是否产生溢出进行记录。
n flag的第11位是OF,溢出标志位
一般情况下,OF记录了有符号数运算的结果是否发生了溢出。
n 如果发生溢出,OF=1,
n 如果没有,OF=0。
数值在计算机中表示方法为其补码,因为只有补码才能完整表示0,而不像原码和反码一样会出现+0与-0的现象,所以计算机就用补码表示,所以此处在cpu计算溢出与不溢出的时候是用的补码运算的不适用的原码运算的:
例如:mov al,0fah ;fah,为有符号数-6的补码
add al,088h ;88h,为有符号数-120的补码
此处运算的结果不会导致溢出,所以of标志位置为0,因为在以补码进行运算时结果为-126,在8位寄存器中能放下。8为寄存器中只能存放范围为-128~127有符号数。
但是如果以原码进行运算那么结果为386,那么就超出了所能表示的范围。
一定要注意CF和OF的区别:
CF是对无符号数运算有意义的标志位;
而OF是对有符号数运算有意义的标志位
对于8086CPU,当内部有下面情况发生的时候,将产生中断信息:
n 1、除法错误,比如:执行div指令产生的除法溢出;
n 2、单步执行;
n 3、执行into指令;
n 4、执行int 指令。
上述的4种中断源,在8086CPU中的中断类型码如下:
n (1)除法错误:0
n (2)单步执行:1
n (3)执行 int0 指令:
n (4)执行 int 指令 ,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。
CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。我们知道,中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。
CPU用 8 位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。那么什么是中断向量表呢?中断向量表就是中断向量的列表。中断向量表在内存中保存,其中存放着 256个中断源所对应的中断处理程序的入口
8086CPU的中断过程:
n (1)(从中断信息中)取得中断类型码;
n (2)标志寄存器的值入栈( 因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中。);
n (3)设置标志寄存器的第8位TF 和第9位IF的值为0;(这一步的目的后面将介绍)
n (4)CS的内容入栈;
n (5)IP的内容入栈;
n (6)从内存地址为中断类型码*4 和中断类型码 *4+2 的两个字单元中读取中断处理程序的入口地址设置IP和CS。(设置中断向量表,便于程序调用)
我们更简洁的描述中断过程,如下:
n (1)取得中断类型码N;
n (2) pushf
n (3) TF = 0,IF = 0
n (4) push CS
n (5) push IP
n (6)(IP) = (N*4),(CS) = (N*4+2)
n 在最后一步完成后,CPU 开始执行由程序员编写的中断处理程序。
中断处理程序的编写方法和子程序的比较相似,下面是常规的步骤:
n (1)保存用到的寄存器。
n (2)处理中断。
n (3)恢复用到的寄存器。
n (4)用 iret 指令返回。
n iret指令的功能用汇编语法描述为:
pop IP
pop CS
popf
iret通常和硬件自动完成的中断过程配合使用。可以看到,在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP ,而iret的出栈顺序是 IP、CS、标志寄存器,刚好和其对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。
ret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。
int格式: int n,n为中断类型码。它的功能是引发中断过程。
CPU 执行int n指令,相当于引发一个 n号中断的中断过程,执行过程如下:
n (1)取中断类型码n;
n (2)标志寄存器入栈,IF = 0,TF = 0;
n (3)CS、IP入栈;
n (4)(IP) = (n*4),(CS) = (n*4+2)。
n 从此处转去执行n号中断的中断处理程序
可以在程序中使用int指令调用任何一个中断的中断处理程序。
n 比如,下面的程序:
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov byte ptr es:[12*160+40*2],‟!‟
int 0
code ends
end start
这个程序在 Windows 2000中的 DOS方式下执行时,将在屏幕中间显示一个“!”,然后显示“Divide overflow”后返回到系统中。
n“!”是我们编程显示的,而,“Divide overflow”是哪里来的呢?我们的程序中又没有做除法,不可能产生除法溢出。程序是没有做除法,但是在结尾使用了int 0指令。CPU执行int 0指令时,将引发中断过程,执行 0号中断处理程序,而系统设置的 0号中断处理程序的功能是显示“Divide overflow”,然后返回到系统。
可见,int 指令的最终功能和call指令相似,都是调用一段程序。
n一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。
我们在编程的时候,可以用int指令调用这些子程序。
当然,也可以自己编写一些中断处理程序供别人使用。
一般来说,一个供程序员调用的中断例程中往往包括多个子程序,中断例程内部用传递进来的参数来决定执行哪个子程序。
BIOS 和DOS 提供的中断例程,都用ah来传递内部子程序的编号
int 10h中断例程是BIOS提供的中断例程,其中包含了多个和屏幕输出相关的子程序。
我们看一下int 10h中断例程的设置光标位置功能。
mov ah,2
mov bh,0
mov dh,5
mov dl,12
int 10h
(ah)=2表示调用第 10h号中断例程的 2号子程序,功能为设置光标位置,可以提供光标所在的行号(80*25字符模式下:0~24)、列号(80*25字符模式下:0~79),和页号作为参数。(bh)=0,(dh)=5,(dl)=12,设置光标到第0页,第5行,第12列。
bh中页号的含义:内存地址空间中,B8000h~BFFFFh共 32K的空间,为80*25 彩色字符模式的显示缓冲区。一屏的内容在显示缓冲区*占4000个字节。
显示缓冲区分为8页,每页4K(≈4000),显示器可以显示任意一页的内容。一般情况下,显示第 0 页的内容。也就是说,通常情况下,B8000~B8F9F中的4000个字节的内容将出现在显示器上。
int 21h 中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序。我们从前一直使用的是 int 21中断例程的4ch号功能,即程序返回功能,如下:
mov ah,4ch ;程序返回
mov al,0 ;返回值
int 21h
(ah)=4ch表示调用第21h号中断例程的 4ch号子程序,功能为程序返回,可以提供返回值作为参数。我们前面使用这个功能的时候经常写作:
mov ax,4c00h
int 21h
我们看一下int 21h中断例程的在光标位置显示字符串的功能:ds:dx指向字符串 ;要显示的字符串需用“$”作为结束符mov ah ,9 ;功能号9,表示在光标位置显示字符串int 21h
(ah)=9表示调用第21h号中断例程的 9号子程序,功能为在光标位置显示字符串,可以提供要显示字符串的地址作为参数。
n 对端口的读写不能用mov、push、pop等内存读写指令。
n 端口的读写指令只有两条:in和out,分别用于从端口读取数据和往端口写入数据。
访问端口:
n in al,60h;从60h号端口读入一个字节
n 执行时与总线相关的操作:
① CPU通过地址线将地址信息60h发出;
② CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
③ 端口所在的芯片将60h端口中的数据通过数据线送入CPU。
注意:在in和out 指令中,只能使用 ax 或al 来存放从端口中读入的数据或要发送到端口中的数据。访问8 位端口时用al ,访问16 位端口时用ax。
对0~255以内的端口进行读写:
in al,20h ;从20h端口读入一个字节
out 20h,al ;往20h端口写入一个字节
n 对256~65535的端口进行读写时,端口号放在dx中:
mov dx,3f8h ;将端口号3f8送入dx
in al,dx ;从3f8h端口读入一个字节
out dx,al ;向3f8h端口写入一个字节
PC机中有一个CMOS RAM芯片,其有如下特征:
n (1)包含一个实时钟和一个有128个存储单元的RAM存储器。早期的计算机为64个字节)
(2)该芯片靠电池供电。所以,关机后其内部的实时钟仍可正常工作, RAM 中的信息不丢失。
(3)128 个字节的 RAM 中,内部实时钟占用 0~0dh单元来保存时间信息,其余大部分分单元用于保存系统配置信息,供系统启动时BIOS程序读取。BIOS也提供了相关的程序,使我们可以在开机的时候配置CMOS RAM 中的系统信息。
(4)该芯片内部有两个端口,端口地址为70h和71h。CPU 通过这两个端口读写CMOS RAM。
(5)70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM 单元中读取的数据,或要写入到
其中的数据。
可见,CPU对CMOS RAM的读写分两步进行。
n 比如:读CMOS RAM的2号单元:
n 1、将2送入端口70h
n 2、从71h读出2号单元的内容
在PC 系统中,外中断源一共有两类:
n 1、可屏蔽中断
n 2、不可屏蔽中断
可屏蔽中断是CPU 可以不响应的外中断。CPU 是否响应可屏蔽中断,要看标志寄存器的IF 位的设置。
n 当CPU 检测到可屏蔽中断信息时:
n 如果IF=1,则CPU 在执行完当前指令后响应中断,引发中断过程;
n 如果IF=0,则不响应可屏蔽中断。
解释中断过程中将IF置为0的原因了。将IF置0的原因就是,在
进入中断处理程序后,禁止其他的可屏蔽中断。当然,如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1 。
8086CPU 提供的设置IF的指令如下:
n sti,用于设置IF=1;
n cli,用于设置IF=0。
不可屏蔽中断是CPU 必须响应的外中断。当CPU 检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。对于8086CPU 不可屏蔽中断的中断类型码固定为2。所以中断过程中,不需要取中断类型码。
不可屏蔽中断的中断过程:
n 1、标志寄存器入栈,IF=0,TF=0;
n 2、CS、IP入栈;
n 3、(IP)=(8),(CS)=(0AH)
几乎所有由外设引发的外中断,都是可屏蔽中断。当外设有需要处理的事件(比如说键盘输入)发生时,相关芯片向CPU 发出可屏蔽中断信息。
n 不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知CPU 的中断信息。在我们的课程中,主要讨论可屏蔽中断。
键盘的输入到达60H 端口时,相关的芯片就会向CPU 发出中断类型码为9的可屏蔽中断信息。
n CPU检测到该中断信息后,如果IF=1,
则响应中断,引发中断过程,转去执行int 9中断例程。
int 9中断是个很重要的中断,要理解并掌握。
我们已经讲过,键盘输入将引发9号中断,BIOS 提供了int 9 中断例程。CPU 在9 号中断发生后,执行int 9中断例程,从60h 端口读出扫描码,并将其转化为相应的ASCII 码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。
一般的键盘输入,在CPU 执行完int9中断例程后,都放到了键盘缓冲区中。键盘缓冲区中有16 个字单元,可以存储15个按键的扫描码和对应的入ASCII码。
下面的指令从键盘缓冲区中读取一个键盘输入,并且将其从缓冲区中删除:
mov ah,0
int 16h
结果:(ah)=扫描码,
(al)=ASCII码。
从上面我们可以看出,int 16h 中断例程的 0 号功能,进行如下的工作:
n (1)检测键盘缓冲区中是否有数据;
n (2)没有则继续做第1 步;
n (3)读取缓冲区第一个字单元中的键盘输入;
n (4)将读取的扫描码送入ah,ASCII 码送入al;
n (5)将己读取的键盘输入从缓冲区中删除。
可见,B1OS 的int 9 中断例程和int 16h 中断例程是一对相互配合的程序,int 9 中断例程向键盘缓冲区中写入,int 16h 中断例程从缓冲区中读出。它们写入和读出的时机不同,int 9 中断例程在有键按下的时候向键盘缓冲区中写入数据;而int 16h 中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。
BIOS 提供的访问磁盘的中断例程为int 13h 。如下,读取0面0道1扇区的内容到0:200:
mov ax,0
mov es,ax
mov bx,200h
mov al,1
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,2
int 13h
入口参数:
n (ah)=int 13h的功能号(2表示读扇区)
n (al)=读取的扇区数
n (ch)=磁道号
n (cl)=扇区号
n (dh)=磁头号(对于软驱即面号,因为一个面用一个磁头来读写)
n (dl)=驱动器号软驱从0开始,0:软驱A,1:软驱B;硬盘从80h开始,80h:硬盘C,81h:硬盘D。
es:bx指向接收此扇区读入数据的内存区
返回参数:
n 操作成功: (ah)=0,(al)=读入的扇区数
n 操作失败: (ah)=出错代码
注意:
下面我们要使用int 13h 中断例程对软盘进行读写。直接向磁盘扇区写入数据是很危险的,很可能覆盖掉重要的数据。如果向软盘的0 面0 道1 扇区中写入了数据,要使软盘在现有的操作系统下可以使用,必须要重新格式化。
总结:在王爽老师一书中,有关内中断,外中断,int指令,端口,直接定址表,使用BIOS进行键盘输入和磁盘读写等内容一定要理解并掌握,这些都是能够很好理解计算机底层的相关知识,一定要重点看。