揭秘Linux中断处理的核心步骤

发布时间:2026/6/6 19:18:02

揭秘Linux中断处理的核心步骤 中断Linux 处理一个硬件中断的典型步骤1.设备触发中断硬件设备如网卡通过中断线向中断控制器发送信号。2.中断控制器转发控制器将中断信号转换为 IRQ 编号发送给目标 CPU。3.CPU 响应中断保存当前上下文/断点寄存器、程序计数器禁用可屏蔽中断置 IF0避免中断嵌套干扰根据中断向量号查 IDT跳转到内核中断入口函数如 do_IRQ()。4.内核中断分发do_IRQ() 根据 IRQ 编号找到对应的 中断描述符调用中断服务程序ISR处理具体设备逻辑。5.恢复上下文ISR 执行完毕后CPU 恢复现场重新启用中断IF1回到被打断的任务。防止ISR 执行时间过长将中断处理拆分为“上半部”和“下半部”上半部即 ISR必须原子执行不可阻塞、不可睡眠仅完成最紧急的工作如读取硬件寄存器状态、清除中断标志。不能msleep、kmallocGFP_KERNEL还有mutex_lock互斥锁、copy_to_user用户空间拷贝可能阻塞等会导致睡眠。下半部处理非紧急工作如网络协议解析、数据拷贝到用户空间可在中断启用时执行允许阻塞。Linux 提供三种下半部实现的机制1.软中断几乎不用只有对性能要求极高且自己写核心子系统时用2.Tasklet常用不可睡眠不用担心并发问题同一 tasklet 不会同时在两个 CPU 上跑/* 1. 定义 tasklet 处理函数参数为 unsigned long通常传指针 */ static void my_tasklet_func(unsigned long data) { struct my_device *dev (struct my_device *)data; /* 完成耗时但不阻塞的工作 */ } /* 2. 声明并初始化 tasklet静态方式 */ DECLARE_TASKLET(my_tasklet, my_tasklet_func, (unsigned long)my_dev); /* 3. 在上半部中断处理函数中调度 tasklet */ static irqreturn_t my_interrupt_handler(int irq, void *dev_id) { /* 原子上下文只做紧急操作 */ tasklet_schedule(my_tasklet); // 标记 tasklet 待运行 return IRQ_HANDLED; } /* 4. 模块卸载时杀掉 tasklet防止还在挂起 */ static void __exit my_exit(void) { tasklet_kill(my_tasklet); }3.工作队列需要睡眠才用// 定义 Workqueue 处理函数 static void my_work_handler(struct work_struct *work) { struct my_device *dev container_of(work, struct my_device, work); // 可调用阻塞函数如拷贝数据到用户空间 if (copy_to_user(dev-user_buf, dev-data, sizeof(dev-data))) { printk(KERN_ERR copy_to_user failed\n); } wake_up_interruptible(dev-wait_queue); // 唤醒用户态等待进程 } // 初始化 Workqueue模块初始化时 static struct my_device my_dev { .work WORK_INITIALIZER(my_work_handler), }; // 在 ISR 中触发 Workqueue tasklet_schedule(dev-tasklet);什么是中断上下文中断上下文是指 CPU 执行中断服务程序ISR时所处的运行环境。包括硬中断Top Half、软中断异常、陷入、tasklet Bottom Half 。中断上下文为什么不能睡眠、不能进程切换中断上下文不是一个可调度的实体没有进程的task_struct不参与内核调度。当中断发生时CPU 借用被中断进程A的内核栈和地址空间来执行中断服务程序ISR。如果睡眠会将 进程A的状态设为 阻塞调用schedule()让出 CPU。调度器会认为“进程A主动请求睡眠”于是将其移出运行队列转而调度其他进程。但中断上下文本身不在调度队列中永远不会被重新调度所以内核无法找到哪个进程来唤醒导致系统陷入死循环或完全卡死。中断服务函数为什么不能写太复杂阻塞其他中断很多单片机的中断优先级可配置但如果当前 ISR 执行时间过长同级或低优先级中断会被长期挂起导致响应延迟甚至丢失中断事件。破坏实时性系统中最紧急的事件必须在规定时间内处理复杂 ISR 会拖慢这些紧急事件的响应。造成数据丢失例如串口接收中断如果 ISR 处理时间过长下一个字节已经到来而接收寄存器没有被及时读取数据就会被覆盖溢出错误。最佳实践ISR 只做最必要的工作例如清除中断标志读取/保存硬件数据如从 UART 接收寄存器取出一个字节放入环形缓冲区设置一个标志位或释放一个信号量通知后台任务下半部来做后续处理耗时操作放到中断下半部如 Linux 中的 tasklet/工作队列或 RTOS 的任务级处理。裸机中可以设置一个标志在主循环中检查并执行耗时逻辑。中断处理完整流程┌─────────────────────────────────────────────────────────────────────────┐ │ 0. 中断请求IRQ │ │ 【硬件】外设通过中断控制器PIC/APIC向CPU发送电信号 │ │ └─ 中断号如键盘1时钟0 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 1. CPU检测到中断请求 │ │ 【硬件】CPU完成当前指令执行 → 检查中断标志位 │ │ └─ 满足条件如EFLAGS.IF1→ 响应中断 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 2. 硬件自动保存现场隐式操作 │ │ 【硬件】CPU自动执行以下操作 │ │ ├─ 关中断清除EFLAGS.IF防止嵌套中断 │ │ ├─ 保存断点EIP/PC到内核栈 │ │ ├─ 保存状态寄存器EFLAGS │ │ ├─ 保存段寄存器CS、SS、ESP │ │ └─ 切换到内核态CPL0 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 3. 查询中断描述符表IDT │ │ 【硬件内存】 │ │ ├─ 根据中断号 × 8 计算IDT表项偏移 │ │ ├─ 从IDTR寄存器获取IDT基地址 │ │ ├─ 读取IDT表项gate_desc8字节 │ │ │ └─ IDT存储位置内核空间如0xC0000000 │ │ │ 通过__attribute__((__section__(.data.idt)))定义 │ │ └─ 获取中断服务程序ISR入口地址 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 4. 跳转到中断服务程序ISR │ │ 【OS内核】 │ │ ├─ 从IDT获取的地址跳转到ISR入口 │ │ ├─ 软件保存现场保存通用寄存器到内核栈 │ │ └─ 进入【中断上下文】 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 5. 执行中断服务程序Top Half │ │ 【OS内核 - 中断上下文】 │ │ ├─ 快速处理关键任务如读取硬件寄存器、清中断标志 │ │ ├─ 保存必要数据到内存 │ │ ├─ 调度Bottom Half如tasklet、软中断 │ │ └─ 【限制】不能睡眠、不能访问用户空间、不能调用schedule() │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 6. Bottom Half处理可选 │ │ 【OS内核 - 可能在中断上下文或进程上下文】 │ │ ├─ 软中断softirq仍在中断上下文但优先级低于硬中断 │ │ ├─ Tasklet基于软中断仍在中断上下文 │ │ └─ 工作队列workqueue在内核线程中执行处于进程上下文 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 7. 恢复现场 │ │ 【OS内核】 │ │ ├─ 弹出之前保存的通用寄存器 │ │ ├─ 恢复栈指针、段寄存器 │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────────┐ │ 8. 中断返回IRET │ │ 【硬件】CPU执行IRET指令 │ │ ├─ 恢复EFLAGS重新开中断 │ │ ├─ 恢复CS:EIP返回被中断的指令 │ │ └─ 返回到被中断的程序继续执行 │ └─────────────────────────────────────────────────────────────────────────┘一个小例子__interrupt关键字去定义了一个中断服务子程序(ISR)请评论一下这段代码__interrupt double compute_area (double radius) { double area PI * radius * radius; printf(\nArea %f, area); return area; }ISR 不能返回一个值。必须声明为void中断由硬件触发没有调用者接收返回值。ISR 不能传递参数。中断是异步触发的没有调用者传递参数浮点一般都是不可重入的会破坏浮点寄存器状态多数处理器在中断时不自动保存FPU寄存器。ISR应该是短而有效率的浮点运算耗时长。printf()是不可重入函数printf使用全局缓冲区和锁机制还可能导致死锁。而且是阻塞操作严重影响实时性执行时间长延长中断响应时间。ISR只做最必要的工作 读取硬件寄存器、设置标志位、清除中断标志。

相关新闻