rk3588/kernel-6.1 中断流程与上下半部机制分析
1. 中断处理整体框架
Linux 内核中断处理通常分为:
1 | 硬件中断 |
用户提到的经典模型:
1 | 硬件中断 -> ISR -> bottom half |
在 Linux 6.1 中可以展开为:
1 | 硬件 IRQ |
核心目录:
arch/arm64/kernel/entry-common.carch/arm64/kernel/irq.cdrivers/irqchip/irq-gic-v3.ckernel/irq/kernel/softirq.ckernel/workqueue.cinclude/linux/interrupt.hinclude/linux/irqdesc.hinclude/linux/irq.h
2. 中断核心文件作用
kernel/irq/Makefile 中核心模块:
1 | irqdesc.o IRQ 描述符分配和管理 |
重要结构:
struct irq_desc:一个 Linux IRQ 的核心描述符;struct irq_data:IRQ 与 irqchip/domain 的底层数据;struct irq_chip:中断控制器操作;struct irqaction:驱动注册的中断处理函数;struct irq_domain:硬件中断号 hwirq 到 Linux irq 的映射域。
3. ARM64 硬件中断入口
RK3588 是 ARM64 SoC,普通外设中断会从 ARM64 异常向量进入。
相关文件:
arch/arm64/kernel/entry-common.carch/arm64/kernel/irq.c
关键入口:
1 | el1h_64_irq_handler() |
其中:
irq_enter_rcu():进入 hardirq 上下文,更新 preempt count、RCU、统计;do_interrupt_handler():必要时切换到 IRQ stack 后调用 handler;irq_exit_rcu():退出 hardirq,并在合适时机处理 pending softirq;handle_arch_irq:架构 root IRQ handler,由具体 irqchip 初始化时设置。
arch/arm64/kernel/irq.c 中:
1 | init_IRQ() |
irqchip_init() 会根据设备树初始化 GICv3 等中断控制器。
4. GICv3 中断控制器入口
RK3588 ARM64 平台通常使用 GICv3。
相关文件:
drivers/irqchip/irq-gic-v3.c
初始化中:
1 | gic_init_bases() |
中断到来后:
1 | gic_handle_irq() |
其中:
- GIC IAR 寄存器给出硬件中断号
irqnr; generic_handle_domain_irq()通过irq_domain把 hwirq 转成 Linux irq;- 转换后进入 generic IRQ core。
这一步完成了:
1 | GIC hardware interrupt ID -> Linux virtual IRQ number |
5. irq_domain:硬件中断号到 Linux IRQ
相关文件:
kernel/irq/irqdomain.cinclude/linux/irqdomain.h
为什么需要 irq_domain?
不同中断控制器的硬件中断号可能重复,层级也不同。Linux 需要统一的逻辑 IRQ 编号给驱动使用。
典型映射:
1 | 设备树 interrupt specifier |
驱动中常见:
1 | irq = platform_get_irq(pdev, 0); |
platform_get_irq() 背后会通过 OF/ACPI 和 irq_domain 得到 Linux irq。
6. irq_desc:每个中断的核心状态
include/linux/irqdesc.h 中 struct irq_desc 是 generic IRQ layer 的核心。
重要字段:
1 | irq_data irqchip/domain 底层数据 |
中断核心分发时会找到对应 irq_desc:
1 | generic_handle_irq() |
desc->handle_irq 不是驱动 ISR,而是 flow handler,例如:
handle_level_irqhandle_edge_irqhandle_fasteoi_irqhandle_simple_irqhandle_percpu_irq
flow handler 根据中断类型负责 mask/ack/eoi/replay,然后再调用驱动 ISR。
7. irqaction:驱动注册的 ISR
include/linux/interrupt.h 中:
1 | struct irqaction { |
驱动通过以下接口注册:
1 | request_irq(irq, handler, flags, name, dev_id); |
request_irq() 实际只是:
1 | request_threaded_irq(irq, handler, NULL, flags, name, dev) |
因此普通 IRQ 和 threaded IRQ 共用注册框架。
8. request_irq 注册流程
相关文件:
kernel/irq/manage.c
注册流程:
1 | request_irq() |
request_threaded_irq() 关键点:
IRQF_SHARED必须提供非空dev_id;- 如果
handler == NULL且thread_fn != NULL,内核使用默认 primary handler; - 默认 primary handler 返回
IRQ_WAKE_THREAD; __setup_irq()会把irqaction挂到irq_desc->action;- 如果有
thread_fn,会创建irq/%d-%s内核线程。
驱动必须保证:
- 注册中断前硬件状态已经初始化好;
- ISR 能识别是否是自己的中断;
- level 中断必须清除设备中断源;
- shared IRQ 必须使用唯一
dev_id。
9. 硬中断分发流程
以 level IRQ 为例,kernel/irq/chip.c:
1 | handle_level_irq(desc) |
以 fasteoi IRQ 为例:
1 | handle_fasteoi_irq(desc) |
flow handler 的职责是:
- 根据中断类型执行 mask/ack/eoi;
- 防止重入和禁用状态下误处理;
- 更新统计;
- 调用
handle_irq_event(); - 结束时恢复中断线状态。
10. ISR / top half
真正调用驱动 ISR 的路径在 kernel/irq/handle.c:
1 | handle_irq_event() |
驱动 ISR 返回值:
1 | IRQ_NONE 不是我的中断,常见于 shared IRQ |
top half 特点:
- 运行在 hardirq context;
- 当前 CPU 本地中断通常关闭;
- 不能睡眠;
- 不能做慢速 I2C/SPI/MDIO 等可能睡眠操作;
- 不能持有锁后调用可能睡眠函数;
- 应尽快确认中断源、清中断、保存必要状态;
- 把耗时工作推给 bottom half。
典型 ISR:
1 | static irqreturn_t my_irq_handler(int irq, void *dev_id) |
11. threaded IRQ
threaded IRQ 是 Linux 推荐的下半部形式之一,尤其适合驱动里需要睡眠的中断处理。
注册:
1 | ret = devm_request_threaded_irq(dev, irq, |
流程:
1 | 硬件中断 |
kernel/irq/manage.c 中:
setup_irq_thread()创建irq/%d-%s线程;irq_thread()是 IRQ thread 主循环;irq_wait_for_interrupt()等待IRQTF_RUNTHREAD;irq_thread_fn()调用action->thread_fn();irq_finalize_oneshot()处理IRQF_ONESHOT的重新 unmask。
IRQF_ONESHOT 含义:
- top half 返回
IRQ_WAKE_THREAD后,中断线保持 masked; - 等
thread_fn执行完成后再 unmask; - 避免 level 中断在下半部未处理完时反复触发。
threaded IRQ 可以睡眠,因此适合:
- 需要访问 I2C/SPI 慢速外设;
- 需要 mutex;
- 需要较长处理流程;
- 需要与进程上下文 API 交互。
12. bottom half 机制概览
下半部用于把 ISR 中不适合执行的工作延后。
Linux 6.1 中常见 bottom half:
| 机制 | 上下文 | 能否睡眠 | 适合场景 |
|---|---|---|---|
| softirq | softirq context | 不能睡眠 | 网络收发、定时器、RCU |
| tasklet | softirq context | 不能睡眠 | 老式驱动延后处理 |
| workqueue | process context | 可以睡眠 | 需要慢速操作、复杂处理 |
| threaded IRQ | kernel thread | 可以睡眠 | 中断专属下半部 |
选择建议:
- 只需短小、原子、低延迟:softirq/tasklet;
- 需要睡眠或访问慢速总线:workqueue/threaded IRQ;
- 新驱动优先考虑 threaded IRQ 或 workqueue;
- tasklet 仍存在,但新代码应谨慎使用。
13. softirq 机制
相关文件:
kernel/softirq.cinclude/linux/interrupt.h
softirq 是静态注册的高性能下半部机制。
注册:
1 | open_softirq(nr, action); |
触发:
1 | raise_softirq(nr); |
执行路径:
1 | raise_softirq() |
如果 softirq 过多或当前不适合立即执行,会唤醒:
1 | ksoftirqd/N |
softirq.c 中:
1 | DEFINE_PER_CPU(struct task_struct *, ksoftirqd) |
常见 softirq 类型:
HI_SOFTIRQTIMER_SOFTIRQNET_TX_SOFTIRQNET_RX_SOFTIRQBLOCK_SOFTIRQIRQ_POLL_SOFTIRQTASKLET_SOFTIRQSCHED_SOFTIRQHRTIMER_SOFTIRQRCU_SOFTIRQ
14. tasklet 机制
tasklet 基于 softirq 实现。
相关文件:
kernel/softirq.cinclude/linux/interrupt.h
初始化:
1 | tasklet_init(&t, func, data); |
调度:
1 | tasklet_schedule(&t); |
内部流程:
1 | tasklet_schedule() |
tasklet 特点:
- 运行在 softirq context;
- 不能睡眠;
- 同一个 tasklet 同一时间不会在多个 CPU 并发运行;
- 不适合长时间处理;
- 驱动卸载时需要
tasklet_kill(); - 新驱动不建议大量使用 tasklet,优先考虑 threaded IRQ/workqueue。
典型使用:
1 | static void my_tasklet_func(unsigned long data) |
15. workqueue 机制
workqueue 是进程上下文下半部,适合需要睡眠的工作。
相关文件:
kernel/workqueue.cinclude/linux/workqueue.h
初始化:
1 | INIT_WORK(&d->work, my_work_func); |
调度:
1 | schedule_work(&d->work); |
执行路径:
1 | queue_work() |
系统 workqueue:
1 | system_wq events |
workqueue 特点:
- 运行在 kworker 进程上下文;
- 可以睡眠;
- 可以拿 mutex;
- 可以访问 I2C/SPI 等可能睡眠接口;
- 延迟较 tasklet/softirq 高;
- 需要在 remove/shutdown 中
cancel_work_sync()或flush_work()。
典型使用:
1 | static void my_work_func(struct work_struct *work) |
16. top half 与 bottom half 对比
16.1 top half
top half 即 ISR:
1 | irqaction->handler() |
适合做:
- 读取中断状态寄存器;
- 判断是否为本设备中断;
- ack/clear 设备中断;
- 保存少量状态;
- 唤醒 thread/tasklet/workqueue;
- 返回
IRQ_HANDLED或IRQ_WAKE_THREAD。
不适合做:
- 睡眠;
- 大量数据拷贝;
- 长时间循环;
- 复杂协议处理;
- I2C/SPI 同步访问;
- 等待锁或等待硬件完成。
16.2 bottom half
bottom half 适合做:
- 数据包处理;
- 协议解析;
- 唤醒等待队列;
- 调度下一次 DMA;
- 访问慢速总线;
- 上报 input/net/block 子系统;
- 较复杂状态机处理。
不同下半部选择:
1 | 不能睡眠 + 低延迟:tasklet/softirq |
17. 典型驱动中断设计
17.1 tasklet 模式
1 | ISR: |
适合:
- 处理很短;
- 不需要睡眠;
- 对延迟敏感。
17.2 workqueue 模式
1 | ISR: |
适合:
- 需要睡眠;
- 需要复杂状态机;
- 延迟要求不是极端严格。
17.3 threaded IRQ 模式
1 | top handler: |
适合:
- 中断处理天然分成“快速确认 + 慢速处理”;
- 需要睡眠;
- 希望一个 IRQ 对应一个内核线程;
- level 中断可配合
IRQF_ONESHOT。
18. 中断上下文限制
hardirq 和 softirq/tasklet 中:
- 不能调用会睡眠的函数;
- 不能使用普通 mutex;
- 不能执行可能阻塞的内存分配;
- 内存分配应使用
GFP_ATOMIC; - 持锁时间必须短;
- 不能直接访问用户空间;
- 不能做长时间 busy loop。
workqueue/threaded IRQ 中:
- 可以睡眠;
- 可以使用 mutex;
- 可以使用
GFP_KERNEL; - 仍需考虑和 remove/suspend 并发;
- 仍需避免长时间占用 worker。
19. 调试与观察
常用文件:
1 | /proc/interrupts |
常用命令:
1 | cat /proc/interrupts |
常用 ftrace 事件:
1 | irq:irq_handler_entry |
常见问题:
中断风暴
- 没有正确清设备中断源;
- level IRQ 没有让硬件线回到 inactive;
- shared IRQ handler 错误返回
IRQ_HANDLED。
ISR 中睡眠
- hardirq 中调用 mutex/I2C/SPI 同步接口;
- 应改为 threaded IRQ 或 workqueue。
remove 后崩溃
- 未
free_irq(); - 未
tasklet_kill(); - 未
cancel_work_sync(); - threaded IRQ/workqueue 仍访问已释放私有数据。
- 未
延迟过高
- ISR 过长;
- softirq 被 ksoftirqd 延后;
- workqueue 被长任务占用;
- IRQ affinity 不合理。
20. 总结
RK3588 Linux 6.1 的中断路径可以总结为:
1 | 硬件中断 |
上半部:
1 | top half = irqaction->handler() |
职责是快速确认、清中断、保存状态、唤醒下半部。
下半部:
1 | bottom half = |
其中:
tasklet基于TASKLET_SOFTIRQ;workqueue基于kworker线程;threaded IRQ基于irq/%d-%s内核线程;irq_exit_rcu()是 hardirq 返回时触发 softirq 的关键点。
一句话概括:
Linux 中断上半部只做必须的快速硬件响应,下半部负责把耗时或可睡眠的工作延后到 softirq/tasklet/workqueue/threaded IRQ 中执行。
正在加载留言…