【汇编语言】外中断(二)—— 键盘的奥秘:编写自己的 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主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

相关推荐
逸狼4 分钟前
【JavaEE进阶】Spring DI
java·开发语言
生产队队长21 分钟前
ThinkPHP:配置Redis并使用
android·数据库·redis
踏雪羽翼24 分钟前
android 差值器的使用
android
技术小齐25 分钟前
网络运维学习笔记 022 HCIA-Datacom新增知识点03园区网典型组网架构及案例实战
运维·网络·学习
西猫雷婶32 分钟前
python学智能算法(三)|模拟退火算法:深层分析
算法·机器学习·模拟退火算法
my_styles33 分钟前
2025-alibaba-Sentinel组件
java·开发语言·sentinel
致奋斗的我们34 分钟前
HAProxy介绍与编译安装
linux·汇编·数据库·mysql·青少年编程·haproxy·openeurler
禁默1 小时前
C++之旅-C++11的深度剖析(1)
开发语言·c++
程序员黄同学1 小时前
请谈谈 Vue 中的 key 属性的重要性,如何确保列表项的唯一标识?
前端·javascript·vue.js
黄卷青灯771 小时前
抓包工具 wireshark
网络·测试工具·wireshark