linux tasklet 的分析与使用
目录
- [linux tasklet 的分析与使用](#linux tasklet 的分析与使用)
-
- [tasklet 源码分析](#tasklet 源码分析)
- [tasklet_shedule 调度的分析](#tasklet_shedule 调度的分析)
- [tasklet 执行](#tasklet 执行)
- [tasklet 使用简单示例](#tasklet 使用简单示例)
- 结论
tasklet 是利用软中断实现的一种下半部机制,本质上是软中断的一种变种,运行在中断上下文中.
有关于软中断的分析,可以参考之前的文章,有详细的分析。
tasklet 源码分析
interrupt.h 文件下,tasklet_struct结构体见下面代码,代码中有注释说明;
c
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
struct tasklet_struct
{
struct tasklet_struct *next; //多个tasklet 串成一个链表
unsigned long state; // 表示调试状态,见上面的枚举 TASKLET_STATE_SCHED
atomic_t count; // 0 表示tasklet 处理激活状态; 不为0表示tasklet 被禁止,不允许执行
void (*func)(unsigned long); // 处理函数
unsigned long data; //处理函数的数据
};
每个cpu维护两个tasklet 链表,在softirq.c中 tasklet_vec 和tasklet_hi_vec;
tasklet_vec 在软中断的优先级是6,见软中断文章中的枚举,tasklet_hi_vec的优先级是0
在softirq.c 中函数softirq_init 使用,而softirq_init 又被系统启动时调用。
c
/*
* Tasklets
*/
struct tasklet_head {
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
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);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
系统启动时调用
c
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
.......
/* Trace events are available after this */
trace_init();
context_tracking_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
rcu_init_nohz();
init_timers();
hrtimers_init();
softirq_init(); //软中断初始化
timekeeping_init();
time_init();
......
}
同一个tasklet不会同时运行,所以不需要处理tasklet和它本身之间的锁定情况。但是两个tasklet之间共享数据都应该使用spin_lock()和spin_unlock()进行保护
tasklet_shedule 调度的分析
相关代码如下:
c
//interrupt.h
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
//softirq.c
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
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);
}
EXPORT_SYMBOL(__tasklet_schedule);
test_and_set_bit 函数(原子的)设置tasklet_struct->state 为TASKLET_STATE_SCHED 标志位,然后返回state旧的值,返回为true,说明该tasklet 已经被挂入到tasklet 链表中,返回为false,表示没有持入tasklet 链表,需要调度,调用 __tasklet_schedule。
__tasklet_schedule 把tasklet 挂入 task_vec链表中 ; __tasklet_schedule 调用后不会立刻执行tasklet (从源码中可以看出 tasklet_schedule 只是对tasklet_struct 结构体设置 TASKLET_STATE_SCHED 标志位,只要tasklet 还没有执行,驱动程序多次调用tasklet_schedule也不起作用),需要等到软中断执行时才有机会运行,tasklet 挂在哪个cpu的tasklet_vec链表,那么哪个cpu的软中断来执行。
关于test_and_set_bit 的实现
c
//在arch/arm/include/asm/bitops.h
#define ATOMIC_BITOP(name,nr,p) _##name(nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)
转换后为_test_and_change_bit 的函数,该函数是汇编写的,源码如下
c
//arch/arm/lib
#include <linux/linkage.h>
#include <asm/assembler.h>
#include "bitops.h"
.text
testop _test_and_change_bit, eor, str
testop 为汇编的宏,代码在 arch/arm/lib/bitops.h
c
/**
* testop - implement a test_and_xxx_bit operation.
* @instr: operational instruction
* @store: store instruction
*
* Note: we can trivially conditionalise the store instruction
* to avoid dirtying the data cache.
*/
.macro testop, name, instr, store
ENTRY( \name )
UNWIND( .fnstart )
ands ip, r1, #3
strneb r1, [ip] @ assert word-aligned
and r3, r0, #31
mov r0, r0, lsr #5
save_and_disable_irqs ip
ldr r2, [r1, r0, lsl #2]!
mov r0, #1
tst r2, r0, lsl r3
\instr r2, r2, r0, lsl r3
\store r2, [r1]
moveq r0, #0
restore_irqs ip
ret lr
UNWIND( .fnend )
ENDPROC(\name )
.endm
tasklet 执行
软中断执行时会按照软中断状态 __softirq_pending 来依次执行pending 状态的软中断,当执行到TASKLET_SOFTIRQ类型软中断时,会调用 tasklet_action
执行的链条:exit_irq -> __do_softirq -> tasklet_action
c
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
/*
在关中断的情况下,读取tasklet_vec链表到临时链表list中,
并重新初始化tasklet_vec 链表
*/
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;
/*
tasklet_trylock 函数设计成一个锁,如果tasklet 已经处于RUNNING状态,即被设置了TASKLET_STATE_RUN标志位,
tasklet_trylock返回false,表示不能获取该锁
*/
if (tasklet_trylock(t)) {
//检测count 计数是否为0 ,为0则表示tasklet 处理可执行状态
//tasklet_disable 影响的就是count
if (!atomic_read(&t->count)) {
// 清除调试标志TASKLET_STATE_SCHED
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
//tasklet 的执行函数
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
//如果tasklet 已经在其它CPU上执行的情况下,tasklet 返回false,这时会把tasklet
//重新挂入当前CPU的tasklet_vec链表中,等待下一次触发TASKLET_SOFTIRQ类型的软中断才会被执行
local_irq_disable();
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();
}
}
static inline int tasklet_trylock(struct tasklet_struct *t)
{
return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
//见if (!atomic_read(&t->count)) 前面注释所产生的影响
atomic_inc(&t->count);
smp_mb__after_atomic();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
如上述注释中所写,tasklet_trylock 保证了同一个tasklet 只能在一个CPU上运行,不能在多cpu上并行,这与之前文章讲软中断并行不一样,这是tasklet 与其它softirq的差别
tasklet_trylock 函数设计成一个锁,如果tasklet
已经处于RUNNING状态,即被设置了TASKLET_STATE_RUN标志位,tasklet_trylock返回false,表示不能获取该锁
tasklet 使用简单示例
c
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
char tasklet_data[] = "We use a string; but it could be pointer to a structure";
void tasklet_function(unsigned long data)
{
printk("%s\n", (char *)data);
return;
}
DECLARE_TASKLET(my_tasklet, tasklet_function, (unsigned long)tasklet_data);
static int __init my_init(void)
{
//tasklet_disable(&my_tasklet);
//printk("tasklet_disable\n");
tasklet_schedule(&my_tasklet);
printk("tasklet example\n");
return 0;
}
void my_exit(void)
{
tasklet_kill(&my_tasklet);
printk("tasklet example cleanup\n");
return;
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("null");
MODULE_LICENSE("GPL");
结论
tasklet 是利用软中断实现的一种下半部机制,本质上是软中断的一种变种,运行在中断上下文中.