RK3568中断-tasklet

中断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内部是内核来调度的而已。 本质理解还是一个机制,扔给系统去调度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值