16位汇编笔记12-17
文章目录
12.内中断
12.1 中断
发出中断请求的来源叫做中断源
中断根据中断源的不同,可以这样分类:
{
硬
件
中
断
{
外
部
中
断
内
部
中
断
软
件
中
断
\begin{cases}硬件中断 \begin{cases}外部中断 \\ \\内部中断 \end{cases} \\ \\软件中断 \end{cases}\\
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧硬件中断⎩⎪⎨⎪⎧外部中断内部中断软件中断
外部中断有键盘中断、定时器中断等,可以屏蔽。
内部终端有断电、奇偶校验出错、运算错误等,不可屏蔽。
软件中断并不是真正的中断,只是可被调用的一般程序及系统功能,如int 21h
。
中断优先权由高到低:
- 除零、溢出、软件中断
- 不可屏蔽中断
- 可屏蔽中断
- 单步中断
中断是多任务的基础
中断标识码(中断号)用来定位中断处理程序。取值范围为 0 ~ 255(8086) 。利用中断向量表可以根据8位的中断类型码得到中断处理程序的段地址和偏移地址。
8086把中断向量表存放在内存0000:0000
到0000:03ff
处,因为每个地址有2字节段地址和2字节偏移地址,共256*4
个字节。
0020:00-0020:ff
在8086中没有用到,所以是安全空间。
cpu硬件通过终端类型码设置cs
和ip
的过程叫做中断过程:
- 从中断信息中取得中断类型码
pushf
- 设置
tf
和if
为0 push cs
push ip
(ip) = (中断号*4),(cs) = (中断号*4+2)
12.2 中断处理程序
常规步骤:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
iret
返回
iret
相当于pop ip; pop cs; popf
我们自己写的中断处理程序应该存放在内存中,该内存向os申请或使用安全空间0000:0200
,然后安装,将0000:0200
登记到中断向量表。
代码的长度可以用编译器计算,即offset
作差。
编译器可以识别减号
-
,实际上,加减乘除都可以识别。
自定义的中断处理程序要输出的字符串不能在data
中定义,而是放到一段不会被覆盖的空间中。
当然,像下面这样把数据和代码放在一起并不符合设计理念。
div
产生的除法溢出为0号中断,我们重写一下。
assume cs:codesg
codesg segment
start:
mov ax,cs
mov ds,ax
mov si,offset do0
mov ax,0
mov es,ax
mov di,200h
mov cx,offset do0end - offset do0 ;the number of bytes that do0 occupies
cld
rep movsb
;set the interrupt vector table
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0:
; needn't push reg
jmp short do0start ;occupy 2 bytes
data:
db "do0 interrupt."
do0start:
mov ax,cs
mov ds,ax
mov si,202h
mov ax,0b800h
mov es,ax
mov di,12*160 + 35*2
mov cx,14
s:
mov al,[si]
mov es:[di],al
inc si
inc di
mov al,00001010B ;highlight green
mov es:[di],al
inc di
loop s
mov ax,4c00h
int 21h
do0end:
nop
codesg ends
end start
assume cs:codesg
codesg segment
start:
mov ax,1000H
mov bh,1
div bh
mov ax,4c00h
int 21h
codesg ends
end start
按顺序执行,屏幕中间会出现do0 interrupt.
12.3 单步中断
cpu提供了执行一条指令后转去做其它事的功能,debug
才能使用t
命令。
原理:cpu执行完一条指令后,如果检测到tf=1
,则产生单步中断,引发中断过程。
单步中断的中断类型码为1,中断过程如下:
- 取得中断类型码1
pushf
,tf,if
置1push cs; push ip
(ip) = (1*4),(cs) = (1*4+2)
也有一些情况,即使发生中断,cpu也不会响应,比如,mov ss,ax
之后,因为ss
和sp
设置应连续完成,若响应中断,则上面中断过程第3步的ip
不是真正的偏移地址。所以,下面的代码是错的。
mov ax,1000h
mov ss,ax
mov ax,1
mov sp,0
13.int指令
格式:int 中断类型码
功能:引发一个n号中断程序。
该指令有点类似call
。同时这个指令也使dos更加不安全。
写一个中断程序,实现loop
指令,在屏幕上显示80个叹号。
提示,用bx
存储s-send
,这是个负数。利用iret
,计算se + (bx)
,得到s
的偏移地址。
bios
和dos
中断例程安装过程:
- 开机后,
ffff:0
有一条跳转指令,转去执行硬件系统检测和初始化程序。 - 初始化程序会建立
bios
所支持的中断向量表,将中断例程的入口地址登记在中断向量表中。 - 检测并初始化后,
int 19h
,进行os的引导,将计算机交给os。 - 中断例程装入内存,建立相应的中断向量。
bios
和dos
提供的中断例程,都用ah
来传递内部子程序编号。
int 10h
包含多个和屏幕输出相关的子程序。
一般供程序员调用的中断例程会包含多个子程序。
mov ah,2 ;2号子程序,置光标
mov bh,0 ;页
mov dh,5 ;第5行
mov dl,12 ;第12列
int 10h
mov ah,9 ;9号子程序,显示字符
mov al,'a' ;显示的字符
mov bh,0 ;页
mov bl,7 ;颜色
mov cx,3 ;重复个数
int 10h
int 21h
指令,(ah) = 4ch
表示4c号子程序,代表返回功能,(al)
存储返回值。
int 21h
的9号子程序可以显示以$
结尾的字符串。ds:dx
指向字符串。
14.端口
cpu可以直接读取3个地方的数据:
- cpu内部寄存器
- 内存单元
- 端口
外设接口芯片的内部有若干存储器,cpu将这些寄存器当作端口来访问。
cpu通过端口和外设联系
端口的读写指令只有两条:in
和out
.
in al,60h
:从60h号端口读入1个字节。
out 20h,al
:往20h端口写入一个字节。
记法:
in/out dst src
这两个指令只能用ax
或al
读写端口数据。
对256-65535
范围内的端口读写时,端口号存放在dx
中。
cmos ram
这个芯片包含一个实时钟和128字节的ram。
靠电池供电,关机后实时钟仍工作,ram信息不丢失。0-0dh
保存时间信息,其余保存系统配置信息,如bios程序读取 。
芯片有两个端口:
- 地址端口
70h
- 数据端口
71h
。
shr和shl
shl
,逻辑左移,功能:
- 将一个寄存器或内存单元的数据左移,右边用0补充
- 移出的一位写入
cf
格式:shl al,1
移动位数大于1时,把位数放入cl
中。shl al,cl
shr
同理,而且最高位也是用0补充。
当前时间:
- 秒:00h
- 分:02h
- 时:04h
- 日:07h
- 月:08h
- 年:09h
它们各占1字节,用BCD码表示两位十进制数,符合人的思维。
0001 0100
表示14
BCD码值+30H,就变成了十进制。
取得当前月份:
mov al,8
out 70h,al
in al,71h
显示当前月份
assume cs:codesg
codesg segment
start:
mov al,8
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl ; ah: 0 or 1
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
codesg ends
end start
15.外中断
外中断源分为两类:
- 可屏蔽中断
- 不可屏蔽中断
可屏蔽中断占大多数,是否响应取决于if
位,1则响应。
所以中断过程将
if
置为0,禁止其它可屏蔽中断。
可屏蔽中断的中断类型码通过数据总线进入cpu;内中断的中断类型码则是cpu内部产生的。
sti
指令,设置if
为1.
cli
指令,设置if
为0.
8086不可屏蔽的中断类型码固定为2,不需要手动取得。
pushf
- 设置
tf
和if
为0 push cs
,push ip
(ip) = 8,(cs) = (0ah)
键盘对应9号中断。按下一个键时,键盘芯片产生扫描码(通码),说明了键的位置,并送入60h端口。
松开时,也产生扫描码,说明了键的位置(断码),并送入60h端口。
扫描码占1个字节,通码第7位为0,断码第7位为1.
断码 = 通码 + 80H
键盘输入到达60h端口时,芯片会发出终端类型码为9的可屏蔽中断信息。
int 9
中断例程读取扫描码:
- 若是字符,则将扫描码和字符ascii码送入内存bios键盘缓冲区。
- 若是控制键(如ctrl)或切换键(如capslock),则转换为状态字节存入内存专门的状态字节单元。
bios键盘缓冲区有16个字单元,但只能存储15个键盘输入,每个输入用1个字,高位字节存放扫描码,低位字节存放字符。
0040:17
单元存储键盘状态字节。
;编程:在屏幕中间依次显示“a”~“z”,并可以让人看清。
;在显示的过程中,按下“ESC”键后,改变显示的颜色。
assume cs:code,ss:stack,ds:data
data segment
db 0,0,0,0
data ends
stack segment
db 128 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,128
mov ax,0
mov es,ax ;中断向量表的段地址为0
push es:[9*4] ;将原来 int 9 中断例程的指令的
pop ds:[0] ;ip保存到ds:[0],cs保存到ds:[2]单元中
push es:[9*4+2]
pop ds:[2]
mov word ptr es:[9*4],offset int9
mov word ptr es:[9*4+2],cs ;设置新的中断例程
mov ax,0b800h
mov es,ax
mov si,160*12+40*2
mov ah,'a'
s:
mov es:[si],ah
call delay
inc ah
cmp ah,'z'
jna s
mov ax,0 ;中断例程的入口地址恢复为
mov es,ax ;原来的入口地址
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
mov ax,4c00h
int 21h
delay:
push ax
push dx
mov dx,1000h
mov ax,0
s1:
sub ax,1
sbb dx,0
cmp ax,0
jne s1
;cmp dx,0ff0h
cmp dx,0
jne s1
pop dx
pop ax
ret
;--------以下为新的 int 9 中断例程--------------------
int9:
push ax
push bx
push es
push si
in al,60h ;从60h端口读入数据
pushf
pop bx
pushf
and bx,1111110011111111b
push bx
popf
call dword ptr ds:[0] ;对int指令进行模拟,调用原来的int 9中断例程
cmp al,1
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[si+1] ;属性增加1,改变颜色
int9ret:
pop si
pop es
pop bx
pop ax
iret
code ends
end start
用复杂度为二次方的代码实现延时
assume cs:codesg
codesg segment
start:
call delay
mov ax,4c00h
int 21h
delay:
push ax
push dx
mov dx,0f000h
mov ax,0
s:
sub ax,1
sbb dx,0
cmp ax,0
jne s
cmp dx,0
jne s
pop dx
pop ax
ret
codesg ends
end start
16.直接定址表
数据标号
assume cs:codesg,ds:datasg
datasg segment
a db 1,2,3,4,5,6,7,8
b dw 0
...
标号a
同时代表了地址和长度(字,2字节)。
mov al,a [si]
相当于mov al,cs:[0]
,si
也可以换成其它寻址方式。
mov ax,b
相当于mov ax,cs:[8]
mov b,2
相当于mov word ptr cs:[8],2
inc b
相当于inc word ptr cs:[8]
这种数据标号用在除了代码段之外的段中描述数据的地址和长度,通常是数据段。
有冒号的地址标号只能在代码段中使用。数据段等其它段中不需要用到地址。
assume cs:codesg,ds:datasg
codesg segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
上面的c
处,相当于c dw offset a,offset b
如果是c dd a,b
,则相当于c dd offset a,seg a,offset b,seg b
seg
操作符:取得某一标号的段地址
直接定址表
通过数据计算出所要查找的元素的位置的表,叫做直接定址表。
我们可以用一张表,依次存储十六进制0-f。
assume cs:codesg
codesg segment
start:
mov al,9ch
call showbyte
mov ax,4c00h
int 21h
showbyte:
jmp short show
table db "0123456789abcdef"
show:
push es
push bx
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1
and al,00001111b
mov bl,ah
xor bh,bh
mov ah,table[bx]
mov bl,al
xor bh,bh
mov al,table[bx]
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov byte ptr es:[160*12+40*2+1],2
mov es:[160*12+40*2+2],al
mov byte ptr es:[160*12+40*2+3],2
pop bx
pop es
ret
codesg ends
end start
用表的目的:
- 算法清晰
- 快
- 程序可扩充
也可以在表中存储数据标号。数据标号处可以是字符串,也可以是被调用函数的地址。
程序入口地址的直接定址表
实现子程序setscreen
,它有以下功能:
- 清屏
- 设置前景色
- 设置背景色
- 向上翻滚
用ah
传递功能号,用al
传递颜色,范围0-7:
- 0:清屏
- 1:设置前景色
- 2:设置背景色
- 3:向上翻滚
assume cs:codesg
codesg segment
table dw sub0,sub1,sub2,sub3
start:
mov ah,1
mov al,2
cmp ah,3
ja e
mov bl,ah
xor bh,bh
add bx,bx
call word ptr table[bx]
e:
mov ax,4c00h
int 21h
sub0:
push ax
push bx
push cx
push es
push di
mov ax,0b800h
mov es,ax
mov bx,0
mov cx,2000
sub0s:
mov byte ptr es:[bx], ' '
add bx,2
loop sub0s
mov di,0
mov cx,80
pop di
pop es
pop cx
pop bx
pop ax
ret
sub1:
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub1s:
and byte ptr es:[bx], 11111000b
or es:[bx], al
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2:
push bx
push cx
push es
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s:
and byte ptr es:[bx], 10001111b
or es:[bx], al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3:
push ax
push cx
push es
push ds
push si
push di
mov ax,0b800h
mov ds,ax
mov es,ax
mov si,160
mov di,0
cld
mov cx,24
sub3s:
push cx
mov cx,160
rep movsb
pop cx
loop sub3s
mov cx,80
sub3s1:
mov byte ptr ds:[si], ' '
add bx,2
loop sub3s1
pop di
pop si
pop es
pop cx
pop ax
ret
codesg ends
end start
17.使用bios进行键盘输入和磁盘读写
int 16h
之前讲过了int 9中断例程对键盘输入的处理,而int 16h
的0号功能可以读取从键盘缓冲区读取输入。
mov ah,0
int 16h
执行后,ah
存储扫描码,al
存储ascii码。
上面的工作过程如下:
- 检测j键盘缓冲区有无数据
- 没有则重复1,等待随时发生的输入
- 读取字单元,送入
ax
- 删除键盘缓冲区输入
实现程序,输入r|g|b
改变颜色。
assume cs:codesg
codesg segment
start:
mov ah,0
int 16h
mov ah,1
cmp al, 'r'
je red
cmp al, 'g'
je green
cmp al, 'b'
je blue
jmp short e
red:
shl ah,1
green:
shl ah,1
blue:
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
s:
and byte ptr es:[bx],11111000b
or byte ptr es:[bx],ah
add bx,2
loop s
e:
mov ax,4c00h
int 21h
codesg ends
end start
字符串输入
基本功能:
- 输入的同时要显示出来
- 输入回车则停止
- 删除已经输入的字符串
参数:
dh\dl
存储显示的行\列ds:si
指向以0结尾的字符串,
思考一下会发现,字符串可以存在栈里。
程序伪代码:
while(1)
{
int 16h
switch(al)
{
case(字符):
压栈;
显示;
continue;
case(退格):
弹栈;
显示;
continue;
case(回车):
push 0
显示;
break;
}
}
入栈、出栈、显示子程序
参数:
ah
存储功能号,0为入栈,1为出栈,2为显示ds:si
指向字符串栈空间- 入栈时,
push al
- 出栈时,
pop al
- 显示时,
dh\dl
存储显示的行\列
下面的实现有bug,暂且保存。
assume cs:codesg,ds:datasg
datasg segment
db 0fh dup(0)
datasg ends
codesg segment
start:
;mov dh,13
;mov dl,40
;mov al,'a'
;mov ah,0
;call charstack
;mov ah,2
;call charstack
;mov dh,13
;mov dl,40
;mov al,'b'
;mov ah,0
;call charstack
;mov ah,2
;call charstack
mov ax,datasg
mov ds,ax
mov si,0
mov dh,13
mov dl,40
call getstr
mov ax,4c00h
int 21h
getstr:
push ax
mov ah,0
int 16h
cmp al,20h ;32-126 are the characters on the keyboard
jb nochar
mov ah,0 ;push al
call charstack
mov ah,2 ;show str
call charstack
jmp getstr
nochar:
cmp ah,0eh
je backspace
cmp ah,1ch
je enter
jmp getstr
backspace:
mov ah,1
call charstack
mov ah,2
call charstack
jmp getstr
enter:
mov al,0
mov ah,0
call charstack
mov ah,2
call charstack
pop ax
ret
;getstr end
charstack:
jmp short stackstart
table dw charpush,charpop,charshow
top dw 0
stackstart:
cmp ah,2
ja charend
push bx
push cx
push di
push es
mov bl,ah
xor bh,bh
add bx,bx ;table dw
jmp word ptr table[bx]
charpush:
mov bx,top
mov [si][bx],al
inc top
jmp charend
charpop:
cmp top,0
je charend
mov bx,top
mov al,[si][bx]
dec top
jmp charend
charshow:
mov bx,0b800h
mov es,bx
mov al,160
xor ah,ah
dec dh
mul dh
mov di,ax ;row
add dl,dl ;ascii and color
xor dh,dh
add di,dx ;dx:column
mov bx,0
mov cx,top
charshows:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+1],01001010b
;mov byte ptr es:[di+2], ' '
inc bx
add di,2
loop charshows
charend:
pop es
pop di
pop cx
pop bx
ret
codesg ends
end start