tasklet有两个软中断代表: HI_SOFTIRQ 和 TASKLET_SOFTIRQ. 两者的区别是HI_SOFTIRQ 优先级高于 TASKLET_SOFTIRQ.
1. 创建tasklet
/* Tasklets --- multithreaded analogue of BHs.
Main feature differing them of generic softirqs: tasklet
is running only on one CPU simultaneously.
Main feature differing them of BHs: different tasklets
may be run simultaneously on different CPUs.
Properties:
* If tasklet_schedule() is called, then tasklet is guaranteed
to be executed on some cpu at least once after this.
* If the tasklet is already scheduled, but its excecution is still not
started, it will be executed only once.
* If this tasklet is already running on another CPU (or schedule is called
from tasklet itself), it is rescheduled for later.
* Tasklet is strictly serialized wrt itself, but not
wrt another tasklets. If client needs some intertask synchronization,
he makes it with spinlocks.
*/
struct tasklet_struct
{
struct tasklet_struct *next; //指针,指向下一个tasklet,用来创建tasklet_struct实例的链表。
unsigned long state; //任务当前的状态。
atomic_t count; //原子计数器,用于禁用已经调用的tasklet。
void (*func)(unsigned long); //这个最重要,指向一个函数地址,该函数的执行将被延期。
unsigned long data; //传递给 func 函数的参数。
};
TASKLET_STATE_SCHED --- 表示 tasklet 已经被调度,正准备投入运行。
TASKLET_STATE_RUN --- 表示 tasklet 正在运行,TASKLET_STATE_RUN 只有在多处理器的系统中 才会作为一种优化来使用;单处理器系统在任何时刻都知道单个tasklet 是不是正在运行。
--- count 是 tasklet 的引用计数. 如果它不为0,则 tasklet 被禁止,不允许执行;只有当 count = 0 时,tasklet 才被激活,并且设置为挂起状态(pending),该 tasklet 才能执行。
2. 调度tasklet, tasklet_schedule():
2.1 已调度的 tasklet (等同于被触发的软中断)存放于两个单处理器(per-cpu)的数据结构中:
--- tasklet_vec : 普通的 tasklet。
--- tasklet_hi_vec: 高优先级的tasklet。
这两个数据结构,相当于软中断的 softirq_vec[]数组, 只不过这两个数据结构是两个链表,定义如下:
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
/*
* Tasklets
*/
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
这两个数据结构都是由 tasklet_struct 结构体构成的链表,链表中每个 tasklet_struct 代表一个不同的 tasklet。2.2 tasklet 由 tasklet_schedule() 和 tasklet_hi_schedule() 函数进行调度。他们的参数是一个指向 tasklet_struct 结构的指针。
extern void FASTCALL(__tasklet_schedule(struct tasklet_struct *t));
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) //检查 tasklet 状态是否为TASKLET_STATE_SCHED,如果是,说明tasklet已经被调度过了,函数立即返回
__tasklet_schedule(t); //如果不是,则调用__tasklet_schedule()
}
如果设置了TASKLET_STATE_SCHED标志位,则注册结束,表示该tasklet注册过了。
void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags); //保存中断标志,并禁止本地中断
t->next = __get_cpu_var(tasklet_vec).list; //把需要调度的tasklet t 添加到每个处理器的tasklet_vec 或tasklet_hi_vec 链表上。
__get_cpu_var(tasklet_vec).list = t;
raise_softirq_irqoff(TASKLET_SOFTIRQ); //触发 tasklet 软中断,即调用do_softirq()
local_irq_restore(flags); //还原中断现场,并使能本地中断
}
3.执行tasklet
tasklet关联到TASKLET_SOFTIRQ软中断,调用raise_softirq(TASKLET_SOFTIRQ),即可在下一个适当的时机执行当前处理器的tasklet。
内核使用tasklet_action作为action函数:
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable(); //禁止本地中断,不需要保存中断状态,因为这里的代码总是被软中断调用,而且中断总是被激活的
list = __get_cpu_var(tasklet_vec).head; //检索当前处理器的tasklet_vec 或 tasklet_hi_vec链表.
__get_cpu_var(tasklet_vec).head = NULL; //将当前处理器上的该链表设置为NULL, 达到清空的效果
__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
local_irq_enable(); //允许响应中断。
while (list) { //遍历链表上的每个待处理的tasklet_struct
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) { //上锁,就是t->state 状态置为 TASKLET_STATE_RUN, 见后面两个函数
if (!atomic_read(&t->count)) { //检查count 值是否为0,确保tasklet 没有被禁止,如果禁止了,则跳过,进入下一个tasklet_struct.
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data); //运行中断处理函数
tasklet_unlock(t);
continue;
}
tasklet_unlock(t); //解锁就是,清空 t->state 的 TASKLET_STATE_RUN 状态.
}
local_irq_disable();
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
一个tasklet只能在一个处理器上执行一次,但其他的tasklet可以并行运行,所以需要特定的tasklet锁,state状态用作锁变量。
在执行一个tasklet处理函数之前,内核使用tasklet_trylock() 检查tasklet的状态是否为TASKLET_STATE_RUN. 即检查是否已在另一个处理器上运行。
static inline int tasklet_trylock(struct tasklet_struct *t)
{
return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}
解tasklet锁:
static inline void tasklet_unlock(struct tasklet_struct *t)
{
smp_mb__before_clear_bit();
clear_bit(TASKLET_STATE_RUN, &(t)->state);
}
可以看出 tasklet 通过运用 HI_SOFTIRQ 和 TASKLET_SOFTIRQ 这两个软中断实现。
当一个tasklet被调度时,内核就会唤起这两个软中断中的一个,随后,该软中断会被特定函数do_irq()处理,执行所有已调度的tasklet。
4. 较高优先级的tasklet,HI_SOFTIRQ
除了普通的tasklet,内核还是用了另一种tasklet,它具有更高的优先级:
a. 它使用HI_SOFTIRQ 作为软中断,而不是TASKLET_SOFTIRQ,相关的action是tasklet_hi_action.
b. 注册的tasklet在CPU相关的变量tasklet_hi_vec中排队,这是使用tasklet_hi_schedule()完成的。
5. 使用 tasklet:
5.1 声明及初始化自己的 tasklet:
静态创建 tasklet:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } //引用计数为0,tasklet 处于激活状态。
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } //引用计数count = 1, tasklet处于禁止状态。
这两个宏都能静态的创建一个 tasklet_struct 结构,
例:
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);
动态创建 tasklet:struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
my_tasklet_handler, dev};
这样就创建了一个名为 my_tasklet,处理程序为tasklet_handler,并且是已被激活了的tasklet。
当处理程序被调用时, dev就会被传递给 tasklet_handler 函数。
还可以使用 tasklet_init(),来动态的初始化 tasklet_struct:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
如 tasklet_init(t, tasklet_handler, dev); //动态的创建一个tasklet。5.2 编写 tasklet 处理函数:
void tasklet_handler(unsigned long data);
因为是靠软中断实现的,所以tasklet 不能睡眠,这意味着不能再tasklet 中使用信号量或者其他阻塞式的函数。
由于tasklet运行时,允许响应中断,所以如果tasklet和中断处理函数之间共享了某些数据的话,必须进行适当的锁保护。
同一个tasklet绝不会两个CPU上同时执行,这点和软中断不一样,但是两个不同的tasklet可以在两个处理器上同时执行。
如果一个tasklet和其他的tasklet或者软中断共享了数据,必须进行适当的锁保护。如spin_lock()
5.3 调度 tasklet --- tasklet_schedule()
例:
tasklet_schedule(&my_tasklet); //把 my_tasklet 标记为挂起pending。
中断中使用tasklet_schedule时,记住,中断服务程序的返回值要改成: IRQ_NONE
中断服务程序结束,返回值如下:
/*
* For 2.4.x compatibility, 2.4.x can use
*
* typedef void irqreturn_t;
* #define IRQ_NONE
* #define IRQ_HANDLED
* #define IRQ_RETVAL(x)
*
* To mix old-style and new-style irq handler returns.
*
* IRQ_NONE means we didn't handle it.
* IRQ_HANDLED means that we did have a valid interrupt and handled it.
* IRQ_RETVAL(x) selects on the two depending on x being non-zero (for handled)
*/
typedef int irqreturn_t;
#define IRQ_NONE (0)
#define IRQ_HANDLED (1)
#define IRQ_RETVAL(x) ((x) != 0)
IRQ_NONE 才会让下半部去运行,如果写了IRQ_HANDLED, 下半部就不会运行咯。禁止某个指定的tasklet --- tasklet_disable(),定义如下:
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count); //count + 1 to diasble tasklet
smp_mb__after_atomic_inc();
}
激活一个tasklet --- tasklet_enable():
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
从挂起的队列中去掉一个tasklet --- tasklet_kill():
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do {
yield();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
EXPORT_SYMBOL(tasklet_kill);