第8章 计算机中断机制与控制接口设计
中断机制是现代计算机系统中极其重要的一个功能,它允许CPU暂停当前执行的程序,转而处理更紧急的任务,处理完成后再返回原来的程序继续执行。本章将深入探讨计算机中断系统的原理、实现方式以及应用案例。
8.1 x86架构的中断体系结构
8.1.1 x86处理器中断分类体系
在x86架构的处理器中,中断可以分为三大类:
硬件中断:由外部设备如键盘、鼠标、硬盘等通过特定电路发出的中断请求信号。软件中断:由程序执行INT指令主动触发的中断。异常:CPU执行指令时遇到异常情况触发的中断,如除零错误、页错误等。
从另一个角度,中断也可以分为:
可屏蔽中断(Maskable Interrupt):可以通过设置中断标志位来禁止的中断。非可屏蔽中断(Non-Maskable Interrupt):不能被禁止的中断,通常用于处理严重错误。
下面是一个简单的示例,展示如何在C语言中使用内联汇编触发软件中断:
c
#include <stdio.h>
int main() {
printf("准备触发软件中断INT 0x80...
");
// Linux系统调用中断
__asm__ __volatile__(
"movl $1, %%eax
" // 系统调用号1表示exit
"movl $42, %%ebx
" // 退出码为42
"int $0x80
" // 触发中断
::: "eax", "ebx" // 告诉编译器这些寄存器被修改
);
// 这行代码永远不会执行,因为上面的中断会导致程序退出
printf("这条消息不会显示
");
return 0;
}
8.1.2 x86处理器中断响应机制
当中断发生时,处理器会执行以下步骤:
完成当前指令:处理器会先完成当前正在执行的指令。保存现场:自动将当前的程序计数器(EIP)、代码段寄存器(CS)和标志寄存器(EFLAGS)压入栈中。查找中断处理程序:从中断描述符表(IDT)中找到对应中断号的处理程序地址。执行中断处理程序:跳转到中断处理程序执行。恢复现场并返回:通过IRET指令恢复之前保存的状态,返回被中断的程序。
下面的汇编代码展示了一个简单的中断处理程序框架:
asm
; 中断处理程序示例
interrupt_handler:
; 保存更多寄存器(如有必要)
push eax
push ebx
push ecx
push edx
; 处理中断的代码
; ...
; 发送EOI(End Of Interrupt)信号到中断控制器(如果是硬件中断)
mov al, 0x20
out 0x20, al
; 恢复寄存器
pop edx
pop ecx
pop ebx
pop eax
; 返回到被中断的程序
iret
8.1.3 x86架构的中断向量表
中断向量表是处理器用来查找中断处理程序地址的数据结构。在实模式下,它位于内存的前1KB区域,包含256个4字节的中断向量,每个向量包含CS:IP值指向对应的处理程序。
在保护模式下,使用中断描述符表(IDT)替代中断向量表,每个描述符8字节,描述中断门、陷阱门或任务门。
下面是使用C语言设置IDT的示例:
c
#include <stdio.h>
#include <stdint.h>
// 中断门描述符结构
typedef struct {
uint16_t offset_low; // 处理函数偏移地址低16位
uint16_t selector; // 代码段选择子
uint8_t reserved; // 保留位
uint8_t type_attr; // 类型和属性
uint16_t offset_high; // 处理函数偏移地址高16位
} __attribute__((packed)) IDTEntry;
// IDTR寄存器结构
typedef struct {
uint16_t limit; // IDT大小减1
uint32_t base; // IDT基地址
} __attribute__((packed)) IDTR;
// 定义IDT全局变量
IDTEntry idt[256];
IDTR idtr;
// 中断处理函数(外部定义)
extern void keyboard_handler(void);
// 设置中断描述符
void set_idt_entry(int vector, uint32_t handler, uint16_t selector, uint8_t flags) {
idt[vector].offset_low = handler & 0xFFFF;
idt[vector].selector = selector;
idt[vector].reserved = 0;
idt[vector].type_attr = flags;
idt[vector].offset_high = (handler >> 16) & 0xFFFF;
}
// 初始化IDT
void init_idt(void) {
// 设置IDTR
idtr.limit = sizeof(IDTEntry) * 256 - 1;
idtr.base = (uint32_t)&idt;
// 设置键盘中断处理程序(IRQ1对应INT 0x21)
set_idt_entry(0x21, (uint32_t)keyboard_handler, 0x08, 0x8E);
// 加载IDTR
__asm__ volatile ("lidt %0" : : "m"(idtr));
printf("IDT初始化完成
");
}
int main() {
init_idt();
// 其他代码...
return 0;
}
8.2 处理器内部中断处理机制
处理器内部集成了一系列中断处理机制,使得在发生中断时能够迅速响应并正确处理。这些机制包括中断优先级、中断嵌套和中断屏蔽等功能。
内部中断处理流程
当处理器接收到中断请求时,会执行以下内部步骤:
中断识别:处理器识别中断源和中断类型。中断优先级判断:如果同时有多个中断请求,处理器会根据优先级决定先处理哪个。现场保护:自动保存关键寄存器内容。中断处理程序执行:跳转到相应的中断处理程序。现场恢复:从栈中恢复之前保存的寄存器内容。返回被中断程序:继续执行被中断的程序。
下面是一个简单的C语言函数,模拟处理器内部中断处理机制:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 处理器状态结构
typedef struct {
uint32_t eax, ebx, ecx, edx; // 通用寄存器
uint32_t esi, edi, ebp, esp; // 指针寄存器
uint32_t eip; // 指令指针
uint32_t eflags; // 标志寄存器
uint16_t cs, ds, es, fs, gs, ss; // 段寄存器
bool interrupt_enabled; // 中断启用标志
} ProcessorState;
// 中断处理函数类型
typedef void (*InterruptHandler)(void);
// 模拟中断描述符表
InterruptHandler interrupt_table[256] = {NULL};
// 模拟处理器状态
ProcessorState cpu_state;
// 保存处理器状态
void save_processor_state(ProcessorState* state) {
printf("保存处理器状态...
");
// 实际实现中,这些值会从实际CPU寄存器中读取
state->eip = 0x1000;
state->cs = 0x08;
state->eflags = 0x202; // IF=1, 表示中断启用
}
// 恢复处理器状态
void restore_processor_state(ProcessorState* state) {
printf("恢复处理器状态...
");
printf("EIP=0x%08X, CS=0x%04X, EFLAGS=0x%08X
",
state->eip, state->cs, state->eflags);
}
// 注册中断处理程序
void register_interrupt_handler(uint8_t vector, InterruptHandler handler) {
if (handler != NULL) {
interrupt_table[vector] = handler;
printf("已注册中断处理程序 %d (0x%02X)
", vector, vector);
}
}
// 模拟中断处理程序
void timer_interrupt_handler(void) {
printf("执行时钟中断处理程序...
");
}
void keyboard_interrupt_handler(void) {
printf("执行键盘中断处理程序...
");
}
// 模拟处理器处理中断的过程
void handle_interrupt(uint8_t vector) {
printf("
开始处理中断 %d (0x%02X)...
", vector, vector);
// 1. 保存当前处理器状态
ProcessorState saved_state;
save_processor_state(&saved_state);
// 2. 禁用中断(避免中断嵌套,除非显式允许)
cpu_state.interrupt_enabled = false;
// 3. 查找并执行中断处理程序
if (interrupt_table[vector] != NULL) {
printf("找到中断处理程序,开始执行...
");
interrupt_table[vector]();
} else {
printf("错误: 未找到中断 %d (0x%02X) 的处理程序!
", vector, vector);
}
// 4. 重新启用中断
cpu_state.interrupt_enabled = true;
// 5. 恢复处理器状态
restore_processor_state(&saved_state);
printf("中断处理完成
");
}
int main() {
// 初始化CPU状态
cpu_state.interrupt_enabled = true;
// 注册中断处理程序
register_interrupt_handler(0x20, timer_interrupt_handler);
register_interrupt_handler(0x21, keyboard_interrupt_handler);
printf("模拟处理器接收到时钟中断请求...
");
handle_interrupt(0x20);
printf("
模拟处理器接收到键盘中断请求...
");
handle_interrupt(0x21);
printf("
模拟处理器接收到未注册的中断请求...
");
handle_interrupt(0x30);
return 0;
}
8.3 8259A可编程中断控制器
8259A是一种经典的可编程中断控制器(PIC),虽然在现代计算机中已经被APIC(高级可编程中断控制器)所取代,但其工作原理依然是理解中断系统的基础。
8.3.1 8259A的内部结构和引脚功能
8259A是一个40针的芯片,包含以下主要引脚:
IR0-IR7:8个中断请求输入引脚INT:中断输出引脚,连接到CPU的INTR引脚INTA:中断确认输入引脚,接收CPU的确认信号D0-D7:双向数据总线,用于与CPU通信A0:地址输入线,选择8259A的命令寄存器或数据寄存器CS:片选信号RD/WR:读/写控制信号CAS0-CAS2:级联信号,用于多片8259A级联时使用SP/EN:级联模式下配置主/从身份
8259A的内部结构主要包括:
中断请求寄存器(IRR):记录所有接收到的中断请求中断服务寄存器(ISR):记录当前正在服务的中断中断屏蔽寄存器(IMR):控制哪些中断请求可以被屏蔽优先级解析器:决定多个中断请求的服务顺序控制逻辑:处理与CPU和外部设备的通信
8.3.2 8259A的中断处理过程
8259A中断处理的基本流程如下:
外部设备触发中断:设备通过IR0-IR7引脚向8259A发送中断请求。8259A接收并处理请求:
检查相应中断是否被屏蔽(IMR)设置IRR对应位通过优先级解析器确定最高优先级的中断通过INT引脚向CPU发送中断请求
CPU响应中断:
完成当前指令通过INTA信号确认中断
8259A发送中断向量:
接收INTA后,8259A将对应中断向量号发送给CPU设置ISR对应位,清除IRR对应位
CPU执行中断处理程序:
根据中断向量,执行相应的中断服务程序
中断服务完成:
服务程序执行EOI命令8259A清除ISR对应位
下面是一个模拟8259A操作的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 8259A寄存器
typedef struct {
uint8_t irr; // 中断请求寄存器
uint8_t isr; // 中断服务寄存器
uint8_t imr; // 中断屏蔽寄存器
uint8_t icw1; // 初始化命令字1
uint8_t icw2; // 初始化命令字2
uint8_t icw3; // 初始化命令字3
uint8_t icw4; // 初始化命令字4
uint8_t ocw1; // 操作命令字1
uint8_t ocw2; // 操作命令字2
uint8_t ocw3; // 操作命令字3
bool initialized; // 初始化标志
} PIC8259A;
// 初始化8259A
void init_8259a(PIC8259A* pic) {
// 初始状态
pic->irr = 0;
pic->isr = 0;
pic->imr = 0xFF; // 默认屏蔽所有中断
pic->initialized = false;
printf("8259A已初始化,所有中断被屏蔽
");
}
// 写ICW1(初始化命令字1)
void write_icw1(PIC8259A* pic, uint8_t value) {
pic->icw1 = value;
pic->initialized = true;
// 解析ICW1
bool ic4_needed = (value & 0x01) != 0;
bool single_mode = (value & 0x02) != 0;
bool level_triggered = (value & 0x08) != 0;
printf("写ICW1: 0x%02X
", value);
printf(" 需要ICW4: %s
", ic4_needed ? "是" : "否");
printf(" 级联模式: %s
", single_mode ? "单片" : "多片");
printf(" 触发模式: %s
", level_triggered ? "电平触发" : "边沿触发");
}
// 写ICW2(初始化命令字2)
void write_icw2(PIC8259A* pic, uint8_t value) {
pic->icw2 = value;
// ICW2指定中断向量的高5位(低3位由中断号决定)
uint8_t base_vector = value & 0xF8;
printf("写ICW2: 0x%02X
", value);
printf(" 中断向量基址: 0x%02X
", base_vector);
}
// 写ICW3(初始化命令字3)
void write_icw3(PIC8259A* pic, uint8_t value, bool is_master) {
pic->icw3 = value;
printf("写ICW3: 0x%02X
", value);
if (is_master) {
printf(" 从片连接在: ");
for (int i = 0; i < 8; i++) {
if (value & (1 << i)) {
printf("IR%d ", i);
}
}
printf("
");
} else {
printf(" 从片ID: %d
", value & 0x07);
}
}
// 写ICW4(初始化命令字4)
void write_icw4(PIC8259A* pic, uint8_t value) {
pic->icw4 = value;
bool auto_eoi = (value & 0x02) != 0;
bool buf_mode = (value & 0x08) != 0;
bool sfnm_mode = (value & 0x10) != 0;
printf("写ICW4: 0x%02X
", value);
printf(" 处理器模式: %s
", (value & 0x01) ? "8086/88" : "MCS-80/85");
printf(" EOI模式: %s
", auto_eoi ? "自动EOI" : "普通EOI");
printf(" 缓冲区模式: %s
", buf_mode ? "使用缓冲区" : "不使用缓冲区");
printf(" 全嵌套模式: %s
", sfnm_mode ? "特殊全嵌套模式" : "普通嵌套模式");
}
// 写OCW1(操作命令字1) - 设置中断屏蔽寄存器
void write_ocw1(PIC8259A* pic, uint8_t value) {
pic->ocw1 = value;
pic->imr = value;
printf("写OCW1: 0x%02X (设置IMR)
", value);
printf(" 中断屏蔽状态: ");
for (int i = 0; i < 8; i++) {
printf("IR%d:%s ", i, (value & (1 << i)) ? "屏蔽" : "启用");
}
printf("
");
}
// 写OCW2(操作命令字2) - EOI和轮询命令
void write_ocw2(PIC8259A* pic, uint8_t value) {
pic->ocw2 = value;
uint8_t cmd = (value >> 5) & 0x07;
uint8_t level = value & 0x07;
printf("写OCW2: 0x%02X
", value);
// 解析命令类型
switch (cmd) {
case 0x00: // 轮询
printf(" 命令: 轮询
");
break;
case 0x01: // 非特定EOI
printf(" 命令: 非特定EOI
");
// 清除最高优先级的ISR位
for (int i = 0; i < 8; i++) {
if (pic->isr & (1 << i)) {
pic->isr &= ~(1 << i);
printf(" 清除ISR中IR%d的服务位
", i);
break;
}
}
break;
case 0x03: // 特定EOI
printf(" 命令: 特定EOI (IR%d)
", level);
pic->isr &= ~(1 << level);
printf(" 清除ISR中IR%d的服务位
", level);
break;
case 0x05: // 持续轮询
printf(" 命令: 持续轮询
");
break;
case 0x06: // 设置优先级
printf(" 命令: 设置优先级 (IR%d最低)
", level);
break;
default:
printf(" 未知命令
");
}
}
// 写OCW3(操作命令字3) - 特殊屏蔽和读状态
void write_ocw3(PIC8259A* pic, uint8_t value) {
pic->ocw3 = value;
bool read_irr = (value & 0x02) == 0x02;
bool read_isr = (value & 0x03) == 0x03;
bool poll_command = (value & 0x04) != 0;
bool special_mask = (value & 0x08) != 0;
printf("写OCW3: 0x%02X
", value);
if (read_irr) printf(" 设置下次读取返回IRR
");
if (read_isr) printf(" 设置下次读取返回ISR
");
if (poll_command) printf(" 执行轮询命令
");
if (special_mask) {
printf(" 特殊屏蔽模式: %s
", (value & 0x20) ? "设置" : "重置");
}
}
// 触发中断请求
void trigger_interrupt(PIC8259A* pic, uint8_t irq) {
if (irq > 7) {
printf("错误: 8259A只支持IRQ0-IRQ7
");
return;
}
// 设置IRR中的对应位
pic->irr |= (1 << irq);
printf("触发中断请求: IR%d
", irq);
printf("IRR = 0x%02X
", pic->irr);
// 检查中断是否被屏蔽
if (pic->imr & (1 << irq)) {
printf("IR%d被屏蔽,不会发送到CPU
", irq);
return;
}
// 检查是否有更高优先级的中断正在服务
bool higher_priority_active = false;
for (int i = 0; i < irq; i++) {
if (pic->isr & (1 << i)) {
higher_priority_active = true;
break;
}
}
if (higher_priority_active) {
printf("有更高优先级的中断正在服务,IR%d将被延迟
", irq);
return;
}
// 发送中断信号到CPU
printf("发送中断信号到CPU (INT引脚激活)
");
// 模拟CPU响应中断(INTA信号)
printf("接收到CPU的INTA信号
");
// 将IRR对应位清零,ISR对应位置1
pic->irr &= ~(1 << irq);
pic->isr |= (1 << irq);
// 计算中断向量号
uint8_t vector = (pic->icw2 & 0xF8) | irq;
printf("发送中断向量号: 0x%02X
", vector);
printf("IRR = 0x%02X, ISR = 0x%02X
", pic->irr, pic->isr);
}
// 打印8259A状态
void print_pic_status(PIC8259A* pic) {
printf("
8259A状态:
");
printf("初始化状态: %s
", pic->initialized ? "已初始化" : "未初始化");
printf("IRR: 0x%02X ", pic->irr);
for (int i = 7; i >= 0; i--) printf("%d", (pic->irr >> i) & 0x01);
printf("
");
printf("ISR: 0x%02X ", pic->isr);
for (int i = 7; i >= 0; i--) printf("%d", (pic->isr >> i) & 0x01);
printf("
");
printf("IMR: 0x%02X ", pic->imr);
for (int i = 7; i >= 0; i--) printf("%d", (pic->imr >> i) & 0x01);
printf("
");
}
int main() {
PIC8259A pic;
init_8259a(&pic);
printf("
1. 初始化8259A:
");
printf("-------------------
");
// 初始化顺序: ICW1 -> ICW2 -> [ICW3] -> [ICW4]
write_icw1(&pic, 0x11); // 需要ICW4, 级联模式, 边沿触发
write_icw2(&pic, 0x20); // 中断向量起始为0x20(IRQ0=0x20, IRQ7=0x27)
write_icw3(&pic, 0x04, true); // 主片, IR2连接从片
write_icw4(&pic, 0x01); // 8086模式
// 设置中断屏蔽
write_ocw1(&pic, 0xF8); // 只启用IR0, IR1, IR2
print_pic_status(&pic);
printf("
2. 模拟中断请求和处理:
");
printf("------------------------
");
// 触发键盘中断(IR1)
printf("
触发键盘中断(IR1):
");
trigger_interrupt(&pic, 1);
// 模拟CPU执行中断服务程序
printf("
执行键盘中断服务程序...
");
// 发送非特定EOI
printf("
发送非特定EOI:
");
write_ocw2(&pic, 0x20); // 非特定EOI
print_pic_status(&pic);
// 触发时钟中断(IR0)和磁盘中断(IR6)
printf("
同时触发时钟中断(IR0)和磁盘中断(IR6):
");
trigger_interrupt(&pic, 0);
trigger_interrupt(&pic, 6); // 应该被屏蔽
// 发送特定EOI给时钟中断
printf("
发送特定EOI给时钟中断:
");
write_ocw2(&pic, 0x60); // 特定EOI, IR0
print_pic_status(&pic);
return 0;
}
8.3.3 8259A的工作模式
8259A支持多种工作模式,可通过初始化命令字(ICW)和操作命令字(OCW)进行配置:
全嵌套模式(Fully Nested Mode):默认模式,IR0优先级最高,IR7最低。高优先级中断可打断低优先级中断处理。
轮转优先级模式(Rotating Priority Mode):
自动轮转:每次中断处理完后,该中断源自动变为最低优先级。特定轮转:通过命令将指定的中断源设为最低优先级。
特殊屏蔽模式(Special Mask Mode):允许被屏蔽的中断源仍然参与优先级比较,但不产生中断请求。
轮询模式(Poll Mode):禁用INT引脚,CPU主动查询8259A获取中断状态。
下面是使用不同模式的示例代码:
c
#include <stdio.h>
#include <stdint.h>
// 模拟8259A寄存器端口
#define PIC1_COMMAND 0x20
#define PIC1_DATA 0x21
#define PIC2_COMMAND 0xA0
#define PIC2_DATA 0xA1
// 模拟I/O端口读写
void outb(uint16_t port, uint8_t value) {
printf("写端口0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
// 模拟返回值
uint8_t value = 0;
if (port == PIC1_DATA) value = 0xF8; // IMR值
printf("读端口0x%04X: 0x%02X
", port, value);
return value;
}
// 初始化主8259A和从8259A
void init_pics(uint8_t master_offset, uint8_t slave_offset) {
printf("初始化8259A PIC...
");
// 保存当前屏蔽
uint8_t master_mask = inb(PIC1_DATA);
uint8_t slave_mask = inb(PIC2_DATA);
// ICW1: 初始化主片
outb(PIC1_COMMAND, 0x11); // 需要ICW4, 级联模式, 边沿触发
outb(PIC2_COMMAND, 0x11); // 需要ICW4, 级联模式, 边沿触发
// ICW2: 设置中断向量号基址
outb(PIC1_DATA, master_offset); // IRQ0-7映射到master_offset+0到master_offset+7
outb(PIC2_DATA, slave_offset); // IRQ8-15映射到slave_offset+0到slave_offset+7
// ICW3: 设置级联关系
outb(PIC1_DATA, 0x04); // 告诉主片IR2连接从片
outb(PIC2_DATA, 0x02); // 告诉从片它连接到主片的IR2
// ICW4: 附加信息
outb(PIC1_DATA, 0x01); // 8086模式
outb(PIC2_DATA, 0x01); // 8086模式
// 恢复屏蔽
outb(PIC1_DATA, master_mask);
outb(PIC2_DATA, slave_mask);
printf("8259A初始化完成
");
}
// 设置自动轮转优先级模式
void set_rotating_priority_auto(void) {
printf("设置自动轮转优先级模式
");
outb(PIC1_COMMAND, 0x80); // OCW2: 自动轮转优先级, 非特定EOI
}
// 设置特定轮转优先级模式
void set_rotating_priority_specific(uint8_t irq) {
printf("设置特定轮转优先级模式, IRQ%d为最低优先级
", irq);
outb(PIC1_COMMAND, 0x60 | (irq & 0x07)); // OCW2: 特定轮转优先级
}
// 设置特殊屏蔽模式
void set_special_mask_mode(bool enable) {
printf("%s特殊屏蔽模式
", enable ? "启用" : "禁用");
// 读取当前OCW3
outb(PIC1_COMMAND, 0x0B); // OCW3: 读取IRR
if (enable) {
outb(PIC1_COMMAND, 0x68); // OCW3: 启用特殊屏蔽
} else {
outb(PIC1_COMMAND, 0x48); // OCW3: 禁用特殊屏蔽
}
}
// 进入轮询模式
uint8_t poll_interrupt(void) {
printf("进入轮询模式
");
outb(PIC1_COMMAND, 0x0C); // OCW3: 启用轮询模式
// 模拟轮询结果
uint8_t poll_result = 0x83; // 假设返回IRQ1中断
printf("轮询结果: 0x%02X (IRQ%d)
", poll_result, poll_result & 0x07);
return poll_result & 0x07; // 返回IRQ号
}
int main() {
printf("8259A工作模式演示
");
printf("================
");
// 1. 初始化8259A
init_pics(0x20, 0x28);
// 2. 演示不同工作模式
// 自动轮转优先级模式
printf("
1. 自动轮转优先级模式:
");
printf("---------------------
");
set_rotating_priority_auto();
// 处理中断并自动轮转优先级
printf("
模拟处理IRQ0中断后:
");
outb(PIC1_COMMAND, 0xA0); // OCW2: 自动轮转 + 非特定EOI
printf("IRQ0变为最低优先级, 现在优先级顺序为: IRQ1, IRQ2, ..., IRQ7, IRQ0
");
// 特定轮转优先级模式
printf("
2. 特定轮转优先级模式:
");
printf("---------------------
");
set_rotating_priority_specific(3);
printf("现在优先级顺序为: IRQ4, IRQ5, IRQ6, IRQ7, IRQ0, IRQ1, IRQ2, IRQ3
");
// 特殊屏蔽模式
printf("
3. 特殊屏蔽模式:
");
printf("---------------
");
set_special_mask_mode(true);
printf("
假设IRQ1正在处理中,IMR=0xFD (除IRQ1外全部屏蔽):
");
outb(PIC1_DATA, 0xFD);
printf("
在普通模式下,只有IRQ0能够打断IRQ1服务
");
printf("
在特殊屏蔽模式下,打开IMR中某位,该IRQ就能打断当前服务
");
outb(PIC1_DATA, 0xF5); // 开放IRQ1和IRQ3
// 轮询模式
printf("
4. 轮询模式:
");
printf("------------
");
uint8_t irq = poll_interrupt();
printf("服务IRQ%d中断
", irq);
// 禁用特殊屏蔽模式,恢复正常
set_special_mask_mode(false);
return 0;
}
8.3.4 8259A的编程
8259A的编程主要通过三组命令字:
初始化命令字(ICW):用于初始化8259A
ICW1:基本操作模式(单/多片、触发方式等)ICW2:中断向量号基址ICW3:级联配置ICW4:特殊功能设置
操作命令字(OCW):用于控制8259A的运行
OCW1:设置中断屏蔽寄存器(IMR)OCW2:结束中断(EOI)和优先级控制OCW3:读取状态和特殊模式控制
下面是一个完整的8259A编程示例,演示如何初始化和控制8259A:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 8259A端口地址
#define MASTER_PIC_COMMAND 0x20
#define MASTER_PIC_DATA 0x21
#define SLAVE_PIC_COMMAND 0xA0
#define SLAVE_PIC_DATA 0xA1
// ICW1标志
#define ICW1_ICW4_NEEDED 0x01
#define ICW1_SINGLE_MODE 0x02
#define ICW1_INTERVAL4 0x04
#define ICW1_LEVEL_TRIGGERED 0x08
#define ICW1_INIT 0x10
// ICW4标志
#define ICW4_8086_MODE 0x01
#define ICW4_AUTO_EOI 0x02
#define ICW4_BUFFERED_SLAVE 0x08
#define ICW4_BUFFERED_MASTER 0x0C
#define ICW4_SFNM 0x10
// OCW2命令
#define OCW2_EOI 0x20
#define OCW2_SL 0x40
#define OCW2_R 0x80
// OCW3命令
#define OCW3_READ_IRR 0x0A
#define OCW3_READ_ISR 0x0B
#define OCW3_POLL 0x0C
#define OCW3_SET_SMM 0x68
#define OCW3_RESET_SMM 0x48
// 模拟I/O端口读写函数
void outb(uint16_t port, uint8_t value) {
printf("写端口0x%04X: 0x%02X
", port, value);
// 实际代码中会写入硬件端口
}
uint8_t inb(uint16_t port) {
// 模拟读取状态
static uint8_t status_values[4] = { 0x01, 0x00, 0x00, 0x00 };
static int call_count = 0;
uint8_t result = status_values[call_count % 4];
printf("读端口0x%04X: 0x%02X
", port, result);
call_count++;
return result;
}
// 初始化8259A
void init_8259a(uint8_t master_offset, uint8_t slave_offset) {
printf("
开始初始化8259A...
");
// 保存现有IMR
uint8_t master_mask = inb(MASTER_PIC_DATA);
uint8_t slave_mask = inb(SLAVE_PIC_DATA);
// 向两个PIC发送ICW1
outb(MASTER_PIC_COMMAND, ICW1_INIT | ICW1_ICW4_NEEDED);
outb(SLAVE_PIC_COMMAND, ICW1_INIT | ICW1_ICW4_NEEDED);
// 向两个PIC发送ICW2(设置中断向量号基址)
outb(MASTER_PIC_DATA, master_offset); // 主片: IRQ0-7 -> 主片offset+0到+7
outb(SLAVE_PIC_DATA, slave_offset); // 从片: IRQ8-15 -> 从片offset+0到+7
// 向两个PIC发送ICW3(设置级联关系)
outb(MASTER_PIC_DATA, 0x04); // IR2连接从片
outb(SLAVE_PIC_DATA, 0x02); // 从片ID为2
// 向两个PIC发送ICW4
outb(MASTER_PIC_DATA, ICW4_8086_MODE); // 8086模式
outb(SLAVE_PIC_DATA, ICW4_8086_MODE); // 8086模式
// 恢复IMR
outb(MASTER_PIC_DATA, master_mask);
outb(SLAVE_PIC_DATA, slave_mask);
printf("8259A初始化完成
");
}
// 设置8259A中断屏蔽
void set_irq_mask(uint16_t mask) {
outb(MASTER_PIC_DATA, mask & 0xFF);
outb(SLAVE_PIC_DATA, (mask >> 8) & 0xFF);
printf("设置IRQ屏蔽: 0x%04X
", mask);
}
// 启用指定IRQ
void enable_irq(uint8_t irq) {
uint16_t port;
uint8_t value;
if (irq < 8) {
port = MASTER_PIC_DATA;
} else {
port = SLAVE_PIC_DATA;
irq -= 8;
}
value = inb(port) & ~(1 << irq);
outb(port, value);
printf("启用IRQ%d
", irq);
}
// 禁用指定IRQ
void disable_irq(uint8_t irq) {
uint16_t port;
uint8_t value;
if (irq < 8) {
port = MASTER_PIC_DATA;
} else {
port = SLAVE_PIC_DATA;
irq -= 8;
}
value = inb(port) | (1 << irq);
outb(port, value);
printf("禁用IRQ%d
", irq);
}
// 发送非特定EOI命令
void send_eoi(uint8_t irq) {
if (irq >= 8) {
outb(SLAVE_PIC_COMMAND, OCW2_EOI);
}
outb(MASTER_PIC_COMMAND, OCW2_EOI);
printf("发送EOI给IRQ%d
", irq);
}
// 发送特定EOI命令
void send_specific_eoi(uint8_t irq) {
uint8_t specific_irq = irq & 7; // 获取IRQ在PIC内的编号(0-7)
if (irq >= 8) {
outb(SLAVE_PIC_COMMAND, OCW2_EOI | OCW2_SL | specific_irq);
outb(MASTER_PIC_COMMAND, OCW2_EOI | OCW2_SL | 2); // 级联IRQ
} else {
outb(MASTER_PIC_COMMAND, OCW2_EOI | OCW2_SL | specific_irq);
}
printf("发送特定EOI给IRQ%d
", irq);
}
// 读取中断请求寄存器(IRR)
uint16_t read_irr(void) {
outb(MASTER_PIC_COMMAND, OCW3_READ_IRR);
outb(SLAVE_PIC_COMMAND, OCW3_READ_IRR);
uint16_t irr = inb(SLAVE_PIC_COMMAND) << 8;
irr |= inb(MASTER_PIC_COMMAND);
printf("读取IRR: 0x%04X
", irr);
return irr;
}
// 读取中断服务寄存器(ISR)
uint16_t read_isr(void) {
outb(MASTER_PIC_COMMAND, OCW3_READ_ISR);
outb(SLAVE_PIC_COMMAND, OCW3_READ_ISR);
uint16_t isr = inb(SLAVE_PIC_COMMAND) << 8;
isr |= inb(MASTER_PIC_COMMAND);
printf("读取ISR: 0x%04X
", isr);
return isr;
}
// 模拟中断处理程序
void interrupt_handler(uint8_t irq) {
printf("
开始处理IRQ%d中断...
", irq);
// 读取IRR和ISR
read_irr();
read_isr();
printf("执行中断处理代码...
");
// 发送EOI
send_eoi(irq);
printf("IRQ%d中断处理完成
", irq);
}
// 8259A中断控制器示例程序
int main() {
printf("8259A中断控制器编程示例
");
printf("=====================
");
// 1. 初始化8259A
init_8259a(0x20, 0x28); // 主片IRQ0-7映射到0x20-0x27,从片IRQ8-15映射到0x28-0x2F
// 2. 设置中断屏蔽(只允许IRQ0、IRQ1和IRQ12)
set_irq_mask(0xEFFB); // 1110 1111 1111 1011
// 3. 启用特定IRQ
enable_irq(4); // 启用IRQ4(COM1)
// 4. 读取IRR和ISR
printf("
读取初始状态:
");
read_irr();
read_isr();
// 5. 模拟中断处理
printf("
模拟处理IRQ1(键盘)中断:
");
interrupt_handler(1);
// 6. 模拟级联中断处理
printf("
模拟处理IRQ12(PS/2鼠标)中断:
");
interrupt_handler(12);
// 7. 禁用IRQ
printf("
禁用IRQ1和IRQ12:
");
disable_irq(1);
disable_irq(12);
// 8. 使用特定EOI
printf("
使用特定EOI模式:
");
interrupt_handler(4);
send_specific_eoi(4);
// 9. 读取最终状态
printf("
读取最终状态:
");
read_irr();
read_isr();
return 0;
}
8.4 8259A在x86 PC系统中的应用
在IBM PC系列计算机中,8259A扮演着至关重要的角色,负责管理系统中各种外部设备的中断请求。典型的PC系统中使用两个8259A级联,提供15个IRQ通道。
8259A在IBM PC中的典型配置
在标准PC架构中,两个8259A的配置如下:
主8259A:
IRQ0: 系统时钟(Timer)IRQ1: 键盘控制器IRQ2: 级联到从8259AIRQ3: COM2串口IRQ4: COM1串口IRQ5: LPT2并口或声卡IRQ6: 软盘控制器IRQ7: LPT1并口
从8259A:
IRQ8: 实时时钟IRQ9: 重定向到IRQ2IRQ10: 保留IRQ11: 保留IRQ12: PS/2鼠标IRQ13: 协处理器IRQ14: 主IDE通道IRQ15: 次IDE通道
实现PC兼容的中断处理
下面是一个模拟PC兼容中断处理系统的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// 8259A控制器端口
#define MASTER_PIC_COMMAND 0x20
#define MASTER_PIC_DATA 0x21
#define SLAVE_PIC_COMMAND 0xA0
#define SLAVE_PIC_DATA 0xA1
// IRQ定义
#define IRQ_TIMER 0
#define IRQ_KEYBOARD 1
#define IRQ_CASCADE 2
#define IRQ_COM2 3
#define IRQ_COM1 4
#define IRQ_LPT2 5
#define IRQ_FLOPPY 6
#define IRQ_LPT1 7
#define IRQ_RTC 8
#define IRQ_REDIRECT 9
#define IRQ_RESERVED1 10
#define IRQ_RESERVED2 11
#define IRQ_MOUSE 12
#define IRQ_FPU 13
#define IRQ_PRIMARY_IDE 14
#define IRQ_SECONDARY_IDE 15
// 中断向量表偏移
#define INT_VECTOR_OFFSET 0x20
// 模拟中断处理函数
typedef void (*interrupt_handler_t)(void);
// 中断处理函数表
interrupt_handler_t interrupt_handlers[16] = { NULL };
// 模拟CPU状态
typedef struct {
uint32_t eax, ebx, ecx, edx;
uint32_t esi, edi, ebp, esp;
uint32_t eflags;
bool interrupt_enabled;
} CPU;
// 模拟8259A状态
typedef struct {
uint8_t irr; // 中断请求寄存器
uint8_t isr; // 中断服务寄存器
uint8_t imr; // 中断屏蔽寄存器
uint8_t vector_offset; // 中断向量偏移
bool auto_eoi; // 自动EOI模式
} PIC;
// 全局状态
CPU cpu;
PIC master_pic;
PIC slave_pic;
// 初始化中断系统
void init_interrupt_system(void) {
printf("初始化中断系统...
");
// 初始化CPU状态
memset(&cpu, 0, sizeof(CPU));
cpu.eflags = 0x202; // IF=1, 中断启用
cpu.interrupt_enabled = true;
// 初始化8259A状态
memset(&master_pic, 0, sizeof(PIC));
memset(&slave_pic, 0, sizeof(PIC));
master_pic.imr = 0xFF; // 默认屏蔽所有中断
slave_pic.imr = 0xFF; // 默认屏蔽所有中断
master_pic.vector_offset = INT_VECTOR_OFFSET;
slave_pic.vector_offset = INT_VECTOR_OFFSET + 8;
printf("模拟初始化8259A...
");
// 初始化主PIC
// ICW1: 需要ICW4,级联模式,边沿触发
printf("主PIC ICW1: 0x11
");
// ICW2: 中断向量偏移
printf("主PIC ICW2: 0x%02X
", master_pic.vector_offset);
// ICW3: 级联配置(IR2连接从PIC)
printf("主PIC ICW3: 0x04
");
// ICW4: 8086模式
printf("主PIC ICW4: 0x01
");
// 初始化从PIC
// ICW1: 需要ICW4,级联模式,边沿触发
printf("从PIC ICW1: 0x11
");
// ICW2: 中断向量偏移
printf("从PIC ICW2: 0x%02X
", slave_pic.vector_offset);
// ICW3: 级联配置(从PIC ID为2)
printf("从PIC ICW3: 0x02
");
// ICW4: 8086模式
printf("从PIC ICW4: 0x01
");
printf("中断系统初始化完成
");
}
// 注册中断处理程序
void register_interrupt_handler(uint8_t irq, interrupt_handler_t handler) {
if (irq < 16) {
interrupt_handlers[irq] = handler;
printf("注册IRQ%d的中断处理程序
", irq);
}
}
// 启用特定IRQ
void enable_irq(uint8_t irq) {
if (irq >= 16) return;
if (irq < 8) {
master_pic.imr &= ~(1 << irq);
printf("启用主PIC IRQ%d
", irq);
} else {
slave_pic.imr &= ~(1 << (irq - 8));
master_pic.imr &= ~(1 << IRQ_CASCADE); // 确保从PIC级联IRQ也启用
printf("启用从PIC IRQ%d
", irq);
}
}
// 禁用特定IRQ
void disable_irq(uint8_t irq) {
if (irq >= 16) return;
if (irq < 8) {
master_pic.imr |= (1 << irq);
printf("禁用主PIC IRQ%d
", irq);
} else {
slave_pic.imr |= (1 << (irq - 8));
printf("禁用从PIC IRQ%d
", irq);
}
}
// 打印中断控制器状态
void print_pic_status(void) {
printf("
8259A状态:
");
printf("主PIC: IRR=0x%02X ISR=0x%02X IMR=0x%02X
",
master_pic.irr, master_pic.isr, master_pic.imr);
printf("从PIC: IRR=0x%02X ISR=0x%02X IMR=0x%02X
",
slave_pic.irr, slave_pic.isr, slave_pic.imr);
printf("启用的中断: ");
for (int i = 0; i < 16; i++) {
bool enabled;
if (i < 8) {
enabled = (master_pic.imr & (1 << i)) == 0;
} else {
enabled = (slave_pic.imr & (1 << (i - 8))) == 0;
}
if (enabled) {
printf("IRQ%d ", i);
}
}
printf("
");
}
// 发送EOI
void send_eoi(uint8_t irq) {
if (irq >= 8) {
// 如果是从PIC的中断,需要向两个PIC都发送EOI
printf("发送EOI到从PIC
");
slave_pic.isr &= ~(1 << (irq - 8));
printf("发送EOI到主PIC(级联IRQ)
");
master_pic.isr &= ~(1 << IRQ_CASCADE);
} else {
printf("发送EOI到主PIC
");
master_pic.isr &= ~(1 << irq);
}
}
// 触发中断
void trigger_interrupt(uint8_t irq) {
if (irq >= 16) return;
printf("
触发IRQ%d...
", irq);
// 设置IRR
if (irq < 8) {
master_pic.irr |= (1 << irq);
} else {
slave_pic.irr |= (1 << (irq - 8));
master_pic.irr |= (1 << IRQ_CASCADE); // 级联中断
}
// 检查中断是否被屏蔽
bool masked = false;
if (irq < 8) {
masked = (master_pic.imr & (1 << irq)) != 0;
} else {
masked = (slave_pic.imr & (1 << (irq - 8))) != 0 ||
(master_pic.imr & (1 << IRQ_CASCADE)) != 0;
}
if (masked) {
printf("IRQ%d被屏蔽,不处理
", irq);
return;
}
// 检查CPU中断是否启用
if (!cpu.interrupt_enabled) {
printf("CPU中断已禁用,不处理
");
return;
}
printf("CPU接收中断...
");
// 计算中断向量号
uint8_t vector;
if (irq < 8) {
vector = master_pic.vector_offset + irq;
// 更新ISR和IRR
master_pic.isr |= (1 << irq);
master_pic.irr &= ~(1 << irq);
} else {
vector = slave_pic.vector_offset + (irq - 8);
// 更新ISR和IRR
slave_pic.isr |= (1 << (irq - 8));
slave_pic.irr &= ~(1 << (irq - 8));
master_pic.isr |= (1 << IRQ_CASCADE);
master_pic.irr &= ~(1 << IRQ_CASCADE);
}
printf("调用中断向量0x%02X
", vector);
// 调用中断处理程序
if (interrupt_handlers[irq] != NULL) {
printf("执行中断处理程序...
");
interrupt_handlers[irq]();
} else {
printf("警告: IRQ%d没有处理程序
", irq);
}
// 发送EOI
send_eoi(irq);
}
// 模拟中断处理程序
void timer_handler(void) {
printf("时钟中断处理程序: 计时器tick
");
}
void keyboard_handler(void) {
printf("键盘中断处理程序: 处理按键
");
// 模拟读取键盘控制器数据
uint8_t scan_code = 0x1E; // 'A'键的扫描码
printf("读取到扫描码: 0x%02X
", scan_code);
}
void com1_handler(void) {
printf("COM1串口中断处理程序: 接收数据
");
// 模拟读取串口数据
uint8_t data = 0x41; // 'A'的ASCII码
printf("接收到串口数据: 0x%02X (ASCII: '%c')
", data, data);
}
void mouse_handler(void) {
printf("鼠标中断处理程序: 处理鼠标移动
");
// 模拟读取鼠标数据
uint8_t status = 0x08; // 左键按下
int8_t x_movement = 10;
int8_t y_movement = -5;
printf("鼠标状态: 0x%02X, X移动: %d, Y移动: %d
", status, x_movement, y_movement);
}
void primary_ide_handler(void) {
printf("主IDE中断处理程序: 硬盘操作完成
");
}
// 应用示例
int main() {
printf("PC兼容中断系统模拟
");
printf("================
");
// 初始化中断系统
init_interrupt_system();
// 注册中断处理程序
register_interrupt_handler(IRQ_TIMER, timer_handler);
register_interrupt_handler(IRQ_KEYBOARD, keyboard_handler);
register_interrupt_handler(IRQ_COM1, com1_handler);
register_interrupt_handler(IRQ_MOUSE, mouse_handler);
register_interrupt_handler(IRQ_PRIMARY_IDE, primary_ide_handler);
// 启用需要的中断
enable_irq(IRQ_TIMER);
enable_irq(IRQ_KEYBOARD);
enable_irq(IRQ_COM1);
enable_irq(IRQ_MOUSE);
enable_irq(IRQ_PRIMARY_IDE);
print_pic_status();
// 模拟触发几个中断
trigger_interrupt(IRQ_TIMER);
trigger_interrupt(IRQ_KEYBOARD);
trigger_interrupt(IRQ_COM1);
trigger_interrupt(IRQ_MOUSE);
trigger_interrupt(IRQ_PRIMARY_IDE);
// 禁用一些中断
printf("
禁用部分中断...
");
disable_irq(IRQ_TIMER);
disable_irq(IRQ_COM1);
print_pic_status();
// 再次触发中断(部分被屏蔽)
trigger_interrupt(IRQ_TIMER); // 应被屏蔽
trigger_interrupt(IRQ_KEYBOARD); // 应正常处理
trigger_interrupt(IRQ_COM1); // 应被屏蔽
return 0;
}
8.5 外部设备中断服务程序设计
外部设备中断服务程序是系统与外设交互的核心部分,负责处理来自各种外部设备的中断请求,如键盘、鼠标、硬盘等。设计高效、稳定的中断服务程序是系统性能和稳定性的关键。
外部中断服务程序的基本结构
一个典型的外部中断服务程序通常包含以下几个部分:
保存现场:保存CPU寄存器状态,避免影响原程序执行识别中断源:确定哪个设备触发了中断处理中断:执行特定于该设备的处理逻辑发送EOI:告知中断控制器中断处理完成恢复现场:恢复CPU寄存器状态返回:返回被中断的程序
下面是一个键盘中断服务程序的C语言实现示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 键盘控制器端口
#define KEYBOARD_DATA_PORT 0x60
#define KEYBOARD_STATUS_PORT 0x64
#define KEYBOARD_COMMAND_PORT 0x64
// 8259A端口
#define PIC1_COMMAND_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIC2_COMMAND_PORT 0xA0
#define PIC2_DATA_PORT 0xA1
// EOI命令
#define PIC_EOI 0x20
// 扫描码到ASCII码的映射表(简化)
const char scancode_to_ascii[] = {
0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '',
' ', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '
',
0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ''', '`',
0, '\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0,
'*', 0, ' ', 0
};
// 模拟读取端口
uint8_t inb(uint16_t port) {
// 模拟不同端口的返回值
if (port == KEYBOARD_DATA_PORT) {
// 模拟返回键盘扫描码(例如'A'键)
return 0x1E;
}
return 0;
}
// 模拟写入端口
void outb(uint16_t port, uint8_t value) {
printf("写端口0x%04X: 0x%02X
", port, value);
}
// 键盘中断服务程序
void keyboard_interrupt_handler(void) {
// 在实际汇编代码中,这里会保存所有寄存器
printf("保存CPU寄存器状态
");
// 读取键盘扫描码
uint8_t scancode = inb(KEYBOARD_DATA_PORT);
printf("读取到扫描码: 0x%02X
", scancode);
// 处理扫描码
if (scancode < sizeof(scancode_to_ascii) && scancode_to_ascii[scancode] != 0) {
char ascii = scancode_to_ascii[scancode];
printf("转换为ASCII: '%c'
", ascii);
// 在实际系统中,这里会将按键加入缓冲区或触发回调
printf("处理按键: '%c'
", ascii);
} else if (scancode & 0x80) {
// 键释放事件(最高位为1)
uint8_t released_key = scancode & 0x7F;
printf("键释放: 0x%02X
", released_key);
} else {
printf("未处理的扫描码: 0x%02X
", scancode);
}
// 发送EOI到PIC
outb(PIC1_COMMAND_PORT, PIC_EOI);
printf("发送EOI到8259A
");
// 在实际汇编代码中,这里会恢复所有寄存器
printf("恢复CPU寄存器状态
");
// 返回被中断的程序
printf("中断处理完成,返回被中断程序
");
}
// 初始化键盘控制器
void init_keyboard(void) {
printf("初始化键盘控制器...
");
// 禁用键盘
outb(KEYBOARD_COMMAND_PORT, 0xAD);
// 清空输出缓冲区
inb(KEYBOARD_DATA_PORT);
// 设置键盘控制器配置
outb(KEYBOARD_COMMAND_PORT, 0x60);
outb(KEYBOARD_DATA_PORT, 0x45); // 启用中断、翻译扫描码等
// 执行键盘自检
outb(KEYBOARD_COMMAND_PORT, 0xAA);
// 等待自检结果(模拟)
printf("键盘自检通过
");
// 启用键盘
outb(KEYBOARD_COMMAND_PORT, 0xAE);
// 设置重复率和延迟
outb(KEYBOARD_COMMAND_PORT, 0x60);
outb(KEYBOARD_DATA_PORT, 0x00);
printf("键盘初始化完成
");
}
// 模拟键盘中断
void simulate_keyboard_interrupt(void) {
printf("
模拟键盘中断...
");
keyboard_interrupt_handler();
}
// 全面的键盘驱动示例
int main() {
printf("外部设备中断服务程序设计 - 键盘示例
");
printf("============================
");
// 初始化键盘
init_keyboard();
// 模拟几次键盘中断
for (int i = 0; i < 3; i++) {
simulate_keyboard_interrupt();
}
return 0;
}
8.6 驻留中断服务程序设计
驻留中断服务程序(TSR, Terminate and Stay Resident)是一种特殊的程序,它在执行后不完全退出,而是将部分代码留在内存中继续提供服务,通常通过钩住特定中断来实现。这种技术在DOS时代非常流行,用于实现后台服务、热键功能等。
驻留程序的实现原理
驻留程序的基本原理是:
初始化程序和数据保存原中断向量设置新的中断处理程序使程序部分代码驻留内存终止程序但不释放内存
下面是一个模拟驻留中断服务程序的C语言实现:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
// 模拟DOS中断向量表
typedef void (*interrupt_handler_t)(void);
interrupt_handler_t interrupt_vectors[256] = { NULL };
// 保存原来的中断处理程序
interrupt_handler_t original_handlers[256] = { NULL };
// 热键组合状态
bool ctrl_pressed = false;
bool alt_pressed = false;
// 当前驻留程序状态
bool tsr_active = false;
// 模拟INT 09h键盘中断处理程序
void keyboard_interrupt_handler(void) {
// 模拟读取键盘扫描码
uint8_t scancode = 0x1E; // 假设按下了'A'键
printf("TSR键盘处理程序: 扫描码=0x%02X
", scancode);
// 处理控制键状态
if (scancode == 0x1D) { // Ctrl按下
ctrl_pressed = true;
} else if (scancode == 0x9D) { // Ctrl释放
ctrl_pressed = false;
} else if (scancode == 0x38) { // Alt按下
alt_pressed = true;
} else if (scancode == 0xB8) { // Alt释放
alt_pressed = false;
}
// 检查热键组合
if (ctrl_pressed && alt_pressed) {
if (scancode == 0x13) { // R键 (重新加载TSR)
printf("检测到热键组合: Ctrl+Alt+R
");
printf("重新加载TSR功能
");
tsr_active = true;
} else if (scancode == 0x17) { // I键 (显示TSR信息)
printf("检测到热键组合: Ctrl+Alt+I
");
printf("显示TSR信息:
");
printf(" 状态: %s
", tsr_active ? "活动" : "非活动");
printf(" 内存使用: 2048字节
");
printf(" 热键: Ctrl+Alt+R (重新加载), Ctrl+Alt+I (信息), Ctrl+Alt+U (卸载)
");
} else if (scancode == 0x16) { // U键 (卸载TSR)
printf("检测到热键组合: Ctrl+Alt+U
");
printf("卸载TSR功能
");
tsr_active = false;
}
}
// 调用原始的键盘中断处理程序
printf("调用原始键盘处理程序
");
if (original_handlers[0x09] != NULL) {
original_handlers[0x09]();
}
}
// 模拟INT 08h时钟中断处理程序
void timer_interrupt_handler(void) {
static int counter = 0;
// 只有在TSR活动状态下才执行特定功能
if (tsr_active) {
counter++;
// 每隔一段时间执行一次特定操作
if (counter >= 18) { // 大约每秒一次(18.2Hz时钟)
printf("TSR定时操作: 检查系统状态
");
counter = 0;
}
}
// 调用原始的时钟中断处理程序
if (original_handlers[0x08] != NULL) {
original_handlers[0x08]();
}
}
// 模拟原始的键盘中断处理程序
void original_keyboard_handler(void) {
printf("执行原始键盘处理程序
");
}
// 模拟原始的时钟中断处理程序
void original_timer_handler(void) {
// 时钟中断处理很频繁,不打印消息
}
// 模拟DOS中断设置函数
void set_interrupt_vector(uint8_t interrupt, interrupt_handler_t handler) {
// 保存原来的处理程序
original_handlers[interrupt] = interrupt_vectors[interrupt];
// 设置新的处理程序
interrupt_vectors[interrupt] = handler;
printf("设置中断向量0x%02X指向新的处理程序
", interrupt);
}
// 模拟DOS中断恢复函数
void restore_interrupt_vector(uint8_t interrupt) {
if (original_handlers[interrupt] != NULL) {
interrupt_vectors[interrupt] = original_handlers[interrupt];
printf("恢复中断向量0x%02X到原始处理程序
", interrupt);
}
}
// 模拟DOS内存分配函数
void* allocate_memory(size_t size) {
void* memory = malloc(size);
printf("分配%zu字节内存: 0x%p
", size, memory);
return memory;
}
// 模拟触发中断
void trigger_interrupt(uint8_t interrupt) {
printf("
触发中断0x%02X...
", interrupt);
if (interrupt_vectors[interrupt] != NULL) {
interrupt_vectors[interrupt]();
} else {
printf("没有找到中断0x%02X的处理程序
", interrupt);
}
}
// 驻留程序初始化
void tsr_initialize(void) {
printf("
TSR程序初始化...
");
// 设置初始状态
tsr_active = true;
// 保存并设置中断向量
printf("钩住键盘中断(INT 09h)...
");
set_interrupt_vector(0x09, keyboard_interrupt_handler);
printf("钩住时钟中断(INT 08h)...
");
set_interrupt_vector(0x08, timer_interrupt_handler);
// 分配驻留内存(在实际DOS中,这会告诉DOS保留多少内存)
allocate_memory(2048);
printf("TSR初始化完成,程序将驻留内存
");
}
// 驻留程序卸载
void tsr_uninstall(void) {
printf("
卸载TSR程序...
");
// 恢复原始中断向量
restore_interrupt_vector(0x09);
restore_interrupt_vector(0x08);
printf("TSR程序已卸载
");
}
// 模拟DOS环境下的驻留程序
int main() {
printf("驻留中断服务程序(TSR)示例
");
printf("=======================
");
// 设置一些原始中断处理程序
interrupt_vectors[0x08] = original_timer_handler;
interrupt_vectors[0x09] = original_keyboard_handler;
// 初始化并安装TSR
tsr_initialize();
// 模拟系统运行一段时间
printf("
模拟系统运行...
");
// 触发几次键盘中断
trigger_interrupt(0x09); // 普通按键
// 模拟Ctrl+Alt+I热键
ctrl_pressed = true;
alt_pressed = true;
trigger_interrupt(0x09); // 热键按下(扫描码设为0x17/'I')
// 触发时钟中断
for (int i = 0; i < 20; i++) {
trigger_interrupt(0x08);
}
// 模拟Ctrl+Alt+U热键(卸载TSR)
ctrl_pressed = true;
alt_pressed = true;
trigger_interrupt(0x09); // 热键按下(扫描码设为0x16/'U')
// 再次触发时钟中断(TSR功能应已禁用)
for (int i = 0; i < 5; i++) {
trigger_interrupt(0x08);
}
// 完全卸载TSR
tsr_uninstall();
return 0;
}
习题8
简述x86处理器中断系统的三种类型,并说明它们的区别。
8259A中断控制器的主要寄存器有哪些?它们各自的功能是什么?
请说明8259A级联系统中,当从片的IRQ12(鼠标)发生中断时,中断处理的完整流程。
编写一个C语言函数,用于在保护模式下设置IDT中的一个中断门描述符。
简述驻留中断服务程序(TSR)的工作原理及其应用场景。
如何在程序中禁用和重新启用中断?请给出x86汇编代码示例。
当多个中断同时发生时,8259A如何决定优先处理哪个中断?至少说明两种优先级模式。
编写一个简单的C语言模拟程序,展示中断处理过程中”现场保护”和”现场恢复”的基本操作。


