文章目录
中断与异常的区别
中断(Interrupt) 和 异常(Exception) 都会打断 CPU 的正常执行,但它们的来源和处理方式有所不同:
比较项 | 中断(Interrupt) | 异常(Exception) |
---|---|---|
产生来源 | 外部设备(如键盘、网络、磁盘) | 内部软件或 CPU |
是否同步 | 异步(由外部事件触发,不一定与当前指令执行相关) | 同步(发生在指令执行过程中,与指令相关) |
处理方式 | 由中断控制器(如 PIC 或 APIC)通知 CPU,然后调用中断处理程序 | 由 CPU 自动触发异常处理程序 |
触发时机 | 可能发生在指令执行的任何时刻 | 只有在执行指令时才会触发 |
典型示例 | 时钟中断、键盘中断、网络中断 | 除零错误、非法指令、缺页异常 |
例如:
- 当你按下键盘时,会触发键盘中断,CPU 需要暂停当前执行的程序,调用键盘驱动处理输入。
- 当一个程序执行
div 0
试图除以零时,CPU 会触发一个异常,让操作系统处理错误。
中断与 DMA 的区别
比较项 | DMA | 中断 |
---|---|---|
主要作用 | 让外设直接与内存交换数据,而不经过 CPU | 让 CPU 处理突发事件 |
是否需要 CPU 参与 | 不需要,数据传输完全由 DMA 控制 | 需要,CPU 需要暂停当前任务并执行中断处理 |
速度 | 高(直接操作内存) | 可能较慢(需要 CPU 介入) |
典型示例 | 磁盘控制器通过 DMA 把数据传输到内存 | 网络设备收到数据后通知 CPU |
总结:
- DMA 主要用于数据传输,例如:磁盘或网卡使用 DMA 直接把数据写入内存,不需要 CPU 逐字节读取。
- 中断主要用于通知 CPU 发生了某些事件,如网络设备接收到新数据时,会触发中断,让 CPU 读取数据包。
中断能否睡眠?下半部能否睡眠?
1. 中断处理程序不能睡眠
- 原因:中断发生时,CPU 处于 中断上下文,没有具体的进程。如果在中断处理中调用了导致阻塞的函数(如
schedule()
),则:- 进程调度系统不知道当前上下文对应哪个进程,无法正确恢复上下文。
- 可能会导致系统死锁或崩溃。
- 示例:
void irq_handler() { // 这会导致内核崩溃! schedule(); }
2. 下半部(SoftIRQ、Tasklet、Workqueue)
下半部类型 | 是否能睡眠 | 说明 |
---|---|---|
SoftIRQ | 否 | 仍然处于中断上下文,不允许睡眠 |
Tasklet | 否 | 与 SoftIRQ 类似,仍不能睡眠 |
Workqueue | 是 | 在内核线程中执行,可以睡眠 |
总结:
- 中断处理程序不能睡眠,否则会导致内核崩溃。
- SoftIRQ 和 Tasklet 不能睡眠,因为它们仍然运行在中断上下文中。
- Workqueue 可以睡眠,因为它们在一个独立的内核线程中运行。
中断处理注意点
在写中断服务程序(ISR,Interrupt Service Routine)时,需要注意以下关键点:
1. 快进快出
- 快速完成关键任务:ISR应该只做必要的工作,比如读取硬件寄存器、清除中断标志、存储关键数据等。
- 将复杂任务移至下半部:可以使用 工作队列(workqueue) 或 tasklet 来处理较复杂的任务,而ISR只执行简单、时间敏感的操作。
2. 避免阻塞
- 中断期间CPU不会进行进程切换,因此ISR 不能 执行 sleep、等待锁、I/O 操作 等可能导致阻塞的操作。
- 例如,在ISR中调用
mutex_lock()
是错误的,因为它可能导致死锁。 - 如果必须同步资源,可以使用 自旋锁(spinlock),但要小心避免死锁。
3. 正确返回值
- 在Linux中,ISR必须返回 IRQ_HANDLED 或 IRQ_NONE(位于
<linux/irqreturn.h>
)。 - 返回 IRQ_HANDLED 表示该中断已被正确处理,而 IRQ_NONE 则表示该中断不属于当前设备,可能需要进一步排查。
4. 如何处理大量任务
- 使用中断下半部机制(Deferred Processing):
- 软中断(SoftIRQ):适用于高吞吐量需求,如网络数据包处理。
- Tasklet:适用于优先级较高但不需要高吞吐量的任务,如调度一些紧急的任务处理。
- 工作队列(workqueue):适用于可以被内核线程调度的任务,适合耗时较长的任务处理。
- 示例代码(使用 Tasklet):
#include <linux/interrupt.h> void my_tasklet_func(unsigned long data); DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0); irqreturn_t my_irq_handler(int irq, void *dev_id) { // 仅做必要的中断处理 printk(KERN_INFO "Interrupt occurred\n"); // 调度 tasklet 进行后续处理 tasklet_schedule(&my_tasklet); return IRQ_HANDLED; } void my_tasklet_func(unsigned long data) { // 在下半部处理较复杂的任务 printk(KERN_INFO "Tasklet executed\n"); } static int __init my_module_init(void) { int irq = 19; // 假设是 IRQ19 request_irq(irq, my_irq_handler, IRQF_SHARED, "my_irq_device", NULL); return 0; } static void __exit my_module_exit(void) { free_irq(19, NULL); tasklet_kill(&my_tasklet); } module_init(my_module_init); module_exit(my_module_exit);
5. 避免竞态问题
- 使用自旋锁保护共享资源:
static spinlock_t my_lock; irqreturn_t my_irq_handler(int irq, void *dev_id) { unsigned long flags; spin_lock_irqsave(&my_lock, flags); // 关闭本地中断,并上锁 // 访问共享资源 spin_unlock_irqrestore(&my_lock, flags); // 解锁,并恢复中断 return IRQ_HANDLED; }
6. 中断线程化
- 对于较复杂的任务,可以将中断处理线程化(使用
request_threaded_irq()
)。 - 线程化的中断 允许进程调度,但会有一定的时延:
irqreturn_t my_irq_handler(int irq, void *dev_id) { return IRQ_WAKE_THREAD; // 让内核调度中断线程执行后续操作 } irqreturn_t my_irq_thread_fn(int irq, void *dev_id) { // 处理较复杂的任务 printk(KERN_INFO "Threaded IRQ handling\n"); return IRQ_HANDLED; } request_threaded_irq(irq, my_irq_handler, my_irq_thread_fn, IRQF_SHARED, "my_irq_device", NULL);
7. 清除中断标志
- 在某些设备中,如果不清除 中断标志位,中断可能会持续触发,导致系统卡死:
irqreturn_t my_irq_handler(int irq, void *dev_id) { iowrite32(0, INTERRUPT_STATUS_REGISTER); // 清除中断标志 return IRQ_HANDLED; }
总结
- 上半部(ISR)做 最少的事情,如读取数据、清除中断标志,然后 尽快退出。
- 下半部 通过 tasklet、工作队列 或 软中断 处理复杂任务。
- 避免阻塞,不要在ISR中调用
sleep()
或等待资源。 - 保护共享资源,使用 自旋锁(在中断上下文)。
- 中断线程化 适用于较复杂的任务,但可能会有一定延迟。
中断 vs. 轮询:效率比较
方式 | 适用场景 | 优势 | 劣势 |
---|---|---|---|
中断(Interrupt) | 低频请求设备(如键盘、鼠标、串口、传感器) | - CPU 不需要主动查询,减少不必要的 CPU 开销 - 适合低频请求设备 |
- 需要中断上下文处理,可能引入额外的调度开销 - 可能会影响实时性(高优先级任务打断低优先级任务) |
轮询(Polling) | 高频率、高吞吐设备(如高速网卡、DMA 设备、实时音视频处理) | - 对于高频设备,可以减少中断开销,提高吞吐量 - 控制简单,不涉及中断处理 |
- 占用 CPU 资源,可能导致其他任务无法执行 - 低效,尤其是在设备空闲时,CPU 仍然需要不断查询 |
应用场景
设备请求频率低 → 中断
- 适用于 低速设备,如键盘、鼠标、传感器、串口等。
- CPU 只有在 设备有数据 时才会被打断,中断处理时间短。
- 避免 CPU 不断轮询浪费资源。
设备请求频率高 → 轮询
- 适用于 高速数据传输设备,如 网络设备、硬盘控制器、音视频处理 等。
- 轮询可以减少中断频繁触发的开销,提高数据吞吐能力。
- 例如,Linux 内核中的 NAPI(New API)网络处理模式就是 轮询 + 中断混合 方案。
折中方案:中断 + 轮询
- 许多高性能设备(如网卡、存储设备)使用 中断触发 + 轮询 结合的方式:
- 初始阶段:中断触发 → 当有新数据时,触发中断通知 CPU。
- 高负载阶段:轮询处理 → 在一段时间内,CPU 进入轮询模式,提高数据处理效率。
- 示例:Linux NAPI 模式(用于网络驱动)
- 初始时 使用中断 监听网络数据包。
- 如果数据包流量很大,则改为 轮询模式,避免频繁触发中断。
- 数据处理完毕后,系统重新 切换回中断模式。
- 许多高性能设备(如网卡、存储设备)使用 中断触发 + 轮询 结合的方式:
总结
- 低频设备(键盘、鼠标、传感器) → 中断
- 高吞吐设备(网卡、存储、视频流) → 轮询
- 高负载设备(混合场景) → 中断 + 轮询(NAPI、Hybrid Polling)
为什么FIQ(Fast Interrupt Request)比 IRQ(Interrupt Request) 快
1. FIQ 具有更多的 Banked(专用)寄存器
- FIQ 模式 在 ARM 体系结构 下有 更多的专用寄存器:
- FIQ 模式 拥有 R8-R14 的专用 Banked 寄存器,同时还有 SPSR(Saved Program Status Register)。
- IRQ 模式 只有 R13(SP)、R14(LR)、SPSR 是 Banked 寄存器,而 R8-R12 需要手动保存。
为什么这会影响速度?
- 在 IRQ 处理程序 中,由于 R8-R12 没有 Banked 版本,所以在进入中断时,必须 手动保存这些寄存器,然后在中断退出时 恢复这些寄存器,这会增加 入栈和出栈的开销。
- FIQ 由于有专门的寄存器,在 模式切换时 CPU 自动保存/恢复,无需手动压栈/出栈,从而 减少了指令开销,提高了执行速度。
2. FIQ 拥有更高的优先级
- 在 ARM 处理器中,FIQ 优先级高于 IRQ:
- 如果 FIQ 和 IRQ 同时发生,CPU 会先响应 FIQ,然后再处理 IRQ。
- 如果正在处理 IRQ 时 FIQ 触发,那么 FIQ 会抢占 IRQ 立即执行,这保证了 FIQ 的 实时性 和 低延迟。
3. FIQ 处理过程中屏蔽了其他中断
当 CPU 进入 FIQ 模式 时:
- 所有 IRQ 中断被屏蔽(禁用)。
- 其他异常(如未定义指令异常、软件中断异常)也被屏蔽。
- 这意味着 FIQ 不会被其他中断或异常打断,能够在 更短的时间内完成处理。
反之,在 IRQ 模式 下:
- FIQ 仍然可以打断 IRQ,导致 IRQ 可能会被抢占,增加了中断处理的复杂度和上下文切换的时间。
4. FIQ 的入口地址使得它可以直接执行
- ARM 处理器的中断向量表地址:
- IRQ 入口地址:
0x18
- FIQ 入口地址:
0x1C
- IRQ 入口地址:
为什么这会影响速度?
IRQ 入口
0x18
只能放一条指令,通常是B
(Branch)指令,即 必须跳转到中断处理程序:0x18: B IRQ_Handler
- 由于 需要跳转,这 增加了一条额外的跳转指令,影响了中断的响应时间。
FIQ 入口
0x1C
之后没有其他中断向量表,可以 直接放入 FIQ 处理代码:0x1C: STMFD SP!, {R0-R4} ; 直接保存寄存器 LDR R0, =DATA ; 读取数据 STR R0, [R1] ; 处理数据 LDMFD SP!, {R0-R4} ; 恢复寄存器
- 这样 FIQ 处理程序可以直接在
0x1C
处执行,避免跳转,从而 进一步减少指令执行时间,提高中断响应速度。
- 这样 FIQ 处理程序可以直接在
总结
对比项 | FIQ(Fast Interrupt) | IRQ(Interrupt Request) |
---|---|---|
专用寄存器 | R8-R14 + SPSR(Banked 寄存器,不需要手动保存) | 只有 R13(SP)、R14(LR)、SPSR(R8-R12 需要手动保存) |
中断优先级 | FIQ 最高优先级,可以打断 IRQ | IRQ 低于 FIQ,可能被 FIQ 抢占 |
中断屏蔽 | 进入 FIQ 后,所有 IRQ 被屏蔽,不受其他中断影响 | 进入 IRQ 后,FIQ 仍然可以抢占 |
入口地址 | 0x1C,可以直接执行,无需跳转 | 0x18 只能放一条指令,需要跳转 |
执行速度 | 更快,适用于高优先级、低延迟任务 | 较慢,适用于一般设备 |
应用场景
FIQ 适用于:
- 高实时性场景(如 DMA 传输完成中断)
- 数据流处理(如高速 音频/视频数据 处理)
- 低延迟任务(如紧急错误处理)
IRQ 适用于:
- 普通设备中断(如键盘、鼠标、串口)
- 网络中断(如果流量不大)