1 基础知识
1.1 存储单元
一个存储单元存储一个字节
1.2 地址总线
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
地址总线的宽度决定了CPU的寻址能力。
1.3 数据总线
数据总线的宽度决定了CPU和外界的数据传送速度。8根数据总线一次可以传送一个8位二进制数据(1个字节)。
8086的数据总线宽度为16。
数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量。
1.4 控制总线
控制总线的宽度决定了CPU对外部器件的控制能力。
1.5 内存地址空间
地址0~7FFFH的32KB空间为主随机存储器的地址空间;
地址8000H~9FFFH的8KB空间为显存地址空间;
地址A000H~FFFFH的24KB空间为各个ROM的地址空间。
2 寄存器
2.1 通用寄存器
8086CPU的所有寄存器是16位的,可以存放两个字节。AX,BX.CX.DX这4个寄存器通常用来存放一般性的数据,被称为通用寄存器。
AX分为AH和AL;
BX分为BH和BL;
以此类推。
AX的低8位构成了AL寄存器,AX的高8位构成了AH寄存器。
2.2 字在寄存器中的存储
字节:Byte,一个字节由8个bit组成,可以存在8位寄存器中。
字:word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节。
2.3 8086CPU的物理地址
物理地址=段地址*16+偏移地址
一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
段地址*16表示以二进制形式存放的段地址左移4位,十六进制形式存放的段地址左移1位。
2.4 段
CPU可以用不同的段地址和偏移地址形成同一个物理地址。
偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻64KB个内存单元。
比如给定段地址1000H,用偏移地址寻址,CPU的寻址范围为:10000H~1FFFFH。
2.5 CS和IP
CS为代码段寄存器,IP为指令指针寄存器。
8086CPU的工作过程可以简要描述如下。
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
- IP=IP+所读取指令的长度,从而指向下一条指令;
- 执行指令。转到步骤1,重复这个过程
2.6 修改CS,IP的指令
JMP 段地址:偏移地址:用指令中给出的段地址修改CS,偏移地址修改IP。
JMP 某一合法寄存器:用寄存器的值修改IP。
2.7 代码段
对于8086PC机,我们可以将长度为N(N<=64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中。
3 寄存器(内存访问)
3.1 字的传送
在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
3.2 DS和[address]
DS是数据段寄存器,用来存放要访问数据的段地址。
"[X]"表示一个内存单元,X表示内存单元的偏移地址
3.3 CPU提供的栈机制
push ax:将寄存器ax中的数据送入栈中。
pop ax:从栈顶取出数据送入ax。
任意时刻,SS:SP指向栈顶元素。
push ax的执行过程
- SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
- 将ax中的内容送入SS:SP指向的内存单元处。SS:SP此时指向新栈顶。
SP=最底部的字单元的偏移地址+2,此时最底部的字单元的偏移地址SP是000EH,加2后SP是0010H
pop ax的执行过程
将SS:SP指向的内存单元处的数据送入ax中;
SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反
4 第一个程序
4.1 源程序
1.伪指令
伪指令是由编译器来执行的指令,没有对应的机器指令。
(1)XXX segment XXX ends:定义一个段。
(2)end:汇编程序的结束标记。
(3)assume:它假设某一寄存器和程序中的某一个用segment...ends定义的段相关联
2.汇编指令
汇编指令有对应的机器码指令,可以被编译为机器指令,最终为CPU所执行。
4.2 shell将可执行文件中的程序装载进入内存并使它运行
PS:操作系统的外壳
任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户使用这个程序来操作计算机系统进行工作。
DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS系统的shell。
- 在DOS中直接执行1.exe时,是正在运行的command,将1.exe中的程序加载如内存;
- command设置CPU的CS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行;
- 程序运行结束后,返回到command中,CPU继续运行command。
5 [BX]和loop指令
5.1 [bx]
[bx]表示一个内存单元,它的偏移地址在bx中。
5.2 Loop指令
CPU执行loop指令的时候,要进行两步操作
- (cx)=(cx)-1
- 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行
5.3 段前缀
在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。比如:
mov ax,ds:[bx]
5.4 一段安全的空间
当我们需要直接向一段内存中写入内容时,这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误;
DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码,这段内存空间是安全的;
6 包含多个段的程序
6.1 在代码段中使用数据
dw:define word,用来开辟内存空间。
end指令指明了程序的入口在标号start处
6.2 在代码段中使用栈
程序运行时,定义的数据存放在CS:0~CS:F单元中,共8个字单元。依次将这8个字单元的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放。
把start标识符去掉的话,编译器会认为CS:0是指令开始处,实际上CS:0~CS:F是数据,CS:10开始才是代码段。
6.3 将数据、代码、栈放入不同的段
将数据、栈和代码都放到一个段里面,会有以下两个问题:
- 把它们放到一个段中使程序显得混乱;
- 在8086中,如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中。
如下程序实现和程序6.2同样的功能,不同之处在于它将数据、栈和代码放入了不同的段中。
7 更灵活的定位内存地址的方法
7.1 and和or指令
and指令:逻辑与指令,按位进行与运算。
例如:
mov al,01100011B
and al,00111011B
执行后:al=00100011B
or指令:逻辑或指令,按位进行或运算。
例如:
mov al,01100011B
or al,00111011B
al=01111011B
7.2 以字符形式给出的数据
assume cs:code,ds:data data segment db 'unIX' db 'forRK' data ends code segment start: mov al,'a' mov bl,'b' mov ax,4c00h int 21h code ends end start
db 'unIX'相当于"db 75H,6EH,49H,58H","u","n","I","X"的ASCII码分别为75H,6EH,49H,58H;
7.3 大小写转换问题
将“BaSiC”转化为小写,将'iNfOrMaTiOn'转化为大写
7.4 用[bx+idata]的方式进行数组的处理
起始地址不需要像7.3显示地声明mov bx,5
以上汇编程序用C语言描述如下
7.5 SI和DI
si和di是8086CPU中和bx功能象进的寄存器, si和di不能够分成两个8位寄存器来使用。
下面的3组指令实现了相同的功能
下面的3组指令实现了相同的功能
7.6 不同的寻址方式的灵活应用
[idata]用一个常量来表示地址,可用于直接定位一个内存单元;
[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
[bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
[bx+si]用两个变量表示地址;
[bx+si+idata]用两个变量和一个常量表示地址。
编程,将datasg段中每个单词改为大写字母。
程序如下:
8 数据处理的两个基本问题
8.1 bx,si,di和bp
1.在8086CPU中,只有bx,si,di和bp这4个寄存器可以用在"[...]"中来进行内存单元的寻址。
以下的指令都是正确的:
2.在[...]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。
以下的指令都是正确的:
8.2 寻址方式
8.3 指令要处理的数据有多长
在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令种可以为word或byte。
例如,下面的指令中,用word ptr指明了指令访问的内存单元是一个字单元。
mov word ptr ds:[0],1 inc word ptr [bx] inc word ptr ds:[0] add word ptr [bx],2
下面的指令中,用byte ptr指明了指令访问的内存单元是一个字节单元。
mov byte ptr ds:[0],1 inc byte ptr [bx] inc byte ptr ds:[0] add byte ptr [bx],2
内存内容如下
2000:1000 FF FF FF FF FF FF ......
那么指令
mov ax,2000H
mov ds,ax
mov byte ptr [1000H],1
将使内存变为:
2000:1000 01 FF FF FF FF FF ......
而指令
mov ax,2000H
mov ds,ax
mov word ptr [1000H],1
将使内存中的内容变为:
2000:1000 01 00 FF FF FF FF ......
8.4 寻址方式的综合应用
数据存放示意
汇编和C语言对应关系
8086CPU提供的如[bx+si+idata]的寻址方式为结构化数据的处理提供了方便。一般来说,我们可以用[bx+idata+si]的方式来访问结构体中的数据。
用bx定位整个结构体,用idata定位结构体中的某一个数据项,用si定位数组顶中的每个元素。
8.5 div指令
被除数:
如果除数为8位,被除数为16位,默认存放在AX;
如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位;
结果:
如果除数为8位,AL存储商,AH存储余数;
如果除数为16位,AX存储商,DX存储余数;
编程,利用除法指令计算100001/100
编程,利用除法指令计算1001/100
8.6 dd和dup指令
dd:define double word
dup:用来进行数据的重复
db 重复的次数 dup (重复的字节型数据)
db 重复的次数 dup (重复的字型数据)
db 重复的次数 dup (重复的双字型数据)
9 转移指令的原理
9.1 操作符offset
操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。
如下面的程序:
9.2 依据位移进行转移的jmp指令
jmp short 标号:段内短转移,它对IP的修改范围为-128~127。
实际上,“jmp short 标号”的功能为:(IP)=(IP)+8位位移。
- 8位位移=标号处的地址-jmp指令后的第一个字节的地址;
- short指明此处的位移为8位位移;
- 8位位移的范围为-128~127,用补码表示;
- 8位位移由编译程序在编译时算出。
“jmp short 标号”指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移。
jump near ptr 标号:段内近转移
“jmp near ptr 标号”的功能为:(IP)=(IP)+16位位移。
- 16位位移=标号处的地址-jmp指令后的第一个字节的地址;
- near ptr 指明此处的位移为16位位移,进行的时段内近转移;
- 16位位移的范围为-32768~32767,用补码表示;
- 16位位移由编译程序在编译时算出。
9.3 转移的目的地址在指令中的jmp指令
jmp far ptr 标号:段间转移,又称为远转移。
far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
jmp far ptr s所对应的机器码包含转移的目的地址
9.4 转移地址在内存中的jmp指令
1.jmp word ptr 内存单元地址(段内转移)
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
2.jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的短地址,低地址处的字是转移的目的偏移地址
9.5 jcxz指令
jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,对IP的修改范围是:-128~127。
指令格式:jcxz标号(如果(cx)=0,转移到标号处执行)
操作:
当(cx)=0时,(IP)=(IP)+8位位移;
8位位移=标号处的地址-jcxz指令后的第一个字节的地址;
8位位移的范围时-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)≠0时,程序向下执行。
9.6 loop指令
loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127。
指令格式:loop标号((cx)=(cx)-1,如果(cx)≠0,转移到标号处执行。)
操作:
(1) (cx)=(cx)-1
(2) 如果(cx)≠0,(IP)=(IP)+8位位移
8位位移=标号处的地址-loop指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
如果(cx)=0,什么也不做(程序向下执行)。
10 CALL和RET指令
10.1 ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移
用汇编语法解释ret和retf指令,则
CPU执行ret指令时,相当于进行:
pop IP
CPU执行retf指令时,相当于进行:
pop IP
pop CS
10.2 依据位移进行转移的call指令
call标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此种格式的call指令时,进行如下的操作:
16位位移=标号处的地址-call指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出。
CPU执行"call 标号"时,相当于进行:
push IP
jmp near ptr 标号
10.3 转移的目的地址在指令中的call指令
"call far ptr 标号"实现的是段间转移。
CPU执行此种格式的call指令时,进行如下操作。
CPU执行“call far ptr 标号”时,相当于进行:
push CS
push IP
jmp far ptr 标号
10.4 转移地址在寄存器中的call指令
指令格式:call 16位reg
功能:
(SP)=(SP)-2
((SS)*16+(SP))=(IP)
(IP)=(16位reg)
CPU执行“call 16位reg”,相当于进行:
push IP
jmp 16位reg
10.5 转移地址在内存中call指令
(1)call word ptr 内存单元地址
用汇编语法来解释此种格式的call指令,则
CPU执行“call word ptr 内存单元地址”时,相当于进行:
push IP
jmp word ptr 内存单元地址
(2)call dword ptr 内存单元地址
用汇编语法来解释此种格式的call指令,则:
CPU执行“call dword ptr 内存单元地址”时,相当于进行:
push CS
push IP
jmp dword ptr 内存单元地址
10.6 mul指令
mul是乘法指令
(1)两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果
是16位,一个默认在AX中,另一个放在16位reg或内存字单元中。
(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中存放。
格式如下:
mul reg
mul 内存单元
例子1
例子2
11 标志寄存器
标志寄存器具有以下作用
- 用来存储相关指令的某些执行结果;
- 用来为CPU执行相关指令提供行为依据;
- 用来控制CPU的相关工作方式;
11.1 ZF标志
零标志位
结果为0,zf=1;结果不为0,那么zf=0
11.2 PF标志
奇偶标志位
1的个数为偶数,pf=1;1的个数为奇数,那么pf=0;
11.3 SF标志
符号标志位
结果为负,sf=1;结果非负,sf=0;
11.4 CF标志
进位标志位
存在最高有效位向更高位进位/借位的情况,cf=1;否则cf=0
11.5 OF标志位
溢出标志位
进行有符号数运算的时候,结果超过了机器所能表示的范围称为溢出,溢出of=1,无溢出,of=0;
11.6 adc指令
指令格式:adc 操作对象1,操作对象2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
例如adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF
11.7 sbb指令
指令格式:sbb 操作对象1,操作对象2
功能:操作对象1 = 操作对象1-操作对象2-CF
比如指令 sbb ax,bx 实现的功能是:(ax)=(ax)-(bx)-CF
11.8 cmp指令
cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。
cmp 指令格式:cmp 操作对象1,操作对象2
例如,指令cmp ax,ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。指令执行后:zf=1,pf=1,sf=0,cf=0,of=0
11.9 检测比较结果的条件转移指令
je 等于则转移 zf=1
jne 不等于则转移 zf=0
jb 低于则转移 cf=1
jnb 不低于则转移 cf=0
ja 高于则转移 cf=0且zf=0
jna 不高于则转移 cf=1或zf=1
11.10 DF标志和串传送指令
DF标志
DF是方向标志位。
df=0 每次操作后si,di递增;
df=1 每次操作后si,di递减。
串传送指令
格式:movb
功能:执行movsb指令相当于进行下面几步操作。
((es)*16+(di))=((ds)*16+(si))
如果df=0则:(si)=(si)+1
(di)=(di)+1
如果df=1则:(si)=(si)-1
(di)=(di)-1
格式:movsw
movsw的功能是将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。
rep movsb
用汇编语法来解释
s:movsb
loop s
rep的作用是根据cx的值,重复执行后面的串传送指令。
11.11 pushf和popf
pushf:将标志寄存器的值压入栈
popf:从栈中弹出数据
12 内中断
12.1 中断向量表
中断源:中断信息的来源
中断类型码:由8位组成
中断向量表:中断向量的列表
中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。
内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。
对于8086CPU,一个表项占两个字,高地址存放段地址,低地址存放偏移地址。
12.2 中断过程
简洁地描述中断过程
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push CS
- push IP
- (IP)=(N*4),(CS)=(N*4+2)
在最后一步完成后,CPU开始执行由程序员编写的中断处理程序。
12.3 中断处理程序和iret指令
中断处理程序的常规步骤
- 保存用到的寄存器;
- 处理中断;
- 恢复用到的寄存器;
- 用iret指令返回;
iret指令的功能用汇编语法描述为:
pop IP
pop CS
popf
12.4 编程处理0号中断
assume cs:code
code segment
start:设置es:di指向目的地址
设置ds:si指向源地址
设置cx为传输长度
设置传输方向为正
rep movsb
设置中断向量表
mov ax,4c00h
int 21h
do0:显示字符串"overflow"
mov ax,4c00h
int 21h
code ends
end start
12.5 设置中断向量
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
12.6 单步中断
CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,它引发的中断过程如下。
- 取得中断类型码1
- 标志寄存器入栈,TF、IF设置为0;
- CS、IP入栈;
- (IP)=(1*4),(CS)=(1*4+2);
在进入中断处理程序之前,设置TF=0。从而避免CPU在执行中断处理程序的时候发生中断。
12.7 响应中断的特殊情况
在执行完ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。
这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。所以debug的时候mov sp,0不会单步中断。
13 int指令
13.1 int指令
int指令的格式:int n,n为中断类型码,它的功能是引发中断过程。
CPU执行int n指令,相当于引发一个n号中断的中断过程,执行过程如下。
- 取得中断类型码n
- 标志寄存器入栈,TF、IF设置为0;
- CS、IP入栈;
- (IP)=(1*4),(CS)=(1*4+2);
mov ah,9
int 21h
调用21h例程9号子程序
13.2 编写供应用程序调用的中断例程
功能:将一个全是字母,以0结尾的字符串,转化为大写。
参数:ds:si指向字符串的首地址。
assume cs:code
data segment
db 'conversation',0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
int 7ch
mov ax,4c00h
int 21h
code ends
end start
安装程序如下
assume cs:code code segment start: mov ax,cs mov ds,ax mov si,offset capital mov ax,0 mov es,ax mov di,200h mov cx,offset capitalend - offset capital 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 capital: push cx push si change: mov cl,[si] mov ch,0 jcxz ok and byte ptr [si],11011111b inc si jmp short change ok: pop si pop cx iret capitalend:nop code ends end start
13.3 BIOS和DOS中断例程的安装过程
- 开机后,CPU一加电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条转跳指令,CPU执行该指令,转去执行BIOS中的硬件系统检测和初始化程序。
- 初始化程序将简历BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。注意,对于BIOS所提供的中断例程,只需将入口地址登记在中断向量表中即可,因为它们是固化到ROM中的程序,一直在内存中存在。
- 硬件系统检测和初始化完成后,调用int 19h 进行操作系统的引导。从此将计算机交由操作系统控制。
- DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
14 端口
在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有以下3种芯片。
- 各种接口卡(网卡,显卡)上的接口芯片,它们控制接口卡进行工作;
- 主板上的接口芯片,CPU通过它们对部分外设进行访问;
- 其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理。
14.1 端口的读写
端口的读写指令只有两条:in和out,分别用于从端口读取数据和往端口写入数据。
CPU执行内存访问指令和端口访问指令的比较
(1).访问内存
mov ax,ds:[8] ;假设执行前(ds)=0
- CPU通过地址线将地址信息8发出;
- CPU通过控制线发出内存读命令,选中存储芯片,并通知它,将要从中读取数据;
- 存储器将8号单元中的数据通过数据线送入CPU。
(2).访问端口
in al,60h
- CPU通过地址线将地址信息60h发出;
- CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
- 存储器将60h号端口中的数据通过数据线送入CPU。
注意,在in和out指令中,只能使用ax或al来存放端口中读入的数据或要发送到端口中的数据。
14.2 CMIOS RAM芯片
CMOS RAM芯片,一般简称为CMOS。此芯片特征如下。
- 包含一个实时钟和一个有128个存储单元的RAM存储器。
- 该芯片靠电池供电,所以,关机后其内部的实时钟仍可正常工作,RAM中的信息不丢失。
- 128个字节的RAM中,内部时钟占用 0~0dh 单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时BIOS程序读取。
- 该芯片内部有两个端口,端口地址为 70h 和 71h 。CPU 通过这两个端口来读写CMOS RAM。
- 70h 为地址端口存放要访问的 CMOS RAM 单元的地址; 71h 为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据。
比如,读CMOS RAM的2号单元
- 将2送入端口70h;
- 从端口71h读取2号单元的内容。
14.3 shl和shr指令
shl是逻辑左移指令,它的功能为:
- 将一个寄存器或内存单元中的数据向左移位;
- 将最后移出的一位写入CF中;
- 最低位用0补充。
指令:
mov al,01001000b
shl al,1
执行后(al)=10010000b,CF=0。
shr是逻辑左移指令,它的功能为:
- 将一个寄存器或内存单元中的数据向右移位;
- 将最后移出的一位写入CF中;
- 最高位用0补充。
指令:
mov al,01010001b
mov cl,3
shl al,cl
执行后(al)=10001000b,因为最后移出的一位是0,所以CF=0。
14.4 CMOS RAM中存储的时间信息
年、月、日、时、分、秒存放单元依次为
0H、2H、4H、7H、8H、9H
这些数据以BCD码的方式存放。
在屏幕中间显示当前的月份
程序如下
;编程:在屏幕中间显示当前的月份 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
15 外中断
15.1 接口芯片与端口
PC系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CPU将这些寄存器当作端口来访问。
外设的输入送入相关的接口芯片的端口中;CPU向外设的输出先送入端口中,再由相关的芯片送到外设。
CPU向外设输出控制命令,这些命令先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。
可见,CPU通过端口和外部设备进行联系。
15.2 外中断信息
1.可屏蔽中断
CPU可以不响应的中断。当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;
如果IF=0,则不响应可屏蔽中断,禁止其他的可屏蔽中断。
sti:设置IF=1
cli:设置IF=0
2.不可屏蔽中断
不可屏蔽中断是CPU必须响应的中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要获取中断类型码。
15.3 PC机键盘的处理过程
1.键盘输入
按下一个键,该芯片产生一个扫描码,叫做通码。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h。
松开按下的键,也产生一个扫描码,叫做断码。松开按键时产生的扫描码也被送入60h端口中。
扫描码长度为一个字节,通码的第7位为0,断码的第7位为1,即:
断码=通码 + 80h
例如,g键的通码为22h,断码为a2h。
2.引发9号中断
键盘的输入到达60h端口时,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息。CPU检测到该中断信息后,如果IF=1,则响应中断,
引发中断过程,转去执行int9中断例程。
3.执行int9中断例程
(1)读取60h端口的扫描码;
(2)如果是字符键的扫描码,将该扫描码的和它所对应的ASCII码送入内存中的BIOS键盘缓冲区;如果是控制键(Ctrl)和切换键(Capslock)的扫描码,
则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元;
(3)对键盘系统进行相关的控制。
BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码
0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节各位记录的信息如下。
15.4 指令系统总结
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等前缀指令配合使用。
16 直接定址表
16.1 数据标号
数据标号标记了存储数据的单元的地址和长度。
对于程序中的a db 1,2,3,4,5,6,7,8:
指令:mov al,a[si]
相当于:mov al,cs:0[si]
指令:mov al,a[3]
相当于:mov al,cs:0[3]
指令:mov al,a[bx+si+3]
相当于:mov al,cs:0[bx+si+3]
16.2 在其他段中使用数据标号
在其他段中,我们可以使用数据标号来描述存储数据的单元的地址和长度。
在后面加有":"的地址标号,只能在代码段中使用,不能在其他段中使用。
assume cs:code,ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov cx,8
s:
mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
16.3 直接定址表
建立一张具有映射关系的表,节省计算时间。
编写程序,以十六进制的形式在屏幕中间显示给定的字节型数据。
在数值0~15和字符"0"~"F"建立映射关系。
assume cs:code code segment start: mov al,0eh call showbyte mov ax,4c00h int 21h ;子程序: ;用al传送要显示的数据 showbyte: jmp short show table db '0123456789ABCDEF' ;字符表 show: push bx push es mov ah,al shr ah,1 shr ah,1 shr ah,1 shr ah,1 ;右移4位,ah中得到高4位的值 and al,00001111b ;al中为低4位的值 mov bl,ah mov bh,0 mov ah,table[bx] ;用高4位的值作为相对于table的偏移,取得对应的字符 mov bx,0b800h mov es,bx mov es:[160*12+40*2],ah mov bl,al mov bh,0 mov al,table[bx] ;用低4位的值作为相对于table的偏移,取得对应的字符 mov es:[160*12+40*2+2],al pop es pop bx ret code ends end start
16.4 程序入口地址的直接定址表
实现一个子程序,为显示输出提供如下功能。
- 清屏;
- 设置前景色;
- 设置背景色;
- 向上滚动一行;
入口参数说明:
(1)用ah 寄存器传递功能号:
0 表示清屏,
1表示设置前景色,
2 表示设置背景色,
3 表示向上滚动一行;
(2)对于2、3号功能,用al传送颜色值,
(al)∈{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }
下面,我们讨论一下各种功能如何实现 :
(1)清屏
将显存中当前屏幕中的字符设为空格符;
(2)设置前景色
设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位;
(3)设置背景色
设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位;
(4)向上滚动一行
依次将第 n+1行的内容复制到第n行处:最后一行为空。
;编程:实现一个子程序setscreen,为显示输出提供如下功能: ;(1) 清屏。 ;(2) 设置前景色。 ;(3) 设置背景色。 ;(4) 向上滚动一行。 ; ;入口参数说明: ;(1) 用 ah 寄存器传递功能号:0 表示清屏,1表示设置前景色,2 表示设置背景色,3 表示向上滚动一行; ;(2) 对于2、3号功能,用 al 传送颜色值,(al) ∈{0,1,2,3,4,5,6,7} setscreen: jmp short set table dw sub1,sub2,sub3,sub4 set: push bx cmp ah,3 ;判断传递的是否大于 3 ja sret mov bl,ah mov bh,0 add bx,bx ;根据ah中的功能号计算对应子程序的地址在table表中的偏移 call word ptr table[bx] ;调用对应的功能子程序 sret: pop bx iret ;功能子程序1:清屏 sub1: push bx push cx push es mov bx,0b800h mov es,bx mov bx,0 mov cx,2000 sub1s: mov byte ptr es:[bx],' ' add bx,2 loop sub1s pop es pop cx pop bx ret ;sub1 ends ;功能子程序2:设置前景色 sub2: push bx push cx push es mov bx,0b800h mov es,bx mov bx,1 mov cx,2000 sub2s: and byte ptr es:[bx],11111000b or es:[bx],al add bx,2 loop sub2s pop es pop cx pop bx ret ;sub2 ends ;功能子程序3:设置背景色 sub3: push bx push cx push es mov cl,4 shl al,cl mov bx,0b800h mov es,bx mov bx,1 mov cx,2000 sub3s: and byte ptr es:[bx],10001111b or es:[bx],al add bx,2 loop sub2s pop es pop cx pop bx ret ; sub3 ends ;功能子程序4:向上滚动一行 sub4: push cx push si push di push es push ds mov si,0b800h mov es,si mov ds,si mov si,160 ;ds:si指向第n+1行 mov di,0 ;es:di指向第n行 cld mov cx,24;共复制24行 sub4s: push cx mov cx,160 rep movsb ;复制 pop cx loop sub4s mov cx,80 mov si,0 sub4s1: mov byte ptr es:[160*24+si],' ' ;最后一行清空 add si,2 loop sub4s1 pop ds pop es pop di pop si pop cx ret ;sub4 ends
17 使用BIOS进行键盘输入和磁盘读写
17.1 int9中断例程对键盘输入的处理
我们通过下面几个键A,B,C,D,E,shift_A,A的输入过程,简要地看一下int9中断例程对键盘输入的处理方法。
17.2 使用int16h中断例程读取键盘缓冲区
int16h中断例程是从键盘缓冲区读取一个键盘输入,并且将其从缓冲区中删除,该功能的编号为0。
编程,接收用户的键盘输入,输入"r",将屏幕上的字符设置为红色;输入"g",将屏幕上的字符设置为绿色;输入"b",将屏幕上的字符设置为蓝色。
;编程: ;接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色:输入“g”, ;将屏幕上的字符设置为绿色;输入“b ”,将屏幕上的字符设置为蓝色。 ;A、B、C处的程序指令比较有技巧,请读者自行分析 assume cs:code code segment start: mov ah,0 int 16h ;int 16h 0号功能实现从键盘缓冲区读取一个键盘输入 mov ah,1 ;A 001 cmp al,'r' je red cmp al,'g' je green cmp al,'b' je blue jmp short sret red: shl ah,1 ;B 100 green: shl ah,1 ;C 010 blue: mov bx,0b800h mov es,bx mov bx,1 mov cx,2000 s: and byte ptr es:[bx],11111000b or es:[bx],ah add bx,2 loop s sret: mov ax,4c00h int 21h code ends end start
17.3 字符串的输入
简单地确定程序的处理过程如下。
- 调用int16h读取键盘输入;
- 如果是字符,进入字符栈,显示字符栈中的所有字符;继续执行1;
- 如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行1;
- 如果是Enter键,向字符栈中压入0,返回。
完整的接收字符串输入的子程序如下所示。
;最基本的字符串输入程序,需要具备下面的功能: ;(1) 在输入的同时需要显示这个字符串; ;(2)一般在输入回车符后,字符串输入结束; ;(3)能够删除已经输入的字符。 ;编写一个接收字符串的输入子程序,实现上面三个基本功能。 ;因为在输入的过程中需要显示,子程序的参数如下: ; (dh)、(dl)=字符串在屏幕上显示的行、列位置; ; ds:si 指向字符串的存储空间,字符串以O 为结尾符。 assume cs:code code segment start: call getstr return: mov ax,4c00h int 21h ;完整的接收字符串输入的子程序 getstr: push ax getstrs: mov ah,0 int 16h cmp al,20h jb nochar ;判断的是ASCII码小于0,说明不是字符 mov ah,0; call charstack ;字符入栈 mov ah,2 call charstack ;显示栈中的字符 jmp getstrs nochar: cmp ah,0eh ;退格键的扫描码 je backspace cmp ah,1ch ;回车键的扫描码 je enter jmp getstrs backspace: ;退格 mov ah,1 call charstack ;字符出栈 mov ah,2 call charstack ;显示栈中的字符 jmp getstrs enter: ;回车 mov al,0 mov ah,0 call charstack ;0入栈 mov ah,2 call charstack ;显示栈中的字符 pop ax ret ;getstr ends ;功能子程序实现 charstack: jmp short charstart table dw charpush,charpop,charshow top dw 0 ;栈顶 charstart: push bx push dx push di push es cmp ah,2 ja sret mov bl,ah mov bh,0 add bx,bx jmp word ptr table[bx] charpush: mov bx,top mov [si][bx],al inc top jmp sret charpop: cmp top,0 je sret dec top mov bx,top mov al,[si][bx] jmp sret charshow: mov bx,0b800h mov es,bx mov al,160 mov ah,0 mul dh mov di,ax add dl,dl mov dh,0 add di,dx mov bx,0 charshows: cmp bx,top jne noempty mov byte ptr es:[di],' ' jmp sret noempty: mov al,[si][bx] mov es:[di],al mov byte ptr es:[di+2],' ' inc bx add di,2 jmp charshows sret: pop es pop di pop dx pop bx ret code ends end start
17.4 应用int 13h中断例程对磁盘进行读写
3.5英寸软盘分为上下两面,每面有80个磁道,每个磁道分为18个扇区,每个扇区的大小为512个字节。
则:2面*80磁道*18扇区*512字节=1440KB=1.44MB
BIOS提供的访问磁盘的中断例程为int 13h
assume cs:code code segment start: mov ax,0b800h mov es,ax mov bx,0 ;es:bx 指向将写入磁盘的数据的内存区 mov al,8 ;写入的扇区数 mov ch,0 ;磁道号,从0开始 mov cl,1 ;扇区号 从1开始 mov dl,0 ;驱动器号0:软驱A, 1:软驱B,硬盘从80h开始, 80h:硬盘C,81h:硬盘D mov dh,0 ;磁头号,(对于软盘即面号,因为一个面用一个磁头来读写) mov ah,3 ;传递 int 13h 写入数据的功能号 int 13h ;返回参数 ;操作成功:(ah) = 0,(al) = 写入的扇区数 ;操作失败:(ah) = 出错代码 return: mov ax,4c00h int 21h code ends end start
资料
电子书:https://pan.baidu.com/s/1oF3qs08fWCG8mZoPNQD_kg 提取码:3vwj
小甲鱼视频:https://pan.baidu.com/s/1cmX-U-YVk6Hv83xzZTzSKg 提取码:got0
小甲鱼视频ppt和书中的源码:https://pan.baidu.com/s/1Qo63M0diytfTTjMEq884AA 提取码:pqs9
课后习题答案:https://blog.csdn.net/andrewgithub/article/details/78432046
侵删。