中断tasklet 小实验,初步理解tasklet 使用
文章目录
tasklet 概念
在 Linux 内核驱动开发中,Tasklet 是一种常用的中断下半部(Bottom Half)机制,适用于延迟执行非紧急的中断处理任务。它运行在软中断上下文,不能睡眠,但能保证同一 Tasklet 不会在多个 CPU 上并发行,适合处理轻量级的中断后续工作。
TaskLet 参考资料
Linux 中断下半部tasklet
linux kernel的中断子系统之(九):tasklet
Linux内核中的软中断、tasklet和工作队列详解(超详细~)
Linux下中断机制之tasklet执行过程(详细)总结
【原创】Linux中断子系统(三)-softirq和tasklet
一、Tasklet 的关键特性
-
基于软中断
Tasklet 是通过软中断(HI_SOFTIRQ 或 TASKLET_SOFTIRQ)实现的,优先级低于硬件中断,但高于普通进程。 -
原子性调度
Tasklet 的调度(通过 tasklet_schedule())必须在原子上下文中(如硬件中断、软中断或自旋锁保护的代码中)。 -
串行执行
同一个 Tasklet 实例不会在多个 CPU 上并发执行,避免了竞态条件。 -
不可睡眠
因为运行在软中断上下文,Tasklet 不能调用可能睡眠的函数(如 kmalloc(…, GFP_KERNEL))
二、Tasklet 的主要 API 方法
函数/宏 | 说明 |
---|---|
DECLARE_TASKLET(name, func, data) | 静态定义 Tasklet,并绑定回调函数 |
DECLARE_TASKLET_DISABLED(name, func, data) | 定义 Tasklet,但初始状态为 禁用 |
tasklet_init(t, func, data) | 动态初始化 Tasklet |
tasklet_schedule(t) | 调度 Tasklet(在软中断上下文中执行) |
tasklet_hi_schedule(t) | 以 更高优先级(HI_SOFTIRQ)调度 Tasklet |
tasklet_disable(t) | 临时禁用 Tasklet(可嵌套调用) |
tasklet_enable(t) | 重新启用 Tasklet |
tasklet_kill(t) | 强制终止 Tasklet,确保不再运行 |
方法说明如上,核心常用的方法我理解又三个:
动态初始化 tasklet_init -> 调度执行task:tasklet_schedule -> 驱动卸载时候 终止:tasklet_kill
后面再具体讲一讲 核心方法和使用
三、Tasklet 的使用步骤
Tasklet 定义
定义tasklet 分为静态定义和动态定义,如下:
静态定义创建一个tasklet
#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ...
} /
/ 静态初始化 tasklet
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, 0);
// 驱动程序的其他代码
my_tasklet 是 tasklet 的名称, my_tasklet_handler 是 tasklet 的处理函数, 0是传递给处理函数的参数。 但是需要注意的是, 使用 DECLARE_TASKLET 静态初始化的 tasklet无法在运行时动态销毁, 因此在不需要 tasklet 时, 应该避免使用此方法。 如果需要在运行时销毁 tasklet, 应使用 tasklet_init 和 tasklet_kill 函数进行动态初始化和销毁, 接下来我们来学习动态初始化函数
动态定义创建一个tasklet
函数原型:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
t 是指向 tasklet 结构体的指针, func 是 tasklet 的处理函数, data 是传递给处理函数的参数
示例如下:
#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ...
} /
/ 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 驱动程序的其他代码
我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。 然后, 声明了一个名为 my_tasklet 的 tasklet 结构体。 接下来, 通过调用 tasklet_init 函数, 进行动态初始化。
通过使用 tasklet_init 函数, 我们可以在运行时动态创建和初始化 tasklet。 这样, 我们可以根据需要灵活地管理和控制 tasklet 的生命周期。 在不再需要 tasklet 时, 可以使用 tasklet_kill函数进行销毁, 以释放相关资源
Tasklet - 调度
当你的tasklet 已经静态或者动态初始化好了之后,是需要调度才能执行起来的。 可以使用tasklet_schedule 函数来调度(触发) 一个已经初始化的 tasklet执行。
函数原型如下:
void tasklet_schedule(struct tasklet_struct *t);
其中, t 是指向 tasklet 结构体的指针。
示例如下:使用 tasklet_schedule 函数来调度 tasklet 执行
#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ...
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 调度 tasklet 执行
tasklet_schedule(&my_tasklet);
// 驱动程序的其他代码
在上述示例中, 我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。 然后, 声明了一个名为 my_tasklet 的 tasklet 结构体, 并使用 tasklet_init 函数对其进行初始化。 最后, 通过调用 tasklet_schedule 函数, 我们调度(触发) 了 my_tasklet 的执行。
需要注意的是, 调度 tasklet 只是将 tasklet 标记为需要执行, 并不会立即执行 tasklet 的处理函数。 实际的执行时间取决于内核的调度和处理机制
四、Tasklet 驱动案例
实验代码如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
// #include <linux/delay.h>
int irq;
struct tasklet_struct mytasklet;
// 定义tasklet处理函数
void mytasklet_func(unsigned long data)
{
printk("data is %ld\n", data);
// msleep(3000);
}
// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
tasklet_schedule(&mytasklet); // 调度tasklet执行
return IRQ_RETVAL(IRQ_HANDLED);
}
// 模块初始化函数
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将GPIO转换为中断号
printk("irq is %d\n", irq);
// 请求中断
ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
// 初始化tasklet
tasklet_init(&mytasklet, mytasklet_func, 1);
return 0;
}
// 模块退出函数
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL);
tasklet_kill(&mytasklet); // 销毁tasklet
printk("bye bye\n");
}
module_init(interrupt_irq_init); // 指定模块的初始化函数
module_exit(interrupt_irq_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("fangchen"); // 模块的作者
驱动程序流程分析
-
中断三大步骤:驱动中断核心点:gpio 转中断号(gpio_to_irq) -> 请求中断(request_irq) ->释放中断(free_irq)。
-
请求中断时候 声明了 标志位 IRQF_TRIGGER_RISING: 上升沿触发方式, 表示中断在信号上升沿时触发。
-
初始化tasklet: tasklet_init(&mytasklet, mytasklet_func, 1); 提供了task 是需要之前定义的,同步绑定了tasklet 回调函数 mytasklet_func
-
请求中断,中断函数回调 test_interrupt 方法,在这个函数里面去调度tasklet。 这里我们再次思考一下 tasklet 定义:
tasklet 是一种特殊的软中断机制, 被广泛用于处理中断下文相关的任务
那是不是可以这么理解:申请中断中绑定了中断回调函数,这个函数是中断上文,中断上文中调度的tasklet 就是中断下文。 为什么叫下文? 它只是在中断上文调度 ,但调度不是立即执行的,而且tasklet 里面是可以延时处理的,所以理解下 tasklet 和 中断下文的意思。
实际驱动实验效果如下
五、Tasklet 的适用场景-限制
场景
- 轻量级中断下半部:如数据处理、状态更新等。
- 需要串行执行的任务:避免多 CPU 并发问题。
- 替代 SoftIRQ:如果不想处理复杂的并发问题(SoftIRQ 需要自己处理多 CPU 竞争)。
限制
- 不能睡眠(因为运行在软中断上下文)。
- 可能影响实时性(如果 Tasklet 运行时间过长,会延迟其他软中断)。
总结
- 初步了解中断机制里面的 tasklet, 这里了解task 为什么说是中断下文
- 从其它语言程序角度和仅从程序流程角度来看,在申请中断绑定的中断回调函数里面 调度 tasklet,仅从字面来看就是回调一个方法,但是这个task内部是内核来调度的而已。 本质理解还是一个机制,扔给系统去调度。