一、什么是 tasklet
在 Linux 系统里,Tasklet 是一个用来处理中断相关复杂任务的软件机制。它主要有两个特点:
- 安全性:在多核 CPU 环境下,同一时间每个 Tasklet 的任务只能在一个 CPU 核心上运行。这样就能避免多个 CPU 同时处理同一个任务导致的数据冲突问题。
- 限制:使用时要注意,Tasklet 执行的任务不能包含任何会让 CPU 暂停的函数(比如需要等待 I/O 或资源的操作)。如果违反这点,会导致系统异常。
简单理解
Tasklet 就像一个安全的任务调度员,负责在多核 CPU 之间协调任务执行,但它的任务必须是快速完成且不会卡住的。
在 Linux 内核中,tasklet 结构体的定义位于 include/linux/interrupt.h 头文件中。其原型如下:
c
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
bool use_callback;
union {
void (*func)(unsigned long data);
void (*callback)(struct tasklet_struct *t);
};
unsigned long data;
};1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
tasklet_struct 结构体包含以下成员:
- next:指向下一个 tasklet 的指针,用来把多个 tasklet 连成链表。
- state:表示 tasklet 的当前状态(比如是否正在运行或等待)。
- count:记录引用次数的计数器,确保在多个地方同时调度或取消 tasklet 时能正确处理。
- func:指向一个函数的指针,这个函数会在 tasklet 启动时被自动调用执行。
- data:传递给上述函数的具体参数值。
二、tasklet 相关接口函数
1、静态初始化函数
在 Linux 内核中,DECLARE_TASKLET 宏可以方便地完成 tasklet 的静态初始化设置。宏函数的原型如下:
c
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }1
2
2
一个 tasklet 包含三个参数:名称(name)、处理函数(func)和数据(data)。tasklet 在初始化时默认处于启用状态。
如果 tasklet 初始化函数为非使能状态,使用以下宏定义:
c
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }1
2
2
2、动态初始化函数
c
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);1
tasklet_init 函数需要三个参数:指向任务结构的指针 t、处理函数 func,以及要传递给该函数的数据 data。
3、关闭函数
c
void tasklet_disable(struct tasklet_struct *t);1
当关闭一个 tasklet 后,即使通过 tasklet_schedule 函数尝试触发它,它的处理函数也不会再执行。
4、使能函数
c
void tasklet_enable(struct tasklet_struct *t);1
注意
- 单纯启用 tasklet 并不会自动运行,必须显式调用
tasklet_schedule()才能启动 - 要暂停 tasklet 时,可以用
tasklet_disable()暂时阻止其运行 - 如果要永久停止并释放资源,必须用
tasklet_kill()彻底销毁该 tasklet
5、调度函数
c
void tasklet_schedule(struct tasklet_struct *t);1
注意
触发 tasklet 仅仅是给它打上"需要执行"的标记,并不会立刻运行它的处理函数。实际运行时间由系统内核的调度安排决定。
6、销毁函数
c
void tasklet_kill(struct tasklet_struct *t);1
关键步骤
- 停止
- 销毁
- 重新初始化(如需再次使用)
销毁前务必确认任务块已完全停止运行,否则可能引发严重错误。
三、实验代码
源代码下载
bash
git clone git@gitee.com:yangxuesong314/linux-driver.git1
- 若之前已 git 拉取代码可以忽略
- 代码位于:
linux-driver/04.中断及异常/01.tasklet实验
实验说明
- 硬件 IO 表:https://lceda001.feishu.cn/wiki/UN0pw7Y1KixKirkMh0rcP3Tandc
- 操作第七个管脚触发中断,对应 GPIO1_A4
- GPIO1_A4 →
1 * 32 + 0 * 8 + 4 = 36
驱动代码:
c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
int irq;
struct tasklet_struct mytasklet;
// 定义 tasklet 处理函数
void mytasklet_func(unsigned long data)
{
printk("data is %ld\n", data);
}
// 中断处理函数
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(36);
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_init(&mytasklet, mytasklet_func, 1);
return 0;
}
// 模块退出函数
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL);
tasklet_enable(&mytasklet);
tasklet_kill(&mytasklet);
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
四、实验截图
驱动加载:insmod interrupt.ko

拉高 GPIO,触发中断服务程序,打印如下:
