正在加载……
专注、离线、切勿分心
接口芯片和端口
    PC系统的接口卡和主板上,装有各种接口芯片。这些外设接口的内部有若干寄存器,CPU 将这些寄存器当做端口来访问。
    外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU 向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。CPU也是用这种方式向外设输出控制命令。
    CPU通过端口和外部设备进行联系。





外中断信息
eg:外设的输入到达,相关的芯片将向 CPU 发出相应的中断信息。CPU 在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
外中断源一共有两类:
1、可屏蔽中断
      可屏蔽中断是CPU可以不相应的外中断。CPU是否响应可屏蔽中断,要看标志寄存器的 IF 位的设置。当 CPU 检测到可屏蔽中断信息时,如果 IF=1,则 CPU 在执行完当前指令后相应中断,引发中断过程;如果 IF=0,则不响应可屏蔽中断
可屏蔽中断信息来自于 CPU 外部,中断类型码是通过数据总线送入CPU的;而内中断的中断类型码是在CPU内部产生的。
① 获取到中断类型码 n;
标志寄存器入栈,IF=0,TF=0;    如果在中断处理程序中需要处理可屏蔽中断,可以用指令将 IF 置1。
③ CS、IP 入栈;
④ (IP)=(n*4),(CS)=(n*4+2)
转去执行中断处理程序
sti ,用于设置 IF=1;
cli ,用于设置 IF=0;
2、不可屏蔽中断
      不可屏蔽中断是CPU必须立即响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
      对于 8086CPU,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码。不可屏蔽中断的中断过程:
① 标志寄存器入栈,IF=0,TF=0;
② CS、IP 入栈;
③ (IP)=(8),(CS)=(0AH)。

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






PC 机键盘的处理过程
    键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。
    按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为 60H
    松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入 60H 端口中。
    一般按下一个键时产生的扫描码称为通码松开一个键产生的扫描码称为断码。扫描码长度为一个字节,通码的第7位为0,断码的第7位为1。即:断码=通码+80H
eg:  g 键的通码为22H,断码为a2H。
 // 都是通码






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





执行 int 9 中断例程
     BIOS 提供了 int 9 中断例程,用来进行基本的键盘输入处理,主要的工作如下:
(1) 、读出 60H 端口中的扫描码;
(2)、 如果是字符键的扫描码,将该扫描码和它所对应的字符码 (ASCII)送入内存中的 BIOS 键盘缓冲区;如果是控制键(eg:Ctrl)和切换键(eg:Caps Lock)的扫描码,则将其转换为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元
(3)、对键盘系统进行相关的控制,eg: 向相关芯片发出应答信息。
 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:ScrollLock 状态,置 1 表示 Scroll 指示灯亮; 5:NumLock 状态,置 1 表示小键盘输入的是数字;
6:Caps Lock 状态,置 1 表示输入大写字母; 7:Insert 状态,置 1 表示处于删除状态;





编写int 9中断例程
    从键盘输入的处理过程:
① 键盘产生扫描码;
② 扫描码送入 60h 端口;
③ 引发 9 号中断;
④ 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重复执行一段代码,达到延迟效果



assume cs:code
data segment
        db 128 dup (0)    ; 自己开辟栈空间,可以不要这步操作
data ends
code segment
start:        mov ax , data
        mov ss , ax
        mov sp , 128

        mov ax , 0b800h
        mov es , ax
        mov ah , 'a'
s:        mov es:[160*12+40*2] , ah
        mov byte ptr es:[160*12+40*2+1] , 01001010b
        call delay
        inc ah
        cmp ah , 'z'
        jna s

        mov ax , 4c00h
        int 21h

delay:        push ax
        push dx
        mov ax , 0
        mov dx , 5h
s1:        sub ax , 1     ; 首次执行(ax)=-1,计算机存的是补码ffff=65535
        sbb dx , 0        ; 第一次执行dx-0-(CF),CF的值为1,减完CF=0
        cmp ax , 0
        jne s1
        cmp dx , 0
        jne s1            ; 一次call执行减法的次数就是65536*5
        pop dx
        pop ax
        ret
code ends
end start






编写 int 9 中断例程
    键盘输入的处理过程:1、键盘产生扫描码;2、扫描码送入60h 端口;3、引发 9 号中断;4、CPU执行 int 9 中断例程处理键盘输入。其中,1~3 是由硬件系统完成,我们能改变的只有 int 9 中断处理程序。前面实现了显示“a~z”,现在实现在显示过程中,按下 Esc 键后,改变显示的颜色
    键盘输入到达 60h 端口后,就会引发 9 号中断,CPU 则转去执行 int 9 中断例程。
// 编写 int 9 中断例程
1、从 60h 端口读出键盘输入;
2、调用 BIOS 的 int 9 中断例程,处理其他硬件细节;
  我们写的中断处理程序要成为新的 int 9 中断例程,主程序必须要将中断向量表中的 int 9 中断例程入口地址改为我们写的中断处理程序的入口地址。则在新的中断处理程序中调用原来的 int 9 中断例程时,中断向量表中的 int 9 中断例程的入口地址却不是原来的 int 9 中断例程的地址。所以我们不能使用 int 指令直接调用。
  要能在我们写的新中断例程中调用原来的中断例程,就必须在将中断向量表中的中断例程的入口地址改为新地址之前,将原来的入口地址保存起来,这样,在需要调用的时候,我们才能找到原来的中断例程入口。 
3、判断是否为 Esc 键的扫描码,如果是,改变显示的颜色后返回;如果不是则直接返回。
int 指令在执行的时候,CPU进行下面几个工作:
① 取中断类型码 n ;     \ 目的是定位中断例程的入口地址。我们自己编写中断例程,这个地址我们自己设定,已经知道
② 标志寄存器入栈;
IF=0 , TF=0;
CS、IP 入栈;

假设:中断例程的入口地址( EA:SA )在 ds:0 和 ds:2 ,模拟 int 过程
① 标志寄存器入栈;
IF=0,TF=0;
CS、IP 入栈;
④ (IP)=((ds)*16+0),(CS)=((ds)*16+2)
注意:第 ③ ④ 步和 call dword ptr ds:[0] 的功能一样
int 过程的模拟过程:
① 标志寄存器入栈;                     可以由 pushf 实现
IF=0 , TF=0;                           可以由指令实现
call dword ptr ds:[0]

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)
如果是 Esc 键的扫描码,改变颜色后返回:
注意:程序返回前,要将中断向量表中的 int 9 中断例程的入口地址恢复为原来的地址,否则程序返回后,别的程序无法使用键盘中断。
 
assume cs:code
stack segment
        db 128 dup (0)
stack ends

data segment
        dw 0 , 0        ; 用来保存原本int 9中断例程的入口地址,在新的int9中用来调用原本的中断例程处理其他硬盘细节和程序返回之前恢复9号中断例程入口地址
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]        ; 中断向量表中的9号中断的中断例程入口偏移地址进栈
        pop ds:[0]        ; 定义的 data 段保存9号中断的原有中断例程入口偏移地址
        push es:[9*4+2]        ; 中断向量表中的9号中断的中断例程入口段地址进栈
        pop ds:[2]        ; 定义的data保存9号中断的原有中断例程入口段地址

        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 ax , 0
        mov dx , 5h

s1:        sub ax , 1
        sbb dx , 0
        cmp ax , 0
        jne s1
        cmp dx , 0
        jne s1
        pop dx
        pop ax
        ret
;--------新的中断9例程--------;
int9:        push ax              
        push bx
        push es

        in al , 60h
        pushf                ; 标志寄存器进栈保存

        pushf                ; 一种新的给寄存器赋值方法,要修改的数据先进栈,然后直接出栈放到寄存器中进行处理
        pop bx              
        and bh , 11111100b        ; IF和TF是标志寄存器的第9位和第8位
        push bx
        popf                ; 修改标志寄存器

        call dword ptr ds:[0]        ; 对 int 指令进行模拟,调用原来的int 9中断例程,处理键盘输入
; 这段代码的作用就是①保存当前CS和IP,②用ds:[0]~ds:[1]中的数据设置IP,ds:[2]~ds:[3]中的数据设置CS;这段内存我们事先放的是原本int 9的中断例程

; 原来的int 9要对键盘输入做一系列操作,如果改写了int 9中断例程,但是在自己新写的例程中不调用原来的
; 中断例程对输入做处理系统就要不知道发生了什么,运行卡住
; test,注释了这行代码,运行程序的时候键盘产生了回车的扫描码,
; 但是我们在程序中如果没有这行调用原来的中断例程,系统不知道怎么处理
; 只是拿扫描码来比较一下。
; 因为对于后面的键盘输入,调用不到原先的中断程序,我们的写的中断
; 也没涉及到处理键盘输入,系统崩溃;
; debug 单步调试的时候,单步到设置新的int 9中断例程入口地址就卡住
; 这是因为键盘输入-t命令的时候,入口地址更改命令要回车之后才执行
; 但是在这之前对于键盘的输入,系统就无法处理了。所以不支持单步调试
        cmp al , 1                ; Esc 的扫描码是 01h
        jne int9ret                ; 处理完键盘输入后,再执行我们想要的操作:如果按下 Esc 就改变颜色属性

        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


   // 在上面的汇编程序运行的时候,如果我不按 Esc 键,等到程序显示到 'z' 之后正常退出返回到DOS,但是如果我在程序显示 'a~z' 时候按下 Esc (我按了4次)程序退出之后又显示了 4个 '\' Dos 下面按下 'Esc' 显示的就是 '\' ;猜想:BIOS 键盘缓冲区存放int 9中断接收到的键盘输入,本来要显示在屏幕上的,但是程序执行的时候按下按键产生中断,修改了 IF=0,可屏蔽中断被屏蔽了,只能等到程序运行结束恢复 IF=1 再处理这个可屏蔽中断,在屏幕上打印出 Esc 所代表的字符 '\'; 因为这个缓冲区只能存15个字符,我按下20次 Esc 最后也只能输出15个 '\' ; 第二个运行截图是我执行程序后按下 'abc\\\abc';
改进:
pushf   
           
pushf  
pop bx               
and bh , 11111100b   
push bx
popf      
call dword ptr ds:[0]      

pushf
call dword ptr ds:[0]

执行中断要先执行:
取中断类型码 n ;   
标志寄存器入栈;pushf
IF=0,TF=0;
CS、IP 入栈;
在我们自己写的int9中断例程中,已经进入到中断例程,说明前4步已经做完了,所以就不必在设置IF和TF了,还要有一个 pushf 是因为程序主中我们通过call命令又执行了一次原本的9号中断例程,例程的最后都要执行iret,pushf和call中iret对应
iret==>pop IP、pop CS、popf
我们自己定义的中断例程最后的iret才是恢复到进入我们写的中断例程前cpu相关状态


cli
mov word ptr es:[9*4] , offset int9
mov es:[9*4+2] , cs   
sti
防止在修改9号中断的入口地址的时候产生键盘中断




CPU 对外设输入的通常处理方法:
外设的输入送入端口。
向 CPU 发出外中断(可屏蔽中断)信息。
CPU检测到可屏蔽中断信息,如果 IF=1,CPU 在执行完当前指令后响应中断,执行相应的中断例程。
可在中断例程中实现对外设输入的处理。
端口和中断机制,是 CPU 进行 I/O 的基础。



posted on 2017-11-30 19:48  正在加载……  阅读(455)  评论(0编辑  收藏  举报