16位汇编笔记12-17

8 篇文章 0 订阅

16位汇编笔记12-17

12.内中断

12.1 中断

发出中断请求的来源叫做中断源

中断根据中断源的不同,可以这样分类:
{ 硬 件 中 断 { 外 部 中 断 内 部 中 断 软 件 中 断 \begin{cases}硬件中断 \begin{cases}外部中断 \\ \\内部中断 \end{cases} \\ \\软件中断 \end{cases}\\

外部中断有键盘中断、定时器中断等,可以屏蔽。

内部终端有断电、奇偶校验出错、运算错误等,不可屏蔽。

软件中断并不是真正的中断,只是可被调用的一般程序及系统功能,如int 21h

中断优先权由高到低:

  1. 除零、溢出、软件中断
  2. 不可屏蔽中断
  3. 可屏蔽中断
  4. 单步中断

中断是多任务的基础

中断标识码(中断号)用来定位中断处理程序。取值范围为 0 ~ 255(8086) 。利用中断向量表可以根据8位的中断类型码得到中断处理程序的段地址和偏移地址。

8086把中断向量表存放在内存0000:00000000:03ff处,因为每个地址有2字节段地址和2字节偏移地址,共256*4个字节。

0020:00-0020:ff在8086中没有用到,所以是安全空间。

cpu硬件通过终端类型码设置csip的过程叫做中断过程:

  1. 从中断信息中取得中断类型码
  2. pushf
  3. 设置tfif为0
  4. push cs
  5. push ip
  6. (ip) = (中断号*4),(cs) = (中断号*4+2)

12.2 中断处理程序

常规步骤:

  1. 保存用到的寄存器
  2. 处理中断
  3. 恢复用到的寄存器
  4. 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. 取得中断类型码1
  2. pushftf,if置1
  3. push cs; push ip
  4. (ip) = (1*4),(cs) = (1*4+2)

也有一些情况,即使发生中断,cpu也不会响应,比如,mov ss,ax之后,因为sssp设置应连续完成,若响应中断,则上面中断过程第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的偏移地址。

biosdos中断例程安装过程:

  1. 开机后,ffff:0有一条跳转指令,转去执行硬件系统检测和初始化程序。
  2. 初始化程序会建立bios所支持的中断向量表,将中断例程的入口地址登记在中断向量表中。
  3. 检测并初始化后,int 19h,进行os的引导,将计算机交给os。
  4. 中断例程装入内存,建立相应的中断向量。

biosdos提供的中断例程,都用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通过端口和外设联系

端口的读写指令只有两条:inout.

in al,60h:从60h号端口读入1个字节。

out 20h,al:往20h端口写入一个字节。

记法:in/out dst src

这两个指令只能用axal读写端口数据。

256-65535范围内的端口读写时,端口号存放在dx中。

cmos ram

这个芯片包含一个实时钟和128字节的ram。

靠电池供电,关机后实时钟仍工作,ram信息不丢失。0-0dh保存时间信息,其余保存系统配置信息,如bios程序读取 。

芯片有两个端口:

  • 地址端口70h
  • 数据端口71h

shr和shl

shl,逻辑左移,功能:

  1. 将一个寄存器或内存单元的数据左移,右边用0补充
  2. 移出的一位写入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,不需要手动取得。

  1. pushf
  2. 设置tfif为0
  3. push cspush ip
  4. (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码。

上面的工作过程如下:

  1. 检测j键盘缓冲区有无数据
  2. 没有则重复1,等待随时发生的输入
  3. 读取字单元,送入ax
  4. 删除键盘缓冲区输入

实现程序,输入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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值