tasklet 是中断下半部的一种实现机制,主要用于小任务处理,耗时较短、不能阻塞的任务,用tasklet处理较合适。对于耗时较长,可以用work queue(工作队列)来处理。

tasklet和内核定时器timer_list都是通过软中断方式来实现的。

一、tasklet结构体

中断下半部用结构体tasklet_struct来表示

1
2
3
4
5
6
7
8
9
10
#include <linux/interrupt.h>

struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};

其中,state有2位:
1)bit0:表示TASKLET_STATE_SCHED
等于1,表示已经执行了tasklet_schedule,该把tasklet放入队列了。tasklet_schedule会判断该位,如果已经等于1,那么它就不会再次把tasklet放入队列。

2)bit1:表示TASKLET_STATE_RUN
等于1,表示正在运行tasklet中的func函数。函数执行完毕后,内核会把该位清0。

count表示该tasklet是否使能:值0表示使能了,非0表示被禁止了。对于count非0的tasklet,func()不会被执行。

data 是传递给func()的参数。

二、初始化tasklet_strcut

静态初始化:

宏初始化tasklet_struct

1
2
3
4
5
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data };

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data };

使用DECLARE_TASKLET定义的tasklet结构体,它是使能的。
使用DECLARE_TASKLET_DISABLED定义的tasklet结构体,它是禁止的。使用之前要先调用tasklet_enable使能之。

动态初始化:

也可用函数tasklet_init()初始化tasklet结构体:
data是func的参数

1
2
tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);

三、使能/禁止tasklet

tasklet_enable将count加1;tasklet_disable将count减1。

1
2
3
static inline void tasklet_enable(struct tasklet_struct *t);  // 使能

static inline void tasklet_disable(struct tasklet_struct *t); // 禁止

四、调度tasklet

将tasklet放入链表,并设置它的TASKLET_STATE_SCHED状态为1。

1
static inline void tasklet_schedule(struct tasklet_struct *t);

五、kill tasklet

从链表中删除tasklet。如果一个tasklet未被调度,tasklet_kill会将它的TASKLET_STATE_SCHED状态清0;如果一个tasklet已被调度,tasklet_kill会等待它执行完毕,再把它的TASKLET_STATE_SCHED状态清0。
通常,在卸载驱动程序(module_exit)时,调用task_kill。

tasklet_kill_immediate 与tasklet_kill区别是,前者会立即移除tasklet,二不论tasklet是否处于TASKLET_STATE_SCHED状态。

1
2
3
void tasklet_kill(struct tasklet_struct *t); // 移除tasklet

void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu); // 立即移除tasklet

六、tasklet使用方法

先定义tasklet实例,需要使用时调用tasklet_schedule,驱动卸载前调用tasklet_kill。tasklet_schedule只是将tasklet放入内核队列,其func函数会在软中断执行过程中被调用。

七、tasklet内部实现机制

前面讲过,tasklet是通过软中断实现,属于TASKLET_SOFTIRQ类型的软中断。入口函数tasklet_action。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// kernel/softirq.c

void __init softirq_init(void)
{
int cpu;

for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}

open_softirq(TASKLET_SOFTIRQ, tasklet_action); // 注册TASKLET_SOFTIRQ类型软中断(普通软中断)及其处理函数
open_softirq(HI_SOFTIRQ, tasklet_hi_action); // 高优先级软中断
}

在软中断的初始化(softirq_init)末尾,通过open_softirq注册TASKLET_SOFTIRQ类型的软中断(即tasklet)及其处理函数(即tasklet_action)。

驱动程序调用tasklet_schedule时,会设置tasket的state为TASKLET_STATE_SCHED,并把它放入某个链表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static inline void tasklet_schedule(struct tasklet_struct *t)
{
// 1. 如果未设置为SCHED,则设置为SCHED并放入队列
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) // 设置tasklet.state为TASKLET_STATE_SCHED
__tasklet_schedule(t);
}

void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;

local_irq_save(flags); // 保存中断
// 2. 放入队列
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
raise_softirq_irqoff(TASKLET_SOFTIRQ); // 唤醒中断, 会导致调用该类型软中断对应的处理函数
local_irq_restore(flags); // 恢复中断到flags状态
}
EXPORT_SYMBOL(__tasklet_schedule);

产生硬件中断时,讹你好处理完硬件中断后,会处理软中断。对于TASKLET_SOFTIRQ软中断,会调用tasklet_action函数。

执行过程是:从队列中找到tasklet,进行状态判断后执行func函数,从队列中删除tasklet。
可知:
1)tasklet_schedule 调度tasklet时,其中的函数并不会立即执行,而只是把tasklet放入队列;
2)调用一次tasklet_schedule,只会导致tasklet的函数被执行一次;
3)如果tasklet的函数尚未执行,多次调用tasklet_schedule也是少的,只会放入队列一次。

普通软中断处理函数tasklet_action:

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
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;

local_irq_disable();
list = __this_cpu_read(tasklet_vec.head);
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
local_irq_enable();

while (list) {
struct tasklet_struct *t = list;

list = list->next; // 1. 从列表中去除每一项

if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
// 2. 判断:如果不是SCHED状态,就是有BUG
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
t->func(t->data); // 3. 执行tasklet的func
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}

local_irq_disable();
// 4. 从队列中取出
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));

__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}

描述文字
© 2024 mengchao666 开心地使用 Stellar 主题,星际地球第
收集了 62 颗星星 ✧ 共 83.3k 粒星尘,好像写完一本 鲁迅 的《呐喊》了啊