@v4lour
汇编语言是直接在硬件上工作的编程语言,由以下3类指令组成:
- 汇编指令:机器码的助记符,有对应的机器码
- 伪指令:没有对应的机器码,由编译器执行
- 其他符号:如+、-等,由编译器识别,没有对应的机器码
汇编语言的核心是汇编指令。
指令和数据是应用上的概念。在内存或磁盘中,指令和数据没有任何区别,都是二进制信息。
微机存储器的容量是以字节为最小单位来计算的。一个存储单元可以存储8个bit,即8位二进制。
CPU在读写数据时要指明对哪一个器件进行操作、进行哪种操作、是读还是写操作。
CPU中的主要部件是寄存器,程序员通过改变各种寄存器中的内容来实现对CPU的控制。
8086CPU有14个寄存器——AX, BX, CX, DX, SI, DI, SP, BP, IP, CS, SS, DS, ES, PSW。
一个字节byte有8位,可以存放在8位寄存器中;一个字word有16位,可以存放在16位寄存器中。
CPU访问内存单元时,要给出内存单元地址。所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址。
物理地址 = 段地址 * 16 + 偏移地址
CS和IP是8086CPU中最重要的两个寄存器。任何时刻,CPU将CS:IP指向的内容当作指令执行。
CPU中用16位寄存器来存储一个字,高8位存放高字节,低8位存放低字节。而在内存中存储时,由于内存单元是字节单元,则一个字需要两个地址连续存放,低位字节存放在低地址单元中,高字节存放在高地址单元中。
push ax表示将寄存器ax中的数据送入栈中,pop ax表示从栈顶取出数据送入ax。
8086CPU的入栈和出栈操作都是以字为单位进行的。
任意时刻SS:SP指向栈顶元素。
push、pop操作本质上只是修改SP值。
8086CPU中不能直接向段寄存器中送入数据,需要寄存器ax作为中转。
汇编程序从写出到执行的过程:编程(Edit)——1.asm——编译(Masm)——1.obj——连接(Link)——1.exe——加载(Command)——内存中的程序——运行(CPU)
程序加载后ds中存放着程序所在内存区的段地址,其偏移地址为0,则程序所在的内存区地址为ds:0;这个内存区的前256个字节中存放的是PSP(程序段前缀),DOS用其和程序进行通信。从256个字节向后的空间存放的是程序。即程序的物理地址是SA*16+0+256=(SA+16)*16+0,用段地址和偏移地址表示为SA+10H:0。
loop指令实现循环功能,cx中存放循环次数。
在汇编源程序中数据不能以字母开头。
DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码。
编程的时候需要考虑用多个段来存放数据、代码和栈。用和定义代码段一样的方法来定义多个段,然后在这些段里定义需要的数据,或通过定义数据来取得栈空间。
and指令,将操作对象的相应位设为0;or指令,将操作对象的相应位设为1。
字母大小写转化问题:小写字母比大写字母大20H;大写字母ASCII码的第5位为0,小写为1。
如果一个问题的解决方案使我们陷入一种矛盾之中,那么,很可能是我们的出发点有了问题,或者,我们起初运用的规律并不合适。
我们编写的程序大都是进行数据的处理,而数据在内存中存放,所以我们在处理数据之前首先要搞清楚数据存储在什么地方,即数据的内存地址。
在程序中,用16位寄存器进行内存单元之间的数据传送,一次复制2字节。
不同的寻址方式(即定位内存单元的方法):
- [idata]用一个常量来表示地址,可用于直接定位一个内存单元
- [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元
- [bx+idata]用一个变量和常量来表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元
- [bx+si]用两个变量表示地址
- [bx+si+idata]用两个变量和一个常量表示地址
一般来说,在需要暂存数据的时候,我们都应该使用栈。
bx, si, di, bp只有这4个寄存器可以用[...]进行内存单元寻址,可以单独出现,或以4种组合出现:bx和si,bx和di,bp和si,bp和di。
在[...]中使用bp,默认段地址为ss。
汇编语言用3个概念表达数据的位置:立即数(idata)、寄存器和段地址:偏移地址。
8086CPU可以处理两种尺寸的数据:byte和word,所以指令操作必须指明指令进行的是字操作还是字节操作:
- 通过寄存器指明,如ax进行字操作,al进行字节操作
- 没有寄存器名存在的情况下,用X ptr指明内存单元长度,X可以为word或byte
- push指令只进行字操作
可以修改IP,或者同时修改CS和IP的指令统称为转移指令。
操作符offset在汇编语言中是由编译器处理的符号,功能是取得标号的偏移地址。
jmp short 标号,jmp near ptr 标号,jcxz 标号,loop 标号等指令,他们对于IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们对应的机器码中不包含转移目的地址,而包含的是到目的地址的位移。这种设计,方便了程序段在内存中的浮动装配。
ret指令用栈中的数据修改IP的内容,实现近转移;相当于进行pop IP。 retf指令用栈中的数据修改CS和IP的内容,实现远转移;相当于进行pop IP; pop CS。
CPU执行call指令时进行两步操作:将**当前的IP或CS和IP(即call指令后一条指令)**压入栈中;转移。
模块化程序设计中,用寄存器来存储参数和结果是最常用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序读写恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
编写子程序的标准框架:
- 子程序中使用的寄存器入栈
- 子程序内容
- 子程序中使用的寄存器出栈
- 返回(ret, retf)
标志寄存器具有以下三种作用:
- 用来存储相关指令的某些执行结果
- 用来为CPU执行相关指令提供行为依据
- 用来控制CPU的相关工作方式
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)。
标志寄存器按位起作用,即它的每一位具有专门的含义:
- ZF:零标志位。结果为0,zf=1,否则为0。
- PF:奇偶标志位。如果1的个数为偶数,pf=1;否则为0。
- SF:符号标志位。如果结果为负,sf=1。
- CF:进位标志位。用于无符号数运算。
- OF:溢出标志位。用于有符号数运算。
- DF:方向标志位。df=0表示每次操作后si,di递增,反之递减。cld指令将df置0,std指令将df置1。
pushf将标志寄存器的值入栈,popf将其值出栈。
对于8086CPU,当CPU内部有下面情况发生时,将产生中断信息:
- 除法错误(0)
- 单步执行(1)
- 执行into指令(4)
- 执行int指令(n)
中断信息通过中断类型码识别,中断类型码为一个字节型数据,可表示256种中断信息来源。
用来处理中断信息的程序被称为中断处理程序,即中断例程。
中断向量表就是中断向量的列表,中断向量就是中断处理程序的入口地址。所以,中断向量表就是中断处理程序入口地址的列表。
中断向量表存放在内存中,8086CPU中中断向量表放在地址0处,内存0000:0000~0000:03FF的1024个内存单元中存放着中断向量表。
对于8086CPU,一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。
用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU硬件自动完成的。这个工作的过程被称为中断过程。
中断过程:
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push CS
- push IP
- (IP)=(N4), (CS)=(N4+2)
iret指令为:pop IP;pop CS;popf
int指令格式为:int n,功能是引发n号中断过程。
编写供应用程序调用的中断例程步骤:
- 编写子程序
- 安装程序到内存某处(如0:200)
- 设置中断向量表,将程序的入口保存在相关表项中,使其称为中断例程
在系统板的ROM中存放着一套程序,称为BIOS(基本输入输出系统),BIOS中主要包含以下几部分内容:
- 硬件系统的检测和初始化程序
- 外部中断和内部中断的中断例程
- 用于硬件设备进行I/O操作的中断例程
- 其他和硬件系统相关的中断例程
BIOS和DOS提供的中断例程如何安装到内存中?
- 开机后,CPU一加电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条跳转指令,CPU执行该指令后,转去执行BIOS中的操作系统检测和初始化程序
- 初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记到中断向量表中。注意,对于BIOS所提供的中断例程,只需将入口地址登记在中断向量表中,因为他们是固化到ROM中的程序,一直在内存中存在
- 硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导,从此将计算机转交给操作系统
- DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量
CPU可以直接读写3个地方的数据:
- CPU内部的寄存器
- 内存单元
- 端口
在PC系统中,CPU最多可以定位64KB个不同的端口,端口范围为0~65535。
端口的指令读写只有两条:in和out,分别用于从端口读取数据和往端口写入数据。
在in和out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据。访问8位端口时用al,访问16位端口时用ax。
对0~255以内的端口进行读写时:
in al, 20h;从20h端口读入一个字节
out 20h, al;往20h端口写入一个字节
对256~65535的端口进行读写时,端口号放在dx中:
mov dx, 3f8h;将端口号3f8h送入dx
in al, dx;从3f8h端口读入一个字节
out dx, al;往3f8h端口写入一个字节
外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关芯片送到外设。
PC系统中外中断源共有两类:可屏蔽中断和不可屏蔽中断。
可屏蔽中断指CPU可以不响应的外中断,CPU是否响应可屏蔽中断要看标志寄存器的IF位的值。如果IF=1,则CPU执行完当前指令后响应中断,引发中断过程。若为0则不响应。
8086CPU提供设置IF的指令为:sti设置IF=1;cli设置IF=0。
不可屏蔽中断是CPU必须响应的外中断。对于8086CPU,不可屏蔽中断的中断类型码固定为2。
键盘上每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描,一般将按下一个键时产生的扫描称为通码,松开一个键产生的扫描码称为断码。扫描码长度为一个字节,通码的第7位为0,断码的第7位为1。
断码 = 通码 + 80h
指令系统总结:
- 数据传送指令:mov,push,pop,pushf,popf,xchg等
- 算术运算指令:add,sub,adc,sbb,inc,dec,cmp,imul,idiv,aaa等。执行结果影响标志寄存器的sf、zf、of、cf、pf、af。
- 逻辑指令:and,or,not,nor,xor,test,shl,shr,sal,sar,rol,ror,rcl,rcr等。执行结果都影响标志寄存器的相关标志位。
- 转移指令
- 无条件转移指令:jmp
- 条件转移指令:jcxz,je,jb,ja,jnb,jna
- 循环指令:loop
- 过程:call,ret,retf
- 中断:int,iret
- 处理机控制指令:cld,std,cli,sti,nop,clc,cmc,stc,hlt,wait,esc,lock
- 串处理指令:movsb,movsw,cmps,scas,lods,stos。一般与rep,repe,repne等前缀指令结合使用。
a: db 1,2,3,4,5,6,7,8
b: dw 0
;a,b仅仅表示了内存单元的地址
a db 1,2,3,4,5,6,7,8
b dw 0
;a,b不仅描述内存地址,也描述单元长度。标号a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元;标号b描述了地址code:8,和从这个地址开始,以后的内存单元都是字单元。
;这种标号称为数据标号,标记了存储数据的地址和长度。
在后面加有“:”的地址标号,只能在代码段中使用,不能在其他段中使用。
如果想在代码段中直接使用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。
直接定址表:通过依据数据,直接计算出所要找的元素的位置的表。
键盘输入引发9号中断,BIOS提供int9中断例程。一般的键盘输入,在CPU执行完int9中断例程后,都放到了键盘缓冲区。
键盘缓冲区的字单元中,高字节存储扫描码,低字节存储ASCII码。