tasklet的使用方法在之前也有讲过,但是不够全面,而且也仅仅知道怎么使用而已,现在看看被人的总结:
//初始化tasklet_struct结构体
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
//使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会很快运行。
void tasklet_enable(struct tasklet_struct *t);
//函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出
void tasklet_disable(struct tasklet_struct *t);
//和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU
void tasklet_disable_nosync(struct tasklet_struct *t);
//调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己
void tasklet_schedule(struct tasklet_struct *t);
//和tasklet_schedule类似,只是在更高优先级执行。
void tasklet_hi_schedule(struct tasklet_struct *t);
//确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。
void tasklet_kill(struct tasklet_struct *t);
这里就深入的看看其实现方法,现在先来一个tasklet的使用案例:
#include
#include
#include
#include
static void my_tasklet_function(unsigned long data);
char my_tasklet_data[]="my_tasklet_function was called.";
//1.1)动态定义结构体变量,用于动态初始化
struct tasklet_struct my_tasklet;
//2)定义tasklet工作函数
static void my_tasklet_function(unsigned long data)
{
printk("%s\r\n",(char *)data);
udelay(2000); //延时2ms,而不是休眠
}
static int __init tasklet_module_init(void)
{
printk("mondule insmod\r\n");
//1.2)动态初始化
tasklet_init(&my_tasklet, my_tasklet_function,(unsigned long) my_tasklet_data);
//3)调度tasklet
tasklet_schedule(&my_tasklet);
return 0;
}
static void __exit tasklet_cleanup(void)
{
printk("mondule remove\r\n");
//4)销毁tasklet
tasklet_kill(&my_tasklet);
}
module_init(tasklet_module_init);
module_exit(tasklet_cleanup);
MODULE_LICENSE("GPL");
所以我们只要弄明白下面的几个函数是怎么实现的就可以了:
struct tasklet_struct
{
struct tasklet_struct *next;//指针,用于建立tasklet_struct实例的链表,允许多个任务在排队
//表示任务当前的状态,一共有两种状态:
//TASKLET_STATE_SCHED:表示等待调度,注册tasklet的时候会初始化为该状态
//TASKLET_STATE_RUN:表示tasklet当前正在执行
unsigned long state;
atomic_t count;//原子锁计数器,用于同步tasklet_struct的修改
void (*func)(unsigned long);//最重要的成员:回调函数
unsigned long data;//回调函数的执行参数
};
主要成员就这么几个,next指针主要是把所有的tasklet_struct连起来,state表示tasklet_struct的状态是在排队还是在执行,count是原子锁计数器,他的作用是保护tasklet_struct不会被同时修改,func是回调的执行函数,data就是执行函数的参数。
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
//初始化tasklet_struct结构体成员
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
EXPORT_SYMBOL(tasklet_init);
可以看出tasklet_init的作用就是初始化tasklet_struct 结构体。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
//设置tasklet的状态为TASKLET_STATE_SCHED,使用__tasklet_schedule进行调度
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
__tasklet_schedule_common(t, &tasklet_vec,
TASKLET_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_schedule);
static void __tasklet_schedule_common(struct tasklet_struct *t,
struct tasklet_head __percpu *headp,
unsigned int softirq_nr)
{
struct tasklet_head *head;
unsigned long flags;
local_irq_save(flags);//保存中断状态寄存器并且关闭本地cpu中断
head = this_cpu_ptr(headp);//获取per cpu变量的线性地址
t->next = NULL;//初始化tasklet_struct的next指针
*head->tail = t;//把tasklet_struct放到cpu调度链表中
head->tail = &(t->next);//设置head->tail的地址,用于存放下一个tasklet_struct
raise_softirq_irqoff(softirq_nr);//唤醒第softirq_nr个softirq准备执行
local_irq_restore(flags);//恢复中断状态寄存器
}
调度函数的首先设置tasklet_struct的状态,然后最后调用__tasklet_schedule_common关闭本地cpu中断,把tasklet_struct 放到cpu的tasklet_head调度链表中,让cpu自己找到tasklet_struct并且调度其中的回调函数。
void tasklet_kill(struct tasklet_struct *t)
{
//如果在中断状态,则提醒一下
if (in_interrupt())
pr_notice("Attempt to kill tasklet from interrupt\n");
//等待调度完毕
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do {
yield();//让出cpu
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);//等待解锁
//清除tasklet_struct的状态
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
EXPORT_SYMBOL(tasklet_kill);
销毁函数就是在等待调度完毕后让出cpu,最后清除tasklet_struct 的状态。
看到这里有人就奇怪了,那到底是怎么调用到func函数的呢?
这就要说到linux中的软中断了,因为tasklet是通过软中断实现的,我们下面看看软中断的初始化函数把,函数在kernel/softirq.c文件中:
void __init softirq_init(void)
{
int cpu;
//初始化每个cpu的tasklet_vec链表
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;
}
//打开TASKLET_SOFTIRQ的软中断,中断回调函数为tasklet_action
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
//打开HI_SOFTIRQ的软中断,中断回调函数为tasklet_hi_action
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
软中断无非就是初始化每一个cpu的tasklet_vec链表,然后打开TASKLET_SOFTIRQ和HI_SOFTIRQ的软中断,我们先看看open_softirq是怎么打开软中断的:
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
其实只是把中断向量数组的action成员赋值上该中断的回调函数而已,那么每次该中断产生,就会去到回调函数执行了,是不是很简单呢。
我们再看看回调函数tasklet_action是怎么找到我们的tasklet_struct的:
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
static void tasklet_action_common(struct softirq_action *a,
struct tasklet_head *tl_head,
unsigned int softirq_nr)
{
struct tasklet_struct *list;
local_irq_disable();//关闭本地中断,禁止内核抢占
list = tl_head->head;//记录tasklet_head指针
tl_head->head = NULL;//赋值head,让链表为空
tl_head->tail = &tl_head->head;//赋值tail,让链表为空
local_irq_enable();//开启中断
//遍历tasklet链表,让链表上挂入的函数全部执行完成
while (list) {
//从链表上摘下每一个tasklet_struct结构体
struct tasklet_struct *t = list;
list = list->next;
//根据tasklet_struct的state成员,判断是否RUNING
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {//如果原子锁计数器为0,则可以执行
//执行前需要先改变state为RUNING
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
t->func(t->data);//真正运行user注册的tasklet函数的地方
tasklet_unlock(t);//运行完毕,清除RUNING状态
continue;
}
tasklet_unlock(t);//如果原子锁计数器不为0,清除RUNING状态
}
local_irq_disable();//关闭本地cpu中断
//把以上没有处理完的tasklet重新挂到tasklt_vec数组中对应的cpu上的链表
t->next = NULL;
*tl_head->tail = t;
tl_head->tail = &t->next;
__raise_softirq_irqoff(softirq_nr);//把本地CPU的TASKLET_SOFTIRQ标记挂起
local_irq_enable();//使能中断
}
}
在中断回调函数tasklet_action主要是调用tasklet_action_common来完成tasklet_struct的fun实现,首先还是关闭本地中断,禁止内核抢占,再取出tasklet_head链表并且清空tasklet_head链表,最后把中断开回来,然后再遍历tasklet链表,在while循环中执行fun函数,并且把没有处理的tasklet重新挂到tasklt_vec中。
如果还想知道软中断是怎么通知cpu执行的,请看下集。