以前我们讨论的都是CPU对指令的执行。

我们知道,CPU在计算机系统中,除了能够执行指令,进行运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出。也就是说,CPU除了有运算能力外,还要有I/O(Input/Output,输入/输出)能力。

比如,我们按下键盘上的确个键,CPU最终要能够处理这个键。在使用文本编辑器时,按下a键后,我们可以看到屏幕上出现“a”,是CPU将从键盘上输入的键所对应的字符送到显示上的。

要及时处理外设的输入,显然需要解决两个问题:

一是外设的输入随时可能发生,CPU如何得知?

二是CPU从何处得到外设的输入?

接口芯片和端口

在前面我们知道,在PC系统的接口卡和主板上,装有各种接口芯片。

这些外设接口芯片的内部有若干寄存器,CPU将这些寄存器当作端口来访问。

外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;

CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。

CPU还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。

可见,CPU通过端口和外部设备进行联系。

外中断信息

现在,我们知道了外设的输入被存放在端口中,可是外设的输入随时都有可能到达,CPU如何及时地知道,并进行处理呢?更一般地讲,就是外设随时都可能发生需要CPU及时处理的事件,CPU如何及时得知并进行处理?

CPU提供中断机制来满足这种需要。前面讲过,当CPU的内部有需要处理的事情发生的时候,将产生中断信息,引发中断过程。这种中断信息来自CPU的内部。

还有一种中断信息,来自于CPU外部,当CPU外部有需要处理的事情发生的时候,比如说,外设的输入到达,相关芯片将向CPU发出相应的中断信息。CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。

在PC系统中,外中断源一共有两类:

1、可屏蔽中断

可屏蔽中断是CPU可以不响应的外中断,CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置。当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;如果IF=0,则不响应可屏蔽中断。

回忆一下内中断所引发的中断过程:

1)取中断类型码n;

2)标志寄存器入栈,IF=0,TF=0;

3)CS、IP入栈;

4)(IP)=(n*4),(CS)=(n*4+2)

由此转去执行中断处理程序。

可屏蔽中断所引发的中断过程,除在第1步的实现上有所不同外,基本上和内中断的中断过程相同。因为可屏蔽中断信息来自于CPU外部,中断类型码是通过数据总路线送入CPU的;而内中断的中断类型码是在CPU内部产生的。

现在,我们可以解释中断过程中将IF设置为0的原因了。将IF置0的原因就是,在进入中断处理程序后,禁止其他的可屏蔽中断。

当然,如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1。8086CPU提供的设置IF的指令如下:

sti,用于设置IF=1;

cli,用于设置IF=0。

2、不可屏蔽中断

不可屏蔽中断是CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。

对于8086CPU,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码。则不可屏蔽中断的中断过程为:

(1)标志寄存器入栈,IF=0,TF=0;

(2)CS、IP入栈;

(3)(IP)=(8),(CS)=(0AH)。

几乎所有由外设引发的外中断,都是可屏蔽中断。当外设有需要处理的事件(比如说键盘输入)发生时,相关芯片向CPU发出可屏蔽中断信息。不可屏蔽中断是在系统中有必须处理的情况发生时用来通知CPU的中断信息。

PC机键盘的处理过程

下面来看一下键盘输入的处理过程,并以此来体会一下PC机处理外设输入的基本方法。

1、键盘输入

键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。

按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60H。

松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入60H端口中。

一般将按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码。

扫描码长度为一个字节,通码的第7位为0,断码的第7位为1,即:

断码=通码+80H

比如:g键的通码为22H,断码为a2H。

2、引发9号中断

键盘的输入到达60H端口时,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息。CPU检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,转去执行int 9中断例程。

3、执行int 9中断例程

BIOS提供了int 9中断例程,用来进行基本的键盘输入处理,主要的工作如下:

(1)读出60H端口中的扫描码;

(2)如果是字符键的扫描码,将该扫描码和它所对应的字符码(即ASCII码)送入内存中的BIOS键盘缓冲区;如果是控制键(比如Ctrl)和切换键(比如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元。

(3)对键盘系统进行相关的控制,比如说,向相关芯片发出应答信息。

BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区。该内存区可以存储15个键盘输入,因为int 9中断例程除了接收扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。

0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节各位记录的信息如下:

0:右shift状态,置1表示按下右shift键;

1:左shift状态,置1表示按下左shift键;

2:Ctrl状态,置1表示按下Ctrl键;

3:Alt状态,置1表示按下Alt键;

4:ScrolLock状态,置1表示Scroll指示灯亮;

5:NumLock状态,置1表示小键盘输入的是数字;

6:CapsLock状态,置1表示输入大写字母;

7:Insert状态:置1表示处于删除状态。

编写int 9中断例程

键盘输入的处理过程:

1)键盘产生扫描码;

2)扫描码送入60h端口;

3)引发9号中断;

4)CPU执行int 9中断例程处理键盘输入。

上面的过程中,第(1)、(2)、(3)步都是由硬件系统完成的。我们能够改变的只有int 9的中断处理程序。

编程:在屏蔽中间依次显示“a”~“z”,并可以让人看清。在显示的过程中,按下Esc键后,改变显示的颜色。

assume cs:code

code segment

start:  mov ax,0b800h

mov es,ax

mov ah,’a’

s:     mov es:[160*12+40*2],ah

inc ah

cmp ah,’z’

jna s

mov ax,4c00h

int 21h

code ends

end start

上面的程序的执行过程中,我们无法看清屏幕上的显示,因为一个字母刚显示到屏幕上,因为CPU执行指令太快了。

我们应该在每显示一个字母后,延时一段时间,让人看清后,再显示下一个字母。

那么如何延时呢?我们让CPU执行一段时间的空循环,因为现在的CPU的速度都非常快,所以循环的次数一定要大,我们用两个16位寄存器来存放32位的循环次数。如下:

mov dx,10h

mov ax,0

s: sub ax,1

sbb dx,0

cmp ax,0

jne s

cmp dx,0

jne s

上面的程序,循环100000h次。我们可以将循环延时的程序段写为一个子程序。

assume cs:code

stack segment

db 128 dup (0)

stack ends

code segment

start:    mov ax,stack

mov ss,ax

mov sp,128

mov ax,0b800h

mov ex,ax

mov ah,’a’

s:   mov es:[160*12+40*2],ah

call delay

inc ah

cmp ah,’z’

jna s

mov ax,4c00h

int 21h

delay:   push ax

push dx

mov dx,1000h        ;循环10000000次,读者可以根据自己机器的速度调整循环次数

mov ax,0

s1: sub ax,1

sbb dx,0

jne sl

cmp dx,0

jne s1

pop dx

pop ax

ret

code ends

end start

上面的程序,显示“a”~“z”,并可以让人看清,这个任务已经实现。

那么如何实现,按下Esc键后,改变显示的颜色呢?

键盘输入到达60h端口后,就会引发9号中断,CPU则转去执行int 9中断例程。我们可以编写int 9中断例程,功能如下:

(1)从60h端口读出键盘的输入;

(2)调用BIOS的int 9中断全程,处理其他硬件细节;

(3)判断是否为Esc键的扫描码,如果是,改变显示的颜色后返回;如果不是则直接返回。

分析:

1、从端口60h读出键盘的输入

in al,60h

2、调用BIOS的int 9中断例程

注意,我们写的中断处理程序要成为新的int 9中断例程,主程序必须要将中断向量表中的int 9中断例程的入口地址改为我们写的中断处理程序的入口地址。则在新的中断处理程序中调用原来的int 9中断例程时,中断向量表中的int 9中断例程的入口地址却不是原来的int 9中断例程的地址,所以我们不能使用int 指令直接调用。

要能在我们写的新的中断例程中调用原来的中断例程,就必须在将中断向量表中的中断例程的入口地址改为新地址之前,将原来的入口地址保存起来。这样,在需要调用的时候我们才能找到原来的中断例程的入口。

有了入口地址后,我们如何进行调用呢?

当然不能使用指令int 9来调用,我们可以用别的指令来对int 指令进行一些模拟,从而实现对中断例程的调用。

int指令在执行的时候,CPU进行下面的工作:

1)取中断类型码n;

2)标志寄存器入栈;

3)IF=0,TF=0;

4)CS、IP入栈;

5)(IP)=(n*4),(CS)=(n*4+2)。

取中断类型码是为了定位中断例程的入口地址,在我们的问题中,中断例程的入口地址已经知道。所以,我们用别的指令模拟int指令时候,不需要做第(1)步。在假设要调用的中断例程的入口地址在ds:0和ds:2单元中的前提下,我们将int过程用下面几步模拟:

1)标志寄存器入栈;

2)IF=0,TF=0;

3)CS、IP入栈;

4)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。

所以int 过程的模拟过程变为:

1)标志寄存器入栈;

2)IF=0,TF=0;

3)call dword ptr ds:[0]

对于(1),可用pushf实现。

对于(2),可用下面的指令实现。

pushf

pop ax

and ah,11111100b                 ;IF和TF为标志寄存的第9位和第8位

push ax

popf

则,模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序为:

pushf            ;标志寄存器入栈

pushf

pop ax

and ah,11111100b

push ax

popf                             ;IF=0, TF=0

call dword ptr ds:[0]      ;CS、IP入栈;(IP)=((ds)*16+0), (CS)=((ds)*16+2)

3、如果是Esc键扫描码,改变显示的颜色后返回

如果改变显示的颜色?

显示的位置是屏幕的中间,即第12行40列,显存中的偏移地址为:160*12+40*2。所以字符的ASCII码要送入b800:160*12+40*2处。而b800:160*12+40*2+1处是字符的属性,我们只要改变此处的数据就可以改变在b800:160*12+40*2处显示的字符的颜色了。

该程序的最后一个问题是,要在程序返回前,将中断向量表中的int 9中断例程的入口地址恢复为原来的地址。否则程序返回后,别的程序将无法使用键盘。

完整的程序如下:

assume cs:code

stack segment

db 128 dup (0)

stack ends

data segment

dw 0,0

data ends

code segment

start:mov ax,stack

mov ss,ax

mov sp,128

mov ax,data

mov ds,ax

mov ax,0

mov es,ax

push es:[9*4]

pop ds:[0]

push es:[9*4+2]

pop ds:[2]      ;将原来的int 9中断例程的入口地址保存在ds:0、ds:2单元中

mov word ptr es:[9*4],offset int9

mov es:[9*4+2],cs   ;在中断向量表中设置新的int 9中断例程的入口地址

mov ax,0b800h

mov es,ax

mov ah,'a'

s: mov es:[160*12+40*2],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]    ;将中断向量表中int 9中断例程的入口恢复为原来的地址

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,0

jne s1

pop dx

pop ax

ret

;-------以下为新的int 9中断例程------------------

int9: push ax

push bx

push es

in al,60h

pushf

pushf

pop bx

and bh,11111100b

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:[160*12+40*2+1]   ;将属性值加1,改变颜色

int9ret:pop es

pop bx

pop ax

iret

code ends

end start

检测点15.1

(1)模拟int指令调用原int9中断例程的程序段是可以精简的,因为在进入中断例程后,IF和TF都已经置0,没有必要再进行设置了。

对于程序段:

pushf

pushf

pop ax

and ah,11111100b

push ax

popf

call dword ptr ds:[0]

可以精简为:

pushf

call dword ptr ds:[0]

再条指令。

(2)仔细分析上面程序中的主程序,会发现一个潜在的问题?在主程序中,如果在执行设置int9中断例程的段地址和偏移地址的指令之间发生了键盘中断,则CPU将转去一个错误的地址执行,将发生错误。

排除潜在问题方法:

在pop ds:[2]指令后加入一条cli指令,并在mov es:[9*4+2],cs指令后加入一条sti指令即可。意思是在这段期间,让IF=0,禁止或屏蔽外中断处事程序的执行。

安装新的int9中断处理例程

安装一个新的int 9中断例程,使得原int 9中断例程的功能得到扩展。

任务:安装一个新的int 9中断例程,功能:在DOS下,按F1键后改变当前屏幕的显示颜色,其他的键照常处理。

分析:

(1)改变屏幕的显示颜色

改变从B8000开始的4000个字节中的所有奇地址单元中的内容,当前屏幕的显示颜色即发生改变。程序如下:

mov ax,0b800h

mov es,ax

mov bx,1

mov cx,2000

s: nc byte ptr es:[bx]

add bx,2

loop s

(2)其他键照常处理

可以调用原int 9中断处理程序,来处理其他的键盘输入。

(3)原int 9中断例程入口地址的保存

因为在编写的新int 9中断例程中要调用原int 9中断例程,所以,要保存原int 9中断例程的入口地址。保存在哪里?显然不能保存在安装程序中,因为安装程序返回后地址将丢失。我们将地址保存在0:200单元处。

(4)新int 9中断例程的安装

我们可将新的int 9中断例程安装在0:204处。

完整的程序如下:

assume cs:code

stack segment

db 128 dup (0)

stack ends

code segment

start:

mov ax,stack

mov ss,ax

mov sp,128

push cs

pop ds

mov ax,0

mov es,ax

mov si,offset int9    ;设置ds:si指向源地址

mov di,204h           ;设置es:di指向目的地址

mov cx,offset int9end - offset int9   ;设置cx为传输长度

cld                   ;设置传输方向为正

rep movsb

push es:[9*4]

pop es:[200h]

push es:[9*4+2]

pop es:[202h]     ;保存原有的int 9中断例程入口地址

cli               ;设置IF为0,禁止CPU执行可屏蔽外中断。

mov word ptr es:[9*4],204h

mov word ptr es:[9*4+2],0   ;设置int 9中断向量,指向新的中断例程入口地址

sti               ;恢复设置IF为1

mov ax,4c00h

int 21h

int9:push ax

push bx

push cx

push es

in al,60h

pushf

call dword ptr cs:[200h]    ;当此中断例程执行时(CS)=0

;cmp al,1                  ;F1的扫描码为3bh

;jne int9ret

mov ax,0b800h

mov es,ax

mov bx,1

mov cx,2000

s:mov byte ptr es:[bx],2

;inc byte ptr es:[bx]

and bx,2

loop s

int9ret:pop es

pop cx

pop bx

pop ax

iret

int9end:nop

code ends

end start

通过对键盘输入的处理,了解了CPU对外设输入的通常 通常方法,即:

1)外设的输入送入端口;

2)向CPU发出外中断(可屏蔽中断)信息;

3)CPU检测到可屏蔽中断信息,如果IF=1,CPU在执行完当前指令后响应中断,执行相应的中断例程;

4)可在中断例程中实现对外设输入的处理。

端口和中断机制,是CPU进行I/O的基础。

指令系统总结

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, reft;

5)中断,比如:int, iret。

5、处理机控制指令

这些指令对标志寄存器或其他处理机状态进行设置,比如:cld, std, cli, sti, nop, clc, cmc, stc, hlt, wait, csc, lock等都是处理机控制指令。

6、串处理指令

这些指令对内存中的批量数据进行处理,比如:movsb, movsw, cmps, scas, lods, stos等。

基要使用这些指令方便地进行批量数据的处理,则需要和rep, repe, repne等前缀指令配合使用。

汇编学习--7.16--外中断相关推荐

  1. 外中断---汇编学习笔记

    外中断 要及时处理外设的输入,显然需要解决两个问题: 外设的输入随时可能发生,CPU如何得知? CPU从何处得到外设的输入? 15.1 接口芯片和端口 外设的输入不直接送入内存和CPU,而是送入相关的 ...

  2. 外中断02 - 零基础入门学习汇编语言70

    第十五章:外中断02 让编程改变世界 Change the world by program 小甲鱼和大家谈谈心 一个帖子引发小甲鱼的反省! 猫姐曾经说过,步子别迈太大,容易扯着蛋! 结果还真蛋疼了- ...

  3. 外中断03 - 零基础入门学习汇编语言71

    第十五章:外中断03 让编程改变世界 Change the world by program 继续编写int 9中断例程 第三步:如果是Esc键的扫描码,改变显示的颜色后返回...... 那么,下一个 ...

  4. 汇编学习笔记——汇编指令

    目录 汇编指令 nop指令 mov.add.sub指令 adc.sbb指令 and.or指令 移位指令 逻辑左/右移指令 循环左/右移指令 算术左/右移指令 带进位循环左/右移指令 inc指令 pus ...

  5. 8086汇编学习小记-王爽汇编语言实验12

    8086汇编学习小记-王爽汇编语言实验12 0号中断处理程序,开始安装在0000:0200处的程序最后用死循环导致显示不出'divided error',改成直接退出就正常显示了.注意修改ss,sp之 ...

  6. 8086汇编学习之[BX],CX寄存器与loop指令,ES寄存器等

    同类学习笔记总结: (一).8086汇编学习之基础知识.通用寄存器.CS/IP寄存器与Debug的使用 (二).8086汇编学习之DS寄存器.SS/SP寄存器 一.汇编程序的基本格式: 1.基本格式与 ...

  7. 汇编学习(1)——基础知识

    汇编学习(1)--基础知识 ---谨以此系列文章记录我的汇编学习.  关于汇编 说起汇编语言,那自然不得不想到机器语言,在汇编语言尚未诞生之际,程序猿们只能非常苦逼的敲着0和1,还要记住一大堆复杂难记 ...

  8. 寄存器---汇编学习笔记

    第二章 寄存器 2.0 寄存器的绪论 一个典型的CPU由运算器.控制器.寄存器(CPU工作原理)等器件构成.内部总线实现 CPU 内部各个器件之间的联系,外部总线实现CPU和主板其他器件的联系. 在C ...

  9. ARM 汇编基础教程番外篇 ——配置实验环境

    From:https://zhuanlan.zhihu.com/p/29145513 win10 arm 汇编环境 Windows 平台下搭建 ARM 汇编集成环境:https://jingyan.b ...

最新文章

  1. leetcode-102 二叉树的层次遍历
  2. GeoQuiz项目的开发与总结2
  3. RSA签名的PSS模式
  4. command-line: line 0: Bad configuration option: PermitLocalCommand
  5. ppt流程图字体太小_论文答辩PPT攻略,答辩季你准备好了吗?
  6. memcache---mongodb---redis比较
  7. Symbian 编程总结导读
  8. IDA Pro 数据库文件、函数窗口、结构体窗口
  9. 计算机二级web题目(9.1)--综合选择题3
  10. 一文知晓浪潮云海OS在SPEC Cloud测试中的调优实践!
  11. 零基础想学Python,明白这2点,越快年薪30W!
  12. ModelArts 与HiLens Kit联合开发丨行人社交距离风险提示Demo
  13. go设置linux ip,设置linux虚拟机的静态ip-Go语言中文社区
  14. mysql gis index 索引原理_从原理到优化,深入浅出数据库索引
  15. Q145: 三次曲线对比及其矩阵表示(Bezier, B-Spline, Hermite, Catmull-Rom)
  16. java中整数和字符串间的转换方法
  17. darknet53 作用_darknet53 yolo 下的识别训练
  18. 是运用计算机科学的基础知识进行问题求解,计算机基础-第一章计算机基础知识.ppt...
  19. 【渗透测试】IIS6.0的安装、使用与相关漏洞
  20. proxy_cfw全局代理_浏览器代理配置(chromium based(edge)/firefox/IDM)

热门文章

  1. tensorflow之FIFOQueue
  2. tensorflow之control_dependencies
  3. linux python安装pip_linux安装pip2.7
  4. 处理ajax表单验证结果,使用 jQuery Form 插件完成带数据验证的 Ajax 表单
  5. go语言打印日期_基于 Go 语言开发在线论坛(八):消息、视图及日期时间本地化...
  6. mysql多表关联update
  7. vivado 设计约束学习笔记1
  8. cesium +vue项目怎么运行
  9. java list 分组_Java8 快速实现List转map 、分组、过滤等操作
  10. html怎么在服务器环境,如何搭建node服务器环境?