【汇编语言】外中断(二)—— 键盘的奥秘:编写自己的 int 9 中断例程

文章目录

  • 前言
  • [1. 前提说明](#1. 前提说明)
  • [2. 实操巩固 ------ 编程要求](#2. 实操巩固 —— 编程要求)
  • [3. 实操巩固 ------ 分析与解决问题](#3. 实操巩固 —— 分析与解决问题)
    • [3.1 如何依次显示字母字符?](#3.1 如何依次显示字母字符?)
    • [3.2 实现按Esc建改变字符颜色](#3.2 实现按Esc建改变字符颜色)
      • [3.2.1 int 9中断例程应该具备的功能](#3.2.1 int 9中断例程应该具备的功能)
      • [3.2.2 (1)从端口 60h 读出键盘的输入](#3.2.2 (1)从端口 60h 读出键盘的输入)
      • [3.2.3 (2)调用 BIOS 的int9 中断例程](#3.2.3 (2)调用 BIOS 的int9 中断例程)
        • [3.2.3.1 保存原来的int 9中断例程](#3.2.3.1 保存原来的int 9中断例程)
        • [3.2.3.2 调用原来的int 9中断例程](#3.2.3.2 调用原来的int 9中断例程)
      • [3.2.4 (3)如果是Esc键的扫描码,改变显示的颜色后返回](#3.2.4 (3)如果是Esc键的扫描码,改变显示的颜色后返回)
    • [3.3 得到完整的程序](#3.3 得到完整的程序)
  • 结语

前言

📌

汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。

本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。

1. 前提说明

从之前学习的内容中,可以看出键盘输入的处理过程:

① 键盘产生扫描码;

② 扫描码送入60h端口;

③ 引发9号中断;

④ CPU执行int9中断例程处理键盘输入。

上面的过程中,第1、2、3步都是由硬件系统完成的。我们能够改变的只有int9中断处理程序。我们可以重新编写int9中断例程,按照自己的意图来处理键盘的输入。

但是,在课程中,我们不准备完整地编写一个键盘中断的处理程序,因为要涉及一些硬件细节,而这些内容脱离了我们的内容主线。

但是,我们却还要编写新的键盘中断处理程序,来进行一些特殊的工作,那么这些硬件细节如何处理呢?

这点比较简单,因为BIOS提供的int 9中断例程已经对这些硬件细节进行了处理。我们只要在自己编写的中断例程中调用 BIOS的int 9中断例程就可以了。

2. 实操巩固 ------ 编程要求

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

3. 实操巩固 ------ 分析与解决问题

3.1 如何依次显示字母字符?

我们先来看一下如何依次显示"a"~"z"。

assembly 复制代码
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位的循环次数。如下:

assembly 复制代码
	mov dx,10h
	mov ax,0
s: 	sub ax,1
	sbb dx,0
	cmp ax,0
	jne s
	cmp dx,0
	jne s

上面的程序,循环100000h次(两层循环,内层循环10000h次,外层循环10h次)。

我们可以将循环延时的程序段写为一个子程序。

现在,我们的程序如下:

assembly 复制代码
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 es,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	;循环10000000h次,读者可以根据自己机器的速度调整循环次数
	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

code ends

end start 

3.2 实现按Esc建改变字符颜色

显示"a"~"z",并可以让人看清,这个任务已经实现。

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

3.2.1 int 9中断例程应该具备的功能

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

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

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

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

接下来,我们对这些功能的实现一一进行分析!

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

assembly 复制代码
int al,60h

3.2.3 (2)调用 BIOS 的int9 中断例程

3.2.3.1 保存原来的int 9中断例程

有一点要注意的是,我们写的中断处理程序要成为新的int 9中断例程,主程序 必须要将中断向量表中的int 9中断例程的入口地址改为我们写的中断处理程序的入口地址。

则在新的中断处理程序中调用原来的int 9中断例程时,中断向量表中的int 9中断例程的入口地址却不是原来的int 9中断例程的地址。所以不能使用int指令直接调用。

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

对于我们现在的问题,假设将原来int9中断例程的偏移地址和段地址保存在 ds:[0]和ds:[2]单元中。那么我们在需要调用原来的 int 9中断例程时候,就可以在 ds:[0]、ds:[2]单元中找到它的入口地址。

3.2.3.2 调用原来的int 9中断例程

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

当然不能使用指令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)。

可以注意到第(3)、(4)步和call dword ptr ds:[0]的功能一样。

call dword ptr ds:[0] 的功能也是:

  • (1)CS 、IP 入栈;

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

所以经过我们总结后,int 过程的模拟最终变为:

  • (1)标志寄存器入栈;

  • (2)IF=0,TF=0;

  • (3)call dword ptr ds:[0]

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

对于(2),我们又得动点歪脑筋,没办法,资源条件极其卑劣的8086 要么使人放弃,要么逼出天才😅😅😅,我们可用以下程序实现:

assembly 复制代码
pushf
pop ax
and ah,11111100b ; IF和OF为标志寄存器的第9位和第8位
push ax
popf

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

assembly 复制代码
pushf                     	;标志寄存器入栈

pushf
pop ax
and ah,11111100b  			;IF和OF为标志寄存器的第9位和第8位
push ax
popf                      	;IF=0、TF=0

call dword ptr ds:[0]

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

如何改变显示的颜色?

显示的位置是屏幕的中间,即第12行40列,显存中的偏移地址为:160*12+40* 2。所以字符的ASCII码要送入段地址:b800h,偏移地址:160*12+40*2处。

而段地址:b800h,偏移地址:160*12+40*2+1 处是字符的属性,我们只要改变此处的数据就可以改变在段地址:b800h,偏移地址:160*12+40*2 处显示的字符的颜色了。

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

否则程序返回后,别的程序将无法使用键盘。

3.3 得到完整的程序

经过分析,整理得到完整的程序代码:

assembly 复制代码
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,2000h
	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

注意,本章中所有关于键盘的程序,因要直接访问真实的硬件,则必须在DOS实模式下运行。

在Windows 2000 的DOS 方式下运行,会出现一些和硬件工作原理不符合的现象。

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

相关推荐
沐知全栈开发18 分钟前
HTML DOM 访问
开发语言
脑袋大大的1 小时前
JavaScript 性能优化实战:减少 DOM 操作引发的重排与重绘
开发语言·javascript·性能优化
一只栖枝2 小时前
网络安全 vs 信息安全的本质解析:数据盾牌与网络防线的辩证关系关系
网络·网络安全·信息安全·it·信息安全认证
二进制person2 小时前
Java SE--方法的使用
java·开发语言·算法
CertiK3 小时前
CertiK《Hack3d:2025年第二季度及上半年Web3.0安全报告》(附报告全文链接)
网络
速易达网络3 小时前
RuoYi、Vue CLI 和 uni-app 结合构建跨端全家桶方案
javascript·vue.js·低代码
OneQ6663 小时前
C++讲解---创建日期类
开发语言·c++·算法
耶啵奶膘3 小时前
uniapp+firstUI——上传视频组件fui-upload-video
前端·javascript·uni-app
JoJo_Way3 小时前
LeetCode三数之和-js题解
javascript·算法·leetcode
码农不惑3 小时前
2025.06.27-14.44 C语言开发:Onvif(二)
c语言·开发语言