i386 架构中断管理函数详解
概述
本文档详细解释 i386 架构下中断管理相关函数的实现,包括:
request_irq- 注册中断处理程序free_irq- 释放中断处理程序disable_irq- 禁用中断(等待完成)disable_irq_nosync- 禁用中断(不等待)enable_irq- 启用中断
这些函数在 Linux 2.6.10 内核中的实现分为两个层次:
- 通用层 :
kernel/irq/manage.c- 提供通用的中断管理逻辑 - 架构层 :
arch/i386/kernel/i8259.c- 提供 i386 架构特定的硬件操作
1. request_irq - 注册中断处理程序
函数原型
c
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char *devname,
void *dev_id)
实现代码
c
// kernel/irq/manage.c:310-346
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags, const char * devname, void *dev_id)
{
struct irqaction * action;
int retval;
// 参数检查:共享中断必须提供 dev_id
if ((irqflags & SA_SHIRQ) && !dev_id)
return -EINVAL;
if (irq >= NR_IRQS)
return -EINVAL;
if (!handler)
return -EINVAL;
// 分配 irqaction 结构
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
if (!action)
return -ENOMEM;
// 初始化 irqaction 结构
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
// 调用 setup_irq 设置中断
retval = setup_irq(irq, action);
if (retval)
kfree(action);
return retval;
}
代码逻辑解释
-
参数验证:
- 如果设置了
SA_SHIRQ(共享中断)标志,必须提供dev_id - 检查
irq是否在有效范围内(< NR_IRQS) - 检查
handler是否为空
- 如果设置了
-
分配内存:
- 使用
kmalloc分配struct irqaction结构 - 使用
GFP_ATOMIC标志,因为可能在中断上下文中调用
- 使用
-
初始化结构:
- 设置中断处理函数
handler - 设置标志位
flags(如SA_SHIRQ、SA_INTERRUPT等) - 清空 CPU 掩码
- 设置设备名称和
dev_id
- 设置中断处理函数
-
调用 setup_irq:
- 将
irqaction添加到中断描述符的action链表 - 如果是第一个处理程序,启用中断线
- 将
setup_irq 函数
c
// kernel/irq/manage.c:153-218
int setup_irq(unsigned int irq, struct irqaction * new)
{
struct irq_desc *desc = irq_desc + irq;
struct irqaction *old, **p;
unsigned long flags;
int shared = 0;
// 检查中断线是否已配置有效的中断控制器
if (desc->handler == &no_irq_type)
return -ENOSYS;
// 如果设置了 SA_SAMPLE_RANDOM,初始化随机数生成器
if (new->flags & SA_SAMPLE_RANDOM) {
rand_initialize_irq(irq);
}
// 原子操作:加锁
spin_lock_irqsave(&desc->lock, flags);
p = &desc->action;
// 检查是否已有处理程序
if ((old = *p) != NULL) {
// 必须都支持共享才能共享
if (!(old->flags & new->flags & SA_SHIRQ)) {
spin_unlock_irqrestore(&desc->lock, flags);
return -EBUSY;
}
// 将新处理程序添加到链表末尾
do {
p = &old->next;
old = *p;
} while (old);
shared = 1;
}
// 添加新处理程序
*p = new;
// 如果是第一个处理程序,启用中断
if (!shared) {
desc->depth = 0;
desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT |
IRQ_WAITING | IRQ_INPROGRESS);
if (desc->handler->startup)
desc->handler->startup(irq);
else
desc->handler->enable(irq);
}
spin_unlock_irqrestore(&desc->lock, flags);
new->irq = irq;
register_irq_proc(irq);
new->dir = NULL;
register_handler_proc(irq, new);
return 0;
}
关键点:
- 使用自旋锁保护临界区
- 支持中断共享(多个设备共享同一中断线)
- 第一个处理程序注册时启用中断线
- 调用硬件相关的
enable或startup函数
2. free_irq - 释放中断处理程序
函数原型
c
void free_irq(unsigned int irq, void *dev_id)
实现代码
c
// kernel/irq/manage.c:234-277
void free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *desc;
struct irqaction **p;
unsigned long flags;
if (irq >= NR_IRQS)
return;
desc = irq_desc + irq;
spin_lock_irqsave(&desc->lock, flags);
p = &desc->action;
// 遍历 action 链表,查找匹配的 dev_id
for (;;) {
struct irqaction * action = *p;
if (action) {
struct irqaction **pp = p;
p = &action->next;
// 如果 dev_id 不匹配,继续查找
if (action->dev_id != dev_id)
continue;
// 找到匹配的处理程序,从链表中移除
*pp = action->next;
// 如果没有其他处理程序,禁用中断
if (!desc->action) {
desc->status |= IRQ_DISABLED;
if (desc->handler->shutdown)
desc->handler->shutdown(irq);
else
desc->handler->disable(irq);
}
spin_unlock_irqrestore(&desc->lock, flags);
unregister_handler_proc(irq, action);
// 等待其他 CPU 上的中断处理完成
synchronize_irq(irq);
kfree(action);
return;
}
// 没有找到匹配的处理程序
printk(KERN_ERR "Trying to free free IRQ%d\n", irq);
spin_unlock_irqrestore(&desc->lock, flags);
return;
}
}
代码逻辑解释
-
参数检查 :检查
irq是否有效 -
查找处理程序:
- 遍历
desc->action链表 - 通过
dev_id匹配要释放的处理程序 - 在共享中断中,
dev_id用于区分不同的设备
- 遍历
-
移除处理程序:
- 从链表中移除匹配的
irqaction - 如果这是最后一个处理程序,禁用中断线
- 从链表中移除匹配的
-
同步等待:
- 调用
synchronize_irq(irq)等待其他 CPU 上的中断处理完成 - 确保处理程序不再被调用后再释放内存
- 调用
-
清理资源:
- 从
/proc文件系统注销 - 释放
irqaction结构的内存
- 从
3. disable_irq_nosync - 禁用中断(不等待)
函数原型
c
void disable_irq_nosync(unsigned int irq)
实现代码
c
// kernel/irq/manage.c:52-63
void disable_irq_nosync(unsigned int irq)
{
irq_desc_t *desc = irq_desc + irq;
unsigned long flags;
spin_lock_irqsave(&desc->lock, flags);
// 增加嵌套深度
if (!desc->depth++) {
// 如果是第一次禁用,设置 IRQ_DISABLED 标志
desc->status |= IRQ_DISABLED;
// 调用硬件相关的 disable 函数
desc->handler->disable(irq);
}
spin_unlock_irqrestore(&desc->lock, flags);
}
代码逻辑解释
-
嵌套计数:
desc->depth记录禁用的嵌套深度- 每次调用
disable_irq_nosync都会增加depth - 只有第一次禁用(
depth从 0 变为 1)时才真正禁用硬件中断
-
硬件操作:
- 设置
IRQ_DISABLED状态标志 - 调用硬件相关的
disable函数(如disable_8259A_irq)
- 设置
-
不等待:
- 函数立即返回,不等待正在执行的中断处理程序完成
- 适用于中断上下文或需要快速禁用中断的场景
i386 架构的硬件实现
c
// arch/i386/kernel/i8259.c:90-102
void disable_8259A_irq(unsigned int irq)
{
unsigned int mask = 1 << irq;
unsigned long flags;
spin_lock_irqsave(&i8259A_lock, flags);
// 在缓存掩码中设置对应位
cached_irq_mask |= mask;
// 根据中断号选择主 PIC 或从 PIC
if (irq & 8)
outb(cached_slave_mask, PIC_SLAVE_IMR);
else
outb(cached_master_mask, PIC_MASTER_IMR);
spin_unlock_irqrestore(&i8259A_lock, flags);
}
硬件操作:
- 8259A PIC(可编程中断控制器)通过 IMR(中断屏蔽寄存器)控制中断
- 主 PIC 控制 IRQ 0-7,从 PIC 控制 IRQ 8-15
- 通过
outb写入 IMR 寄存器来屏蔽中断
4. disable_irq - 禁用中断(等待完成)
函数原型
c
void disable_irq(unsigned int irq)
实现代码
c
// kernel/irq/manage.c:79-86
void disable_irq(unsigned int irq)
{
irq_desc_t *desc = irq_desc + irq;
// 先禁用中断(不等待)
disable_irq_nosync(irq);
// 如果有处理程序正在执行,等待其完成
if (desc->action)
synchronize_irq(irq);
}
代码逻辑解释
-
调用 disable_irq_nosync:
- 先禁用中断硬件
-
等待完成:
- 如果有中断处理程序正在执行(
desc->action不为空) - 调用
synchronize_irq(irq)等待其完成
- 如果有中断处理程序正在执行(
synchronize_irq 函数
c
// kernel/irq/manage.c:29-35
void synchronize_irq(unsigned int irq)
{
struct irq_desc *desc = irq_desc + irq;
// 等待中断处理完成
while (desc->status & IRQ_INPROGRESS)
cpu_relax();
}
关键点:
IRQ_INPROGRESS标志表示中断正在处理中- 使用
cpu_relax()优化忙等待循环(不会让出 CPU) - 确保在禁用中断后,不会有处理程序继续执行
关于 cpu_relax():
在 i386 架构中,cpu_relax() 的实现是:
c
// include/asm-i386/processor.h:587-592
static inline void rep_nop(void)
{
__asm__ __volatile__("rep;nop": : :"memory");
}
#define cpu_relax() rep_nop()
rep;nop 指令的作用:
- 这是 x86 的
PAUSE指令(在较新的 CPU 上) - 不会让出 CPU,仍然在同一个 CPU 上执行
- 提示 CPU 这是一个忙等待循环,可以降低 CPU 功耗
- 本质上仍然是忙等待,只是优化了循环
注意 :cpu_relax() 不会让出 CPU,它只是优化了忙等待循环。如果需要真正让出 CPU,应该使用 schedule() 或 cond_resched()。
5. enable_irq - 启用中断
函数原型
c
void enable_irq(unsigned int irq)
实现代码
c
// kernel/irq/manage.c:100-125
void enable_irq(unsigned int irq)
{
irq_desc_t *desc = irq_desc + irq;
unsigned long flags;
spin_lock_irqsave(&desc->lock, flags);
switch (desc->depth) {
case 0:
// 深度为 0 表示已经启用,不应该调用 enable_irq
WARN_ON(1);
break;
case 1: {
// 最后一次启用,真正启用中断
unsigned int status = desc->status & ~IRQ_DISABLED;
desc->status = status;
// 如果有挂起的中断,重新发送
if ((status & (IRQ_PENDING | IRQ_REPLAY)) == IRQ_PENDING) {
desc->status = status | IRQ_REPLAY;
hw_resend_irq(desc->handler, irq);
}
// 调用硬件相关的 enable 函数
desc->handler->enable(irq);
/* fall-through */
}
default:
// 减少嵌套深度
desc->depth--;
}
spin_unlock_irqrestore(&desc->lock, flags);
}
代码逻辑解释
-
嵌套计数:
desc->depth记录禁用的嵌套深度- 每次调用
enable_irq都会减少depth - 只有最后一次启用(
depth从 1 变为 0)时才真正启用硬件中断
-
处理挂起中断:
- 如果中断被禁用期间有中断发生,会设置
IRQ_PENDING标志 - 启用中断时,如果有挂起的中断,会重新发送中断
- 如果中断被禁用期间有中断发生,会设置
-
硬件操作:
- 清除
IRQ_DISABLED状态标志 - 调用硬件相关的
enable函数(如enable_8259A_irq)
- 清除
i386 架构的硬件实现
c
// arch/i386/kernel/i8259.c:104-116
void enable_8259A_irq(unsigned int irq)
{
unsigned int mask = ~(1 << irq);
unsigned long flags;
spin_lock_irqsave(&i8259A_lock, flags);
// 在缓存掩码中清除对应位
cached_irq_mask &= mask;
// 根据中断号选择主 PIC 或从 PIC
if (irq & 8)
outb(cached_slave_mask, PIC_SLAVE_IMR);
else
outb(cached_master_mask, PIC_MASTER_IMR);
spin_unlock_irqrestore(&i8259A_lock, flags);
}
硬件操作:
- 清除 IMR 寄存器中对应中断的屏蔽位
- 允许该中断线产生中断
6. 数据结构
irq_desc - 中断描述符
c
// include/linux/irq.h:61-70
typedef struct irq_desc {
hw_irq_controller *handler; // 硬件中断控制器
void *handler_data; // 控制器私有数据
struct irqaction *action; // 中断处理程序链表
unsigned int status; // 中断状态标志
unsigned int depth; // 嵌套禁用深度
unsigned int irq_count; // 中断计数
unsigned int irqs_unhandled; // 未处理的中断计数
spinlock_t lock; // 保护锁
} ____cacheline_aligned irq_desc_t;
irqaction - 中断处理程序
c
// include/linux/interrupt.h:36-45
struct irqaction {
irqreturn_t (*handler)(int, void *, struct pt_regs *); // 处理函数
unsigned long flags; // 标志(SA_SHIRQ 等)
cpumask_t mask; // CPU 掩码
const char *name; // 设备名称
void *dev_id; // 设备标识符
struct irqaction *next; // 下一个处理程序(共享中断)
int irq; // 中断号
struct proc_dir_entry *dir; // /proc 目录项
};
hw_interrupt_type - 硬件中断控制器
c
// include/linux/irq.h:41-50
struct hw_interrupt_type {
const char *typename; // 控制器类型名称
unsigned int (*startup)(unsigned int irq); // 启动中断
void (*shutdown)(unsigned int irq); // 关闭中断
void (*enable)(unsigned int irq); // 启用中断
void (*disable)(unsigned int irq); // 禁用中断
void (*ack)(unsigned int irq); // 确认中断
void (*end)(unsigned int irq); // 结束中断处理
void (*set_affinity)(unsigned int irq, cpumask_t dest); // 设置 CPU 亲和性
};
7. 关键概念
嵌套禁用/启用
-
disable_irq和enable_irq支持嵌套调用 -
使用
desc->depth计数,只有深度为 0 时才真正启用/禁用硬件 -
例如:
cdisable_irq(5); // depth = 1,禁用硬件 disable_irq(5); // depth = 2,不操作硬件 enable_irq(5); // depth = 1,不操作硬件 enable_irq(5); // depth = 0,启用硬件
中断共享
- 多个设备可以共享同一中断线(
SA_SHIRQ标志) - 每个设备必须提供唯一的
dev_id - 中断发生时,所有注册的处理程序都会被调用
- 处理程序需要检查是否是自己的设备产生的中断
中断状态标志
IRQ_DISABLED:中断被禁用IRQ_INPROGRESS:中断正在处理中IRQ_PENDING:中断挂起(禁用期间发生)IRQ_REPLAY:中断需要重新发送
8259A PIC 操作
- 主 PIC:控制 IRQ 0-7,端口 0x20(命令)、0x21(数据)
- 从 PIC:控制 IRQ 8-15,端口 0xA0(命令)、0xA1(数据)
- IMR(中断屏蔽寄存器):控制哪些中断被屏蔽
- EOI(中断结束):处理完中断后需要发送 EOI 信号
8. 使用示例
注册中断处理程序
c
static irqreturn_t my_interrupt_handler(int irq, void *dev_id, struct pt_regs *regs)
{
// 处理中断
return IRQ_HANDLED;
}
// 注册中断
if (request_irq(IRQ_NUM, my_interrupt_handler,
SA_INTERRUPT | SA_SHIRQ, "my_device", dev)) {
// 注册失败
return -EBUSY;
}
释放中断处理程序
c
// 释放中断
free_irq(IRQ_NUM, dev);
禁用/启用中断
c
// 禁用中断(等待完成)
disable_irq(IRQ_NUM);
// 执行关键代码...
// 启用中断
enable_irq(IRQ_NUM);
// 或者快速禁用(不等待)
disable_irq_nosync(IRQ_NUM);
// 执行关键代码...
enable_irq(IRQ_NUM);
9. no_irq_type 详解
什么是 no_irq_type?
no_irq_type 是一个特殊的"空"中断控制器类型,表示该中断线没有配置有效的中断控制器。
初始化状态
c
// kernel/irq/handle.c:31-37
irq_desc_t irq_desc[NR_IRQS] __cacheline_aligned = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.handler = &no_irq_type, // 初始化为"无控制器"
.lock = SPIN_LOCK_UNLOCKED
}
};
所有中断描述符在初始化时都设置为 no_irq_type。
i386 架构的初始化
c
// arch/i386/kernel/i8259.c:362-388
void __init init_ISA_irqs(void)
{
int i;
init_8259A(0); // 初始化 8259A PIC
for (i = 0; i < NR_IRQS; i++) {
irq_desc[i].status = IRQ_DISABLED;
irq_desc[i].action = NULL;
irq_desc[i].depth = 1;
if (i < 16) {
// IRQ 0-15:配置为使用 8259A PIC
irq_desc[i].handler = &i8259A_irq_type;
} else {
// IRQ 16+:保持为 no_irq_type
// 这些通常是 PCI 中断,需要按需配置
irq_desc[i].handler = &no_irq_type;
}
}
}
关键点:
- IRQ 0-15 :在初始化时配置为使用
i8259A_irq_type(8259A PIC) - IRQ 16+ :保持为
no_irq_type,直到被实际配置(通常是 PCI 中断)
setup_irq 中的检查(第 103-105 行)
c
// kernel/irq/manage.c:103-105
if (desc->handler == &no_irq_type)
return -ENOSYS;
这个检查的含义:
-
目的:确保该中断线已经配置了有效的中断控制器
-
如果为
no_irq_type:- 表示该中断线还没有被初始化
- 无法注册中断处理程序
- 返回
-ENOSYS(函数未实现/不支持)
-
常见场景:
- 尝试在未配置的中断线上注册处理程序
- 某些高编号的 PCI 中断可能还没有被配置
- 需要先配置中断控制器,然后才能注册处理程序
no_irq_type 的实现
c
// kernel/irq/handle.c:57-66
struct hw_interrupt_type no_irq_type = {
.typename = "none",
.startup = startup_none, // 空函数
.shutdown = shutdown_none, // 空函数
.enable = enable_none, // 空函数
.disable = disable_none, // 空函数
.ack = ack_none, // 调用 ack_bad_irq
.end = end_none, // 空函数
.set_affinity = NULL
};
特点:
- 所有操作函数都是空函数或错误处理函数
ack_none会调用ack_bad_irq(irq)报告错误- 表示该中断线不可用
总结 :no_irq_type 检查确保只有在已配置的中断线上才能注册处理程序,防止在无效的中断线上进行操作。
10. 总结
- request_irq:分配并注册中断处理程序,支持中断共享
- free_irq:释放中断处理程序,等待处理完成后再释放
- disable_irq_nosync:快速禁用中断,不等待处理完成
- disable_irq:禁用中断并等待处理完成
- enable_irq:启用中断,支持嵌套调用
这些函数提供了完整的中断管理机制,既支持单设备独占中断,也支持多设备共享中断,同时通过嵌套计数机制支持安全的嵌套禁用/启用操作。