【Linux中断】中断下半部-tasklet的原理与使用
tasklet特性
(1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行
(2)多个不同的类型的tasklet可以并行在多个CPU上
(3)软中断是静态分配的,在内核编译好后,就不能再改变了。但tasklet灵活很多,可以在运行时改变
tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行也行,tasklet就是最好的选择。所以也可以说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。
tasklet的数据结构
tasklet描述符
struct tasklet_struct
{
struct tasklet_struct *next; // 将多个tasklet链接成单向循环链表
unsigned long state; //TASKLET_STATE_SCHED(Tasklet is scheduled for execution) TASKLET_STATE_RUN(Tasklet is running (SMP only))
atomic_t count; // 0:激活tasklet 非0:禁用tasklet
void (*func)(unsigned long); // 用户自定义函数
unsigned long data; // 函数入参
};
tasklet链表
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); // 低优先级
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);// 高优先级
tasklet API接口
// 定义名字为name的非激活tasklet
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
// 定义名字为name的激活tasklet
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
//动态初始化tasklet
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
// 函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出
static inline void tasklet_disable(struct tasklet_struct *t);
// 函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出
static inline void tasklet_enable(struct tasklet_struct *t);
// 调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己
static inline void tasklet_schedule(struct tasklet_struct *t);
// 调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己
static inline void tasklet_hi_schedule(struct tasklet_struct *t);
// 调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己
void tasklet_kill(struct tasklet_struct *t);
tasklet原理
tasklet调度原理
static inline void tasklet_schedule(struct tasklet_struct *t)
{
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);
}
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);
head = this_cpu_ptr(headp);
t->next = NULL;
*head->tail = t;
head->tail = &(t->next); // 加入tasklet列表
raise_softirq_irqoff(softirq_nr); // 触发软中断
local_irq_restore(flags);
}
tasklet执行过程
TASKLET_SOFTIRQ对应执行函数为tasklet_action,HI_SOFTIRQ为tasklet_hi_action,以tasklet_action为例:
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;
tl_head->head = NULL;
tl_head->tail = &tl_head->head; // 获取tasklet链表
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
// 执行tasklet
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
// 如果t->count的值不等于0,说明这个tasklet在调度之后,被disable掉了,所以会将tasklet结构体重新放回到tasklet_vec链表,并重新调度TASKLET_SOFTIRQ软中断,在之后enable这个tasklet之后重新再执行它
local_irq_disable();
t->next = NULL;
*tl_head->tail = t;
tl_head->tail = &t->next;
__raise_softirq_irqoff(softirq_nr);
local_irq_enable();
}
}