中断及外部设备操作
移位指令
逻辑左移SHL ORP,CNT
逻辑右移SHR OPR,CNT
循环左移ROL OPR,CNT
循环右移ROR ORP,CNT
算数左移SAL OPR,CNT
算数右移SAR OPR,CNT
带进位循环左移RCL OPR,CNT
带进位循环右移RCR OPR,CNT
s,sh - shift l-left r-right a-arithmetic r,ro-rotate c-carry
位移操作需要注意:
如果移动的位数大于1的时候,必须用cl,否则移动的位数只能是1
mov al,01010001B
mov cl,3
shl al,cl
操作显存数据
屏幕上的内容 = 显存中的数据
其中B8000H ~ BFFFFH共32K的空间,是80*25彩色字符模式第0页的显示缓冲区
显示缓冲区的结构
由此可知,一列需要两个字节来存储数据,低位字节要显示符号的ASCII,高位字节显示属性字节(闪烁、背景、高亮、前景等)
高位字节
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
BL | R | G | B | I | R | G | B |
闪烁 | 背景 | 背景 | 背景 | 高亮 | 前景 | 前景 | 前景 |
显示信息的一种直接方式
在品目中间,白底蓝字,显示“welcome to masm”
assume cs:code,ds:data
data segment
db 'welcome to masm'
data ends
code segment
main:
;初始化寄存器
mov ax,data
mov ds,ax
mov ax,0B800H
mov ex,ax
mov si,0
mov di,160*12 + 80-16;定位到了第十二行的中间位置,并向左偏移16字节
;显示字符串
mov cx,16
l:
mov al,[si]
mov es:[di],al
inc di
mov al,71H
mov es:[di],al
inc si
inc di
loop l
code ends
end main
描述内存单元的标号
关于标号
1)代码段中的标号可以用来标记指令、段的起始地址
2)代码段中的数据也可以用标号
assume cs:code
code segment
a:db 1,2,3,4,5,6,7,8
b:dw 0
main:
mov si,offset a
mov bx,offset b
mov cx,8
l:
mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop l
mov ax,4c00H
int 21H
code ends
end main
去掉冒号的数据标号
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
main:
mov si,0
mov cx,8
l:
mov al,a[si] ;类似高级语言的数组
mov ah,0
add b,ax
inc si
loop l
mov ax,4c00H
int 21H
code ends
end main
上述代码中的a,b被称为数据标号:
1)数据标号标记了存储数据的单元的地址和长度
2)数据标号不同于仅仅表示地址的地址标号
地址编号只能在代码段内使用
数据编号能在代码段和数据段内使用
扩展用法(类似于C语言的指针)
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends
等同于
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a,offset b
data ends
如果利用双字存储地址,那么会将段地址也进行存储
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends
等同于
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a,seg a,offset b,seg b
data ends
数据的直接定址表
直接定址表:通过查表的方式解决问题
问题:以十六进制的形式在屏幕上显示给定的byte型数据
分析:现将一个byte的高四位和低四位分开,显示对应的数码字符
方案一:逐个进行比较
如果是0,那么将0打印在公屏上
如果是1,那么将1打印在公屏上
....
缺点:比较次数多,过程书写繁琐
方法二:建立一张表,表中依次存储“0 ~ F”,我们可以通过数值0~15直接查找到对应的字符
assume cd:code
code segment
main:
mov al,2BH
call showbyte
mov ax,4c00H
int 21H
showbyte:
jmp short show
table db '0123456789ABCDEF'
show:
push bx
push es
push cx
mov ah,al
mov cl,4
shr ah,cl ;右移四位,ah中得到高四位的值
and al,00001111b;al中获取低四位的值
;用高四位的值作为table中的偏移量,获取对应字符并显示
mov bl,ah
mov bh,0
mov ah,table[bx]
mov bx,0b800H
mov es,bx
mov es:[160 * 12 + 40 * 2],ah
;用低四位的值作为table中的偏移量,获取对应字符并显示
mov bl,al
mov bh,0
mov al,table[bx]
mov es:[160 * 12 + 40 * 2 + 2],al
pop bx
pop es
pop cx
code ends
end main
代码直接定制表
直接定制表
用查表的方式,通过依据数据,直接计算出所要找的元素的位置
直接定制表分类
1)数据的直接定制表
2)代码的直接定制表
要解决的问题
实现一个子程序setscreen,为实现输出提供如下功能
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
popcx
pop bx
ret ;sub1结束
2)设置前景
;设置前景色:显存中奇地址的属性字节的第0,1,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结束
3)设置背景
;设置第4,5,6位
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 sub3s
pop es
pop cx
pop bx
ret ;sub3结束
4)向上滚动一行
;将n + 1行复制到n行处
sub4:
push cx
push si
push di
push es
push ds
;准备阶段
mov si,0b800H
mov es,si
mov ds,si
mov si,160 ;将si指向n+1行
mov di,0 ;将di指向第n行
cld
mov cx,24 ;共复制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 * 34 + si],' '
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret ;sub4结束
5)主程序
assume cs:code
code segment
main:
mov ah,2
mov al,5
call setscreen
moc ax,4c00H
int 21H
setscreen:
jmp short set
table dw sub1,sub2,sub3,sub4
set:
push bx
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx
call word ptr table[bx]
sret:
pop bx
ret
sub1:
....
sub2:
....
sub3:
....
sub4:
....
code ends
end main
解决方案
1)将四个功能写成四个子程序
2)将这些功能子程序的入口地址存储在一个表中,他们在表中的位置和功能号相对应
3)对应关系为:功能号 * 2 = 对应的功能子程序在地址表中的偏移
中断及其处理
中断概述
1)CPU不在接着刚刚执行的指令向下执行,而是转去处理中断信息
2)内中断:由CPU内部发生的事件引起的中断
3)外中断:由外部设备发生的事件引起的中断(键盘、显示器、打印机等引起)
内中断
1)除法错误,比如:执行div指令产生的除法溢出
2)单步执行
3)执行into指令
4)执行int指令
8086中断码类型
1)除法错误:0
2)单步执行:1
3)执行into指令:4
4)执行int n指令,立即数n为终端类型码
CPU接到中断信息怎么办?
执行中断处理程序
中断处理程序在哪里?
中断信息和其处理程序的入口地址之间有某种联系,CPU根据中断信息可以找到要执行的处理程序。
中断向量表
由中断类型码,查表得到中断处理程序的入口地址,从而定位中断处理程序。
(共256个中断程序,1024个字节,每四个字节为一组,低字节记录中断程序的IP,高字节激励中断程序的CS)
定位中断程序
IP = N * 4
CS = N * 4 + 2
中断过程
1)中断过程由CPU的硬件自动完成
2)用中断类型码找到中断向量,并用它设置CS和IP
8086CPU的中断过程
1)从中断信息中取得中断类型码
2)标志寄存器的值入栈--中断过程中要改变标志寄存器的值,需要先保护现场
3)设置标志寄存器的第八位TF和第九位IF的值为0
4)CS的内容入栈
5)IP的内容入栈
6)从中断向量表读取中断处理程序的入口地址
单步中断
程序的正常执行:取指令、改变CS:IP、执行指令、取指令
Debug提供了单步中断的中断处理程序,功能为显示所有寄存器中的内容后等待输入命令
CPU执行一条指令停下来的原理是什么
1)Debug利用了CPU提供的单步中断的功能
2)使用t命令时,Debug将TF标志设为1,使CPU工作在单步中断的方式下
单步中断的过程与处理
TF陷阱标志(Trap fial):用于调试时的单步方式操作。当TF = 1时,每条指令执行完后产生陷阱,由系统控制计算机,当TF = 0时,CPU正常工作,不产生陷阱。
IF中断标志(Interrupt flag):当IF = 1时,允许CPU响应可屏蔽中断请求,当IF = 0时,关闭中断。
CPU在执行完一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断(中断类型码为1),引发中断过程,执行中断处理程序。
中断过程
1)取得中断类型码
2)标志寄存器入栈,TF、IF设置为0
中断处理程序也是由一条条指令组成的
如果在执行中断程序之前,TF = 1,则CPU在执行完中断处理程序的第一条指令后,又要产生单步中断,转而去执行单步中断的中断处理程序的第一条指令
上边的过程将陷入一个永远不能结束的循环,CPU永远执行单不中断处理程序的第一条指令
所以,在进行中断处理程序之前,设置TF = 0
3)CS、IP入栈
4)IP = 1* 4,CS = 1 * 4 + 2
中断不响应的情况
1)一般情况下,CPU执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程
2)在有些情况下,CPU执行完当前指令后,即便发生中断,也不会响应
如:在执行完向ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应,因为系统默认对ss寄存器与sp寄存器的初始化是连续的,如果进行中断的话,可能造成在二者初始化的间隙中,产生其他不可预测的指令。因此,将二者当成一个整体对待。(注意:是将ss赋值指令,与其下一条指令看做一个整体,下一条指令可能不是sp的赋值指令)
由int指令引发的中断
int n引发的中断
格式:int n,n为中断类型码
功能:引发中断过程
CPU执行int n指令,相当于引发一个n号中断的中断过程,执行过程如下:
1)取中断类型码n
2)标志寄存器入栈,IF = 0,TF = 0
3)CS、IP入栈
4)IP = n * 4,CS = n * 4 + 2
BIOS和DOS中断处理
BIOS,是在系统板的ROM中存放着一套程序
容量:8KB
地址:从统一编址的FE000H开始
BIOS中的主要内容
1)硬件系统的检测和初始化程序
2)外部中断和内部中断的中断例程
3)用于对硬件设备进行I/O操作的中断例程
4)其他和硬件系统相关的中断例程
优点:
1)使用BIOS功能调用,程序员不用了解硬件的操作细节,直接使用指令设置参数,并中断调用BIOS例程,即可完成相关工作
2)方便编程
3)能写出简介、可读性好、易于移植的程序
编程任务
在屏幕的5行12列显示三个红底高亮闪烁绿色的 ‘a’
用BIOS的10H中断
1)ah = 2时,调用10h中断例程的2号子程序,设置光标位置
2)ah = 9时,调用第10H中断例程的9号子程序,在光标位置显示字符
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所提供的中断例程包含了很多子程序,这些子程序实现了程序猿在编程时常用到的功能
和硬件设备相关的DOS中断例程中,一般都调用了BIOS的中断例程
int 21H DOS中断例程的应用
4ch功能号:程序返回,功能号在ah中,返回结果在al中
用法:
mov ah,4cH
mov al,0
BIOS和DOS中断例程的安装过程
1)CPU一加电,初始化CS = 0FFFFH,IP = 0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条跳转指令,CPU执行该指令后,转去执行BIOS中的硬件检测和初始化程序
2)初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。
3)硬件系统检测和初始化完成后,调用int 19H进行操作系统的引导,从此将计算机交由操作系统控制。
4)DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
端口的读写
以端口访问外设:以发声为例
assume cs:code
code segment
main:
mov al,08H ;设置声音频率
out 42H,al
out 42H,al
in al,61H ;读设备控制器端口原值
mov ah,al ;保存原值
or al,3 ;打开扬声器和定时器
out 61H,al ;接通扬声器,发声
mov cx,60000;延时
delay:
nop
loop delay
mov al,ah ;恢复端口原值
out 61H,al
mov ax,4c00H
int 21H
code ends
end main
CPU可直接访问的地方
1)CPU内部的寄存器
2)内存单元
3)端口:各种接口卡、网卡、显卡等,主板上的接口芯片,其他芯片
1)各个芯片工作的时候,都有一些寄存器由CPU读写
2)从CPU的角度,将各寄存器当端口并统一编址
3)CPU用统一的方法与各种设备通信
读写端口的指令
in :CPU从端口读数据
out:CPU往端口写入数据
访问端口的方法
in al,60H ;从60号端口读入一个字节
执行时与总线相关的操作
1)CPU通过地址线将地址信息60H发出
2)CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知要从中读取数据
3)端口所在的芯片将60H端口中的数据通过数据总线送入CPU
对0~255以内的端口进行读写,端口号用立即数给出
in al,20H ;从20端口读入一个字节
out 21H,al ;往21端口写入一个字节
对256~65535的端口进行读写时,端口号放在dx中
mov dx,3f8H ;将端口号38f送入dx
in al,dx ;从38f端口读入一个字节
out dx,al ;从38f端口写入一个字节
操作CMOS RAM芯片
在开机的前几秒按F8或其他按键进入BIOS
CMOS RAM芯片
1)包含一个时钟和一个有128个存储单元的RAM存储器
2)128个字节的RAM中存储:内部时钟、系统配置信息、相关程序(用于开机时配置系统信息)
3)CMOS RAM芯片靠电池供电,关机后其内部的时钟仍可正常工作,RAM中的信息不丢失
4)该芯片内部有两个端口,端口地址为70H和71H,CPU通过这两个端口读写CMOS RAM
70H:存放要访问的单元地址
71H:存放从选定的单元中读取的数据,或要写入的数据
5)读写CMOS RAM的两个步骤
将要读写的单元地址送入70H地址端口
将数据写入71H地址端口
时间在CMOS RAM中的存储(用六个字节,分别存放时间信息中的秒、分、时、日、月、年)
内容 | 秒 | 分 | 时 | 日 | 月 | 年 | ||||
---|---|---|---|---|---|---|---|---|---|---|
地址 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 |
读取月份,并将结果显示在屏幕中间
assume cs:code
code segment
main:
mov al,8
out 70H,al ;取得月份
in al,71H
mov ah,al ;分离月份的十、个位
mov cl,4
shr ah,cl
and al,00001111b
add ah,30H ;转换为ASCII码
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 main
外设连接与中断(外中断)
CPU在执行指令的过程中,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
可屏蔽中断
1)可屏蔽中断是CPU可以不响应的外中断
2)CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置
3)当CPU检测到可屏蔽中断信息时:
如果 IF = 1,则CPU在执行过程中完成当前指令后响应中断,引发中断过程
如果 IF = 0,则不响应可屏蔽中断
可屏蔽中断的中断过程
1)去中断类型码
可屏蔽中断信息来自于CPU外部,中断类型码是通过数据总线送入CPU(对比内中断:中断类型是在CPU内部产生的)
2)标志寄存器入栈,IF = 0,TF = 0
将IF置零的原因:进入中断处理程序后,禁止其他可屏蔽中断(如果在中断处理程序中需要处理可屏蔽中断,可以将IF置为1)
3)CS,IP入栈
4)IP = n * 4,CS = n * 4 + 2
不可屏蔽中断
1)CPU必须响应的中断
2)当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程
3)对于8086CPU不可屏蔽中断的中断类型码固定为2
不可屏蔽中断的中断过程
1)标志寄存器入栈,IF = 0,TF = 0
2)CS,IP入栈
3)IP = 8,CS = 0AH
8086CPU提供的设置IF的指令
1)sti:用于设置IF = 1
2)cli:用于设置IF = 0
PC键盘的处理过程
输入键盘的字符如何保存
1)有BIOS键盘缓冲区
2)BIOS键盘缓冲区:是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区
3)BIOS键盘缓冲区:可以存储15个键盘输入,一个键盘输入用一个子单元存放,高位存放扫描码,低位存放字符码
键盘处理过程
1)键盘输入
1)键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描
2)按下一个键的操作
开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置
扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60H
3)松开按下的键时的操作
产生一个扫描码,扫描码说明了松开的键在键盘上的位置
松开按键时产生的扫描码也被送入60H端口中
4)扫描码长度为一个字节的编码
按下一个键时产生的扫描码 - 通码,通码的第八位为0
松开一个键时产生的扫描码 - 断码,断码的第八位为1
2)引发九号中断
1)键盘的输入到达60H端口时,相关芯片就会向CPU发出中断类型码为9的可屏蔽中断信息
2)CPU检测到该中断信息后,如果IF = 1,则相应中断,引发中断过程,专区执行int 9中断例程
3)执行int 9中断例程
1)独处60H端口的扫描码
2)根据我扫描分情况对待
如果是字符键的扫描码,将该扫描码和他对应字符码送入内存中的BIOS键盘缓冲区
如果是控制键(Ctrl)和切换键(capslock),则将其转换为状态字节,写入内存中存储状态字节的单元
3)对键盘系统进行相关的控制,如向相关芯片发出应答信息