中断知识点-中断线程化
文章目录
前言
首先还是特别有必要从概念上来理解这个知识点,如果概念都没有理解清除,何从去理解这个知识点存在的意义呢?
我自己看很多文档,特别第一次看的时候,都是云里雾里,很难理解这到底是个什么东西,当我自己熟悉了理解了发现如此简单,一个简单机制而已。
这里简单理解下:
- 前面我们了解的知识点:中断触发了 中断函数,可以理解为中断上文。 那么中断下文可以通过 tasklet - 共享queue-自定义queue-queue队列管理 来实现延时阻塞处理。
- 那么 中断线程话机制是:在中断函数中断上文的同步触发的情况下,中断下文基于内核机制在线程或者进程中同步处理。甚至中断上文触发函数都不要了 设为null,直接触发中断下文的 中断线程化机制 处理 延时、阻塞业务。
下面先看一下程序,加深理解,简单的驱动中断现成化程序
参考资料
Linux中断线程化
中断的线程化处理
中断线程化,洞悉万象-从理论抽象级到代码实现级
添加链接描述RK3568驱动指南|第五期-中断-第49章 中断线程化实验
一、中断线程化程序
中断线程化接口函数-request_threaded_irq
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id);
参数说明
- irq: 中断号, 表示要请求的中断线路。
- handler: 是在发生中断时首先要执行的处理程序, 非常类似于顶半部, 该函数最后会返
回 IRQ_WAKE_THREAD 来唤醒中断, 一般 handler 设为 NULL, 用系统提供的默认处理。 - thread_fn: 线程化的中断处理函数, 非常类似于底半部。 如果此处设置为 NULL 则表示
没有使用中断线程化。 - irqflags: 中断标志, 用于指定中断的属性和行为。
- devname: 中断的名称, 用于标识中断请求的设备。
- dev_id: 设备标识符, 用于传递给中断处理函数的参数。
函数返回值
函数返回一个整数值, 表示中断请求的结果。 如果中断请求成功, 返回值为 0, 否则返回
一个负数错误代码。
程序示例
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
int irq;
// 中断处理函数的底半部(线程化中断处理函数)
irqreturn_t test_work(int irq, void *args)
{
// 执行底半部的中断处理任务
msleep(1000);
printk("This is test_work\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
// 中断处理函数的顶半部
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
// 将中断处理工作推迟到底半部
return IRQ_WAKE_THREAD;
}
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将GPIO映射为中断号
printk("irq is %d\n", irq);
// 用于请求并注册一个线程化的中断处理函数
ret = request_threaded_irq(irq, test_interrupt, test_work, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
return 0;
}
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fangchen");
示例程序测试触摸,kernel 层日志如下:
二、驱动中中断队列与中断线程化的区别与比较
自己在看这个知识点时候,一直想前面task、队列、队列管理 机制都有了,这个线程化机制又是搞什么的,下面我们从不同角度看看。
基本概念对比
特性 | 中断队列 (传统中断处理) | 中断线程化 |
---|---|---|
执行上下文 | 上半部在中断上下文,下半部多种形式 | 全部在线程上下文(进程上下文)执行 |
可否睡眠 | 上半部不能,部分下半部机制可以 | 可以睡眠和调度 |
优先级 | 中断优先级由硬件决定 | 可调整线程优先级 |
实现复杂度 | 需要区分上下半部 | 编程模型更简单 |
这里就是需要理解的 执行上下文对比:中断线程化 直接在线程和进程中处理。
技术实现差异
中断队列实现
上半部处理:在request_irq()注册的处理函数中快速执行
下半部机制选择:
-
Softirq:内核预定义,执行快但不可睡眠
-
Tasklet:基于Softirq,同类型串行执行
-
Workqueue:内核线程执行,可睡眠
// 传统中断注册
request_irq(irq, handler, flags, name, dev);
// 工作队列使用示例
struct work_struct my_work;
INIT_WORK(&my_work, work_handler);
irqreturn_t handler(int irq, void *dev_id)
{
schedule_work(&my_work); // 调度工作队列
return IRQ_HANDLED;
}
中断线程化实现
// 线程化中断注册
request_threaded_irq(irq, primary_handler, threaded_handler, flags, name, dev);
// 示例:完全线程化(primary_handler为NULL)
request_threaded_irq(irq, NULL, threaded_handler, flags, name, dev);
性能特征比较
指标 | 中断队列 | 中断线程化 |
---|---|---|
响应延迟 | 上半部响应极快 | 有线程调度开销 |
吞吐量 | 高频率中断处理更高效 | 高频中断时线程切换开销大 |
确定性 | 受其他中断影响 | 优先级控制更好,确定性更高 |
CPU负载 | 直接占用中断CPU时间 | 可通过负载均衡分散到其他CPU |
适用场景分析
适合使用中断队列的场景
- 极高频率的中断(如网络数据包接收)
- 对延迟极其敏感的硬件控制
- 不需要复杂处理逻辑的中断
- 内核没有配置线程化支持的场景
适合使用中断线程化的场景
- 需要睡眠或等待资源的操作
- 实时性要求高的应用(PREEMPT_RT补丁)
- 复杂的中断处理逻辑
- 需要精确控制处理优先级的场景
- 用户空间驱动程序(如UIO)
三、知识扩展-irqreturn_t 返回值问题
irqreturn_t 是中断处理函数的返回值类型,用于指示中断是否被成功处理。以下是它的定义和具体应用场景
返回值类型
irqreturn_t 是一个枚举类型,定义在 <linux/interrupt.h> 中:
typedef enum irqreturn {
IRQ_NONE = (0 << 0), // 中断未处理
IRQ_HANDLED = (1 << 0), // 中断已处理
IRQ_WAKE_THREAD = (1 << 1), // 需要唤醒线程处理
} irqreturn_t;
各返回值的含义及应用场景
IRQ_NONE
- 含义 当前中断处理函数未处理该中断。
- 场景:共享中断线(多个设备共享同一个 IRQ 号)时,如果当前设备的中断未被触发,应返回 IRQ_NONE,以便内核继续调用其他设备的中断处理函数。
如下:
if (device_interrupt_condition_not_met) {
return IRQ_NONE; // 不是本设备的中断
}
IRQ_HANDLED
- 含义:中断已由当前处理函数成功处理。
- 场景:非共享中断线(独占 IRQ)时,直接返回 IRQ_HANDLED;共享中断线中,确认是本设备中断并完成处理后返回。
如下:
handle_device_irq();
return IRQ_HANDLED; // 中断已处理
IRQ_WAKE_THREAD
- 含义:中断需要由内核线程进一步处理(耗时操作)
- 场景:中断处理分为上半部(快速响应)和下半部(耗时处理)。如果上半部需要唤醒线程(如 tasklet 或工作队列)继续处理,则返回 IRQ_WAKE_THREAD;必须通过 request_threaded_irq() 注册中断处理函数时才能使用此返回值;
示例:
if (need_threaded_processing) {
return IRQ_WAKE_THREAD; // 唤醒线程处理下半部
}
注意事项
- 共享中断:必须检查中断来源,非本设备时返回 IRQ_NONE。
- 线程化中断:使用 request_threaded_irq() 注册时才能返回 IRQ_WAKE_THREAD。
- 性能考虑:上半部应尽可能快,耗时操作放到下半部(线程、tasklet 等)。
总结
- 梳理了解 驱动中断中的 中断线程化机制
- 对比task、软中断、队列 机制的不同,各自不同的使用场景
建议
- 纯性能考量:高频中断优先考虑传统中断队列
- 功能需求:需要复杂处理/阻塞操作时选择线程化
- 实时性要求:实时系统优先使用线程化中断
- 开发便捷性:线程化编程模型更简单直接
- 资源占用:线程化占用更多内存(每个中断一个线程栈)