深入理解MS - DOS中断处理与硬件控制
1. 中断返回与控制
在MS - DOS编程中,当一个中断处理完成后,CPU会执行IRET(中断返回)指令。该指令会从栈中弹出IP、CS和标志寄存器的值,从而使控制返回到中断发生时正在执行的程序。
1.1 中断控制指令
CPU有一个名为中断标志(IF)的标志位,它控制着CPU对外部(硬件)中断的响应方式。
- 若中断标志被设置(IF = 1),则表示允许中断;
- 若标志被清除(IF = 0),则表示禁止中断。
1.1.1 STI指令
STI指令用于启用外部中断。通常情况下,中断标志是启用的。例如,当系统响应键盘输入时,会暂停当前正在执行的程序,调用INT 9将按键存储在缓冲区,然后返回当前程序。若中断标志未启用,系统定时器将无法正确计算时间和日期,输入的按键也会丢失。
1.1.2 CLI指令
CLI指令用于禁用外部中断。该指令应谨慎使用,仅在执行不能被中断的关键操作时使用。例如,在更改SS和SP值的过程中,如果代码被中断,可能会导致SS寄存器指向新的栈段,而栈指针尚未更新。为确保安全,可使用以下代码:
cli ; 禁用中断
mov ax,mystack ; 重置SS
mov ss,ax
mov sp,100h ; 重置SP
sti ; 重新启用中断
中断不应被禁用超过几毫秒,否则可能会丢失按键并减慢系统定时器。当CPU响应中断处理程序时,其他中断会立即被禁用,而MS - DOS和BIOS中断服务例程在开始执行时会重新启用中断。
1.2 编写自定义中断处理程序
中断向量表存在的原因在于,IBM - PC的设计者希望能够在不更换ROM芯片的情况下对BIOS例程进行修改和更正。通过中断向量表,可以将表中的地址替换为指向RAM中程序的地址。
中断向量表中的每个地址都指向一个称为中断处理程序或中断服务例程(ISR)的程序。应用程序可以用一个新的地址替换表中的地址,指向新的中断处理程序。例如,可以编写一个自定义的键盘中断处理程序。
1.2.1 INT 21h功能25h和35h
- 功能35h(获取中断向量) :返回中断向量的段 - 偏移地址。调用该功能时,需将所需的中断号放在AL中,MS - DOS会在ES:BX中返回32位向量。以下代码用于检索INT 9向量:
.data
int9Save LABEL WORD
DWORD ? ; 在此存储旧的INT 9地址
.code
mov
ah,35h ; 获取中断向量
mov
al,9 ; 针对INT 9
int
21h ; 调用MS - DOS
mov
int9Save,BX ; 存储偏移量
mov
int9Save+2,ES ; 存储段地址
- 功能25h(设置中断向量) :允许用新的处理程序替换现有的中断处理程序。调用时,需将中断号放在AL中,将自己的中断处理程序的段 - 偏移地址放在DS:DX中。例如:
mov
ax,SEG kybd_rtn
; 键盘处理程序
mov
ds,ax
; 段地址
mov
dx,OFFSET kybd_rtn
; 偏移地址
mov
ah,25h
; 设置中断向量
mov
al,9h
; 针对INT 9h
int
21h
.
.
kybd_rtn PROC ; (新的INT 9中断处理程序从此处开始)
1.3 Ctrl - Break处理程序示例
当用户在MS - DOS程序等待输入时按下Ctrl - Break,控制权会传递给默认的INT 23h中断处理程序。默认的Ctrl - Break处理程序会终止当前正在运行的程序,这可能会使当前程序处于不稳定状态。不过,可以将自己的代码替换到INT 23h处理程序中,防止程序停止。以下是一个简单的Ctrl - Break处理程序的安装示例:
TITLE Control - Break Handler (Ctrlbrk.asm)
; 此程序安装自己的Ctrl - Break处理程序
; 防止用户使用Ctrl - Break(或Ctrl - C)
; 来停止程序。程序输入并回显按键,直到按下Esc键。
INCLUDE Irvine16.inc
.data
breakMsg BYTE "BREAK",0
msg
BYTE "Ctrl - Break demonstration."
BYTE 0dh,0ah
BYTE "This program disables Ctrl - Break (Ctrl - C). Press any"
BYTE 0dh,0ah
BYTE "keys to continue, or press ESC to end the program."
BYTE 0dh,0ah,0
.code
main PROC
mov
ax,@data
mov
ds,ax
mov
dx,OFFSET msg
; 显示问候消息
call
Writestring
install_handler:
push
ds
; 保存DS
mov
ax,@code
; 将DS初始化为代码段
mov
ds,ax
mov
ah,25h
; 设置中断向量
mov
al,23h
; 针对中断23h
mov
dx,OFFSET break_handler
int
21h
pop
ds
; 恢复DS
L1:
mov
ah,1
; 等待按键,回显它
int
21h
cmp
al,1Bh
; 是否按下Esc键?
jnz
L1
; 否:继续
exit
main ENDP
; 以下过程在按下Ctrl - Break时执行。必须保留所有寄存器。
break_handler PROC
push
ax
push
dx
mov
dx,OFFSET breakMsg
call
WriteString
pop
dx
pop
ax
iret
break_handler ENDP
END main
主程序初始化INT 23h的中断向量,INT 21h功能25h所需的输入参数如下:
| 参数 | 说明 |
| ---- | ---- |
| AH = 25h | 设置中断向量功能号 |
| AL = 23h | 要处理的中断向量 |
| DS:DX = 新Ctrl - Break处理程序的段/偏移地址 | 新处理程序的地址 |
程序的主循环会不断输入并回显按键,直到按下Esc键。当按下Ctrl - Break时,break_handler程序会显示一条消息,然后立即返回调用程序。当break_handler结束时执行IRET,控制权会返回主程序,之前正在进行的MS - DOS功能会重新启动。在中断处理程序中,必须保留所有寄存器。MS - DOS会在程序结束时自动恢复INT 23h向量,原始向量存储在程序段前缀的偏移000Eh处。在某些系统中,可能需要按下Ctrl - C而不是Ctrl - Break来激活Ctrl - Break处理程序消息。
1.4 终止并驻留程序(TSR)
终止并驻留程序(TSR)会安装在内存中,直到被特殊的移除实用软件移除或计算机重新启动。TSR在被某些事件(如按键)激活之前处于休眠状态。
在TSR的早期,当两个或多个程序替换相同的中断向量时,会出现兼容性问题。后来,为了解决这个问题,TSR作者会保存要替换的中断的现有向量,并在自己的程序处理完中断后将控制权传递给原始的中断处理程序。但这意味着最后安装的TSR在处理中断时会自动具有最高优先级,因此用户有时需要小心地按特定顺序加载TSR程序。当MS - DOS应用程序广泛使用时,存在商业编程工具来管理多个内存驻留程序。
1.4.1 键盘示例
假设编写一个中断服务例程,用于检查键盘输入的每个字符,并将其存储在位置10B2:0020。为安装该ISR,需从中断向量表中获取当前的INT 9向量,保存它,并将表项替换为ISR的地址。
当按下键盘按键时,键盘控制器会将一个字节传输到计算机的键盘端口,并触发硬件中断。8259 PIC将中断号传递给CPU,CPU会跳转到中断向量表中的INT 9地址,即我们的ISR地址。我们的程序有机会检查键盘字节,当键盘处理程序退出时,会跳转到原始的BIOS键盘处理程序。
以下是这个过程的流程图:
graph TD;
A[按键按下] --> B[键盘控制器传输字节到端口];
B --> C[触发硬件中断];
C --> D[8259 PIC传递中断号给CPU];
D --> E[CPU跳转到INT 9地址(ISR)];
E --> F[ISR检查键盘字节];
F --> G[ISR退出,跳转到原始BIOS键盘处理程序];
G --> H[BIOS INT 9h例程执行];
H --> I[IRET指令返回控制权];
1.5 应用:No_Reset程序
No_Reset程序是一种简单的内存驻留程序,它可以防止系统通过Ctrl - Alt - Delete键重启。安装该程序后,系统只能通过按下特殊的组合键Ctrl - Alt - RightShift - Del来重启。该程序仅在以MS - DOS模式启动计算机时有效,因为所有最新版本的Microsoft Windows都防止TSR程序拦截键盘按键。
1.5.1 MS - DOS键盘状态字节
程序需要检查MS - DOS键盘状态字节,该字节存储在RAM的0040:0017h位置,用于判断Ctrl、Alt、Del和RightShift键是否被按下。另一个位于0040:0018的键盘状态字节重复了前面的标志,只是位3表示Ctrl - NumLock当前是否激活。
1.5.2 安装程序
内存驻留代码必须先安装在内存中才能正常工作。从那时起,所有键盘输入都会通过该程序进行过滤。如果程序有任何错误,键盘可能会锁定,需要冷启动计算机。键盘中断处理程序特别难以调试,专业人员通常会使用硬件辅助调试器。
以下是No_Reset程序的代码:
TITLE Reset - Disabling program (No_Reset.asm)
; 此程序通过拦截INT 9键盘硬件中断,禁用通常的DOS重置命令(Ctrl - Alt - Del)。
; 它检查MS - DOS键盘标志中的Shift状态位,将任何Ctrl - Alt - Del更改为Alt - Del。
; 计算机只能通过输入Ctrl + Alt + Right shift + Del来重启。
; 汇编、链接,并通过在Microsoft LINK命令行中包含/T命令将其转换为COM程序。
; 在运行此程序之前,以纯MS - DOS模式启动。
.model tiny
.386
.code
rt_shift EQU 01h
; 右Shift键:位0
ctrl_key EQU 04h
; CTRL键:位2
alt_key EQU 08h
; ALT键:位3
del_key EQU 53h
; DEL键的扫描码
kybd_port EQU 60h
; 键盘输入端口
ORG 100h
; 这是一个COM程序
start:
jmp setup
; 跳转到TSR安装
; 内存驻留代码从此处开始
int9_handler PROC FAR
sti
; 启用硬件中断
pushf
; 保存寄存器和标志
push
es
push
ax
push
di
; 将ES:DI指向DOS键盘标志字节:
L1:
mov
ax,40h
; DOS数据段位于40h
mov
es,ax
mov
di,17h
; 键盘标志的位置
mov
ah,es:[di]
; 将键盘标志复制到AH
; 测试CTRL和ALT键:
L2:
test
ah,ctrl_key
; CTRL键是否被按下?
jz
L5
; 否:退出
test
ah,alt_key
; ALT键是否被按下?
jz
L5
; 否:退出
; 测试DEL和右Shift键:
L3:
in
al,kybd_port
; 读取键盘端口
cmp
al,del_key
; 是否按下DEL键?
jne
L5
; 否:退出
test
ah,rt_shift
; 右Shift键是否被按下?
jnz
L5
; 是:允许系统重置
L4:
and
ah,NOT ctrl_key
; 否:关闭CTRL位
mov
es:[di],ah
; 存储键盘标志
L5:
pop
di
; 恢复寄存器和标志
pop
ax
pop
es
popf
jmp
cs:[old_interrupt9]
; 跳转到INT 9例程
old_interrupt9 DWORD ?
int9_handler ENDP
end_ISR label BYTE
; --------------- (TSR程序结束) ------------------
; 保存原始INT 9向量的副本,并将我们程序的地址设置为新向量。
; 终止此程序并将int9_handler过程留在内存中。
setup:
mov
ax,3509h
; 获取INT 9向量
int
21h
mov
word ptr old_interrupt9,bx
; 保存INT 9向量
mov
word ptr old_interrupt9+2,es
mov
ax,2509h
; 设置INT 9向量
mov
dx,offset int9_handler
int
21h
mov
ax,3100h
; 终止并驻留
mov
dx,OFFSET end_ISR
; 指向驻留代码的末尾
shr
dx,4
; 除以16
inc
dx
; 向上舍入到下一个段落
int
21h
; 执行MS - DOS功能
END start
程序在setup标签处调用INT 21h功能35h获取当前的INT 9h向量,并将其存储在old_interrupt9中,以便能够将控制权传递给现有的键盘处理程序。然后使用INT 21h功能25h将中断向量9h设置为程序驻留部分的地址。最后,调用INT 21h功能31h退出到MS - DOS,将驻留程序留在内存中。
内存驻留的中断处理程序从int9_handler标签开始,每次按下键盘按键时都会执行。该程序会检查键盘标志字节,判断是否按下了Ctrl - Alt - Del组合键。如果按下了该组合键,但没有按下右Shift键,则会清除键盘标志字节中的Ctrl键位,从而禁用用户重启计算机的尝试。最后,程序会跳转到现有的BIOS INT 9h例程,以确保所有正常的按键都能被处理。
1.6 中断处理部分总结
在中断处理的学习中,我们了解了多个重要的方面,以下是对这些内容的总结:
| 内容 | 说明 |
| ---- | ---- |
| 中断返回 | 通过IRET指令从栈中弹出IP、CS和标志寄存器的值,使控制返回到中断发生时正在执行的程序 |
| 中断控制指令 | STI启用外部中断,CLI禁用外部中断,中断禁用时间不宜过长 |
| 自定义中断处理程序 | 利用INT 21h的25h和35h功能获取和设置中断向量,可编写自定义中断处理程序 |
| Ctrl - Break处理程序 | 可替换默认的INT 23h处理程序,防止程序因Ctrl - Break被异常终止 |
| 终止并驻留程序(TSR) | 安装在内存中,等待事件激活,早期存在兼容性问题,后期有改进方法 |
| No_Reset程序 | 防止系统通过Ctrl - Alt - Del重启,需检查键盘状态字节并进行相应处理 |
同时,我们还可以通过以下流程图来概括中断处理的整体流程:
graph LR;
A[程序正常执行] --> B{是否发生中断};
B -- 是 --> C[保存现场(寄存器、标志等)];
C --> D[跳转到中断处理程序];
D --> E{中断类型};
E -- 自定义中断 --> F[执行自定义中断处理逻辑];
E -- 系统默认中断 --> G[执行默认中断处理逻辑];
F --> H[恢复现场];
G --> H;
H --> I[IRET返回原程序];
B -- 否 --> A;
1.7 中断处理部分的常见问题及解答
以下是关于中断处理部分的一些常见问题及解答:
1.
什么默认行动是由关键错误处理程序执行的?
文中未提及关键错误处理程序的默认行动相关内容。
2.
中断向量表的每个条目包含什么?
中断向量表中的每个地址都指向一个称为中断处理程序或中断服务例程(ISR)的程序。
3.
INT 10h的中断向量存储在哪个地址?
文中未提及INT 10h中断向量的具体存储地址。
4.
哪个控制器芯片生成硬件中断?
8259 PIC生成硬件中断。
5.
哪个指令禁用硬件中断?
CLI指令禁用硬件中断。
6.
哪个指令启用硬件中断?
STI指令启用硬件中断。
7.
哪个IRQ级别具有最高优先级,0还是15?
文中未提及IRQ级别优先级的相关内容。
8.
基于对IRQ级别的了解,如果一个程序正在创建磁盘文件,按下键盘上的键,你认为键什么时候会被放入键盘缓冲区——在文件创建之前还是之后?
文中未提及基于IRQ级别判断按键放入键盘缓冲区时间的相关内容。
9.
当在键盘上按下一个键时,执行哪个硬件中断?
当按下键盘按键时,执行INT 9硬件中断。
10.
当中断处理程序完成时,CPU如何恢复到中断触发前的执行位置?
通过执行IRET指令,从栈中弹出IP、CS和标志寄存器的值,使控制返回到中断发生时正在执行的程序。
11.
哪些MS - DOS函数获取和设置中断向量?
INT 21h的35h功能用于获取中断向量,25h功能用于设置中断向量。
12.
解释中断处理程序和内存驻留程序之间的区别。
中断处理程序是响应特定中断而执行的程序,它可以是临时的,在处理完中断后就结束;而内存驻留程序(如TSR)安装在内存中,在被特定事件激活之前处于休眠状态,会一直驻留在内存中。
13.
描述一个TSR程序。
TSR程序安装在内存中,直到被特殊的移除实用软件移除或计算机重新启动。它在被某些事件(如按键)激活之前处于休眠状态。早期存在兼容性问题,后期通过保存现有向量和传递控制权给原始处理程序来改进。
14.
如何从内存中移除一个TSR程序?
可以使用特殊的移除实用软件移除TSR程序,或者重新启动计算机。
15.
如果一个内存驻留程序替换了一个中断向量,它如何仍然利用该中断现有处理程序中的一些功能?
内存驻留程序可以保存要替换的中断的现有向量,并在自己的程序处理完中断后将控制权传递给原始的中断处理程序。
16.
哪个MS - DOS函数终止一个程序并将程序的一部分留在内存中?
INT 21h的31h功能用于终止程序并将部分程序留在内存中。
17.
在No_reset程序中,什么键组合会实际重启计算机?
在No_reset程序中,Ctrl - Alt - RightShift - Del组合键会实际重启计算机。
2. 硬件控制使用I/O端口
2.1 x86系统的硬件输入 - 输出类型
x86系统提供了两种类型的硬件输入 - 输出方式:
-
内存映射I/O
:当使用内存映射I/O时,程序可以将数据写入特定的内存地址,数据会被传输到输出设备。同样,也可以通过从预定义的内存地址复制数据来从输入设备读取数据。例如,文本视频显示就是一个内存映射设备,当在视频段中放置字符时,它们会立即显示在显示器上。
-
端口基I/O
:端口基I/O需要使用IN和OUT指令来读写数据到特定编号的位置,这些位置称为端口。端口是CPU与其他设备(如键盘、扬声器、调制解调器和声卡)之间的连接或通道。
2.2 端口基I/O的操作
端口基I/O的核心是使用IN和OUT指令,以下是对这两个指令的详细说明:
| 指令 | 功能 | 示例 |
| ---- | ---- | ---- |
| IN | 从端口读取数据到寄存器 |
in al, kybd_port
(从键盘端口读取数据到AL寄存器) |
| OUT | 将寄存器中的数据写入端口 | 文中未给出具体示例,但格式一般为
out 端口号, 寄存器
|
在前面提到的No_Reset程序中,就使用了IN指令来读取键盘端口的数据:
L3:
in al,kybd_port ; 读取键盘端口
cmp al,del_key ; 是否按下DEL键?
jne L5 ; 否:退出
2.3 硬件控制使用I/O端口的总结
通过使用I/O端口进行硬件控制,我们可以实现对各种外部设备的交互。内存映射I/O和端口基I/O各有特点,内存映射I/O适用于像视频显示这种可以直接通过内存地址操作的设备,而端口基I/O则更适合于需要通过特定端口进行数据传输的设备,如键盘等。在实际编程中,根据不同的设备和需求选择合适的硬件输入 - 输出方式非常重要。
综上所述,在MS - DOS环境下,中断处理和硬件控制使用I/O端口是非常重要的技术。中断处理可以让程序对各种事件做出响应,而硬件控制使用I/O端口则可以实现与外部设备的交互。通过深入学习和实践这些技术,我们可以编写出功能更强大、更稳定的程序。
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



