tasklet原理Word下载.docx
《tasklet原理Word下载.docx》由会员分享,可在线阅读,更多相关《tasklet原理Word下载.docx(9页珍藏版)》请在冰豆网上搜索。
不关心如何从具体的网卡接收数据包,但是从所有的网卡接收的数据包都要经过内核协议栈的处理。
而且软中断比较“硬”——数量固定、编译时确定、操作函数必须可重入、需要慎重考虑锁的问题,不适合驱动直接调用,因此Linux内核为驱动直接提供了一种使用软中断的方法,就是tasklet。
二、tasklet数据结构
tasklet通过软中断实现,软中断中有两种类型属于tasklet,分别是级别最高的HI_SOFTIRQ和TASKLET_SOFTIRQ。
Linux内核采用两个PER_CPU的数组tasklet_vec[]和tasklet_hi_vec[]维护系统种的所有tasklet(kernel/softirq.c),分别维护TASKLET_SOFTIRQ级别和HI_SOFTIRQ级别的tasklet:
structtasklet_head
{
structtasklet_struct*head;
structtasklet_struct**tail;
};
staticDEFINE_PER_CPU(structtasklet_head,tasklet_vec);
staticDEFINE_PER_CPU(structtasklet_head,tasklet_hi_vec);
tasklet_vec
tasklet的核心结构体如下(include/linux/interrupt.h):
structtasklet_struct
structtasklet_struct*next;
unsignedlongstate;
atomic_tcount;
void(*func)(unsignedlong);
unsignedlongdata;
习惯上称之为tasklet描述符,func指针是具体的处理函数指针,data为可选参数,state表示该tasklet的状态,分别使用不同的bit表示两个状态:
TASKLET_STATE_SCHED和TASKLET_STATE_RUN:
TASKLET_STATE_SCHED置位表示已经被调度(挂起),也意味着tasklet描述符被插入到了tasklet_vec和tasklet_hi_vec数组的其中一个链表中,可以被执行。
TASKLET_STATE_RUN置位表示该tasklet正在某个CPU上执行,单个处理器系统上并不校验该标志,因为没必要检查特定的tasklet是否正在运行。
count为原子计数器,用于禁用已经调度的tasklet,如果该值不为0,则不予以执行。
三、tasklet操作接口
tasklet对驱动开放的常用操作包括:
初始化,tasklet_init(),初始化一个tasklet描述符。
调度,tasklet_schedule()和tasklet_hi_schedule(),将taslet置位TASKLET_STATE_SCHED,并尝试激活所在的软中断。
禁用/启动,tasklet_disable_nosync()、tasklet_disable()、task_enable(),通过count计数器实现。
执行,tasklet_action()和tasklet_hi_action(),具体的执行软中断。
杀死,tasklet_kill(),。
。
tasklet_int()函数实现如下(kernel/softirq.c):
voidtasklet_init(structtasklet_struct*t,
void(*func)(unsignedlong),unsignedlongdata)
t->
next=NULL;
state=0;
atomic_set(&
count,0);
func=func;
data=data;
}
tasklet_schedule()函数与tasklet_hi_schedule()函数的实现很类似,这里只列tasklet_schedule()函数的实现(kernel/softirq.c),都挺明白就不描述了:
staticinlinevoidtasklet_schedule(structtasklet_struct*t)
if(!
test_and_set_bit(TASKLET_STATE_SCHED,&
state))
__tasklet_schedule(t);
void__tasklet_schedule(structtasklet_struct*t)
unsignedlongflags;
local_irq_save(flags);
*__this_cpu_read(tasklet_vec.tail)=t;
__this_cpu_write(tasklet_vec.tail,&
(t->
next));
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
tasklet_disable()函数、task_enable()函数以及tasklet_disable_nosync()函数(include/linux/interrupt.h),不说了只列代码:
staticinlinevoidtasklet_disable_nosync(structtasklet_struct*t)
atomic_inc(&
count);
smp_mb__after_atomic_inc();
staticinlinevoidtasklet_disable(structtasklet_struct*t)
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
staticinlinevoidtasklet_enable(structtasklet_struct*t)
smp_mb__before_atomic_dec();
atomic_dec(&
只列tasklet_action()函数(kernel/softirq.c):
staticvoidtasklet_action(structsoftirq_action*a)
structtasklet_struct*list;
local_irq_disable();
list=__this_cpu_read(tasklet_vec.head);
__this_cpu_write(tasklet_vec.head,NULL);
__get_cpu_var(tasklet_vec).head);
local_irq_enable();
while(list){
structtasklet_struct*t=list;
list=list->
next;
if(tasklet_trylock(t)){
atomic_read(&
count)){
test_and_clear_bit(TASKLET_STATE_SCHED,&
BUG();
func(t->
data);
tasklet_unlock(t);
continue;
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
tasklet_action()函数在softirq_init()函数中被调用:
void__initsoftirq_init(void)
...
open_softirq(TASKLET_SOFTIRQ,tasklet_action);
open_softirq(HI_SOFTIRQ,tasklet_hi_action);
tasklet_kill()实现:
voidtasklet_kill(structtasklet_struct*t)
if(in_interrupt())
printk("
Attempttokilltaskletfrominterruptn"
);
while(test_and_set_bit(TASKLET_STATE_SCHED,&
state)){
do{
yield();
}while(test_bit(TASKLET_STATE_SCHED,&
state));
clear_bit(TASKLET_STATE_SCHED,&
state);
yeild()函数是个值的研究的点。
四、一个tasklet调用例子
找了一个tasklet的例子看一下(drivers/usb/atm,usb摄像头),在其自举函数usbatm_usb_probe()中调用了tasklet_init()初始化了两个tasklet描述符用于接收和发送的“可延迟操作处理”,但此是并没有将其加入到tasklet_vec[]或tasklet_hi_vec[]中:
tasklet_init(&
instance->
rx_channel.tasklet,
usbatm_rx_process,(unsignedlong)instance);
tance->
tx_channel.tasklet,
usbatm_tx_process,(unsignedlong)instance);
在其发送接口usbatm_atm_send()函数调用tasklet_schedule()函数将所初始化的tasklet加入到当前cpu的tasklet_vec链表尾部,并尝试调用do_softirq_irqoff()执行软中断TASKLET_SOFTIRQ:
staticintusbatm_atm_send(structatm_vcc*vcc,structsk_buff*skb)
tasklet_schedule(&
tx_channel.tasklet);
在其断开设备的接口usbatm_usb_disconnect()中调用tasklet_disable()函数和tasklet_enable()函数重新启动其收发tasklet(具体原因不详,这个地方可能就是由这个需要,暂时重启收发tasklet):
voidusbatm_usb_disconnect(structusb_interface*intf)
tasklet_disable(&
rx_channel.tasklet);
tasklet_enable(&
在其销毁接口usbatm_destroy_instance()中调用tasklet_kill()函数,强行将该tasklet踢出调度队列。
从上述过程以及tasklet的设计可以看出,tasklet整体是这么运行的:
驱动应该在其硬中断处理函数的莫为调用tasklet_schedule()接口激活该taskle,内核经常调用do_softirq()执行软中断,通过softirq执行tasket,如下图所示。
图中灰色部分为禁止硬中断部分,为保护软中断pending位图和tasklet_vec链表数组,count的改变均为原子操作,count确保SMP架构下同时只有一个CPU在执行该tasklet:
tasklet_action
五、tasklet同步
主要看两个参数,一个state,一个count。
state用于校验在tasklet_action()或tasklet_schedule()时,是否执行该tasklet的handler。
state被tasklet_schedule()函数、tasklet_hi_schedule()函数、tasklet_action()函数以及tasklet_kill()函数所修改:
tasklet_schedule()函数、tasklet_hi_schedule()函数将state置位TASKLET_STATE_SCHED。
tasklet_action()函数将state的TASKLET_STATE_SCHED清除,并设置TASKLET_STATE_RUN。
tasklet_kill()函数将state的TASKLET_STATE_SCHED清除。
tasklet_action()函数在设置TASKLET_STATE_RUN标志时,使用了tasklet_trylock()、tasklet_unlock()等接口:
count用于smp同步,count不为0,则表示该tasklet正在某CPU上执行,其他CPU则不执行该tasklet,count保证某个tasklet同时只能在一个CPU上执行。
count的操作都是原子操作:
tasklet_disable()函数/tasklet_disable_nosync()函数将count原子减1。
tasklet_enablle()函数将count原子加1。
另外,tasklet的操作中还所使用了local_irq_save()/local_irq_disable()等禁止本地中断的函数,早保护对象被修改完毕后立即使用local_irq_resore()/local_irq_enable()开启:
tasklet_schedule()函数中,用于保护tasklet_vec[]链表和软中断的pending位图的更改。
因为硬中断的激发能导致二者的更改。
tasklet_action()函数中,用于保护tasklet_vec[]链表和软中断的pending位图的更改。
六、总结
tasklet是一种“可延迟执行”机制中的一种,基于软中断实现,主要面向驱动程序。
tasklet与软中断的区别在于每个CPU上不能同时执行相同的tasklet,tasklet函数本身也不必是可重入的。
与软中断一样,为了保证tasklet和硬中断之间在同一个CPU上是串行执行的,维护其PER_CPU的链表时,需要屏蔽硬中断。
不要为了列代码而列代码,不要为了抄书而抄书。
七、细节
1、原子操作
tasklet使用taskle_disable()函数和tasklet_enable()函数对count位进行增减操作,以保证SMP架构下,不在不同的CPU上同时运行相同的tasklet。
这里使用了原子操作atomic_inc()和atomic_dec():
staticinlinevoidatomic_inc(atomic_t*v)
asmvolatile(LOCK_PREFIX"
incl%0"
:
"
+m"
(v-&
gt;
counter));
staticinlinevoidatomic_dec(atomic_t*v)
decl%0"
#ifdefCONFIG_SMP
#defineLOCK_PREFIX_HERE
"
.pushsection.smp_locks,"
a"
n"
.balign4n"
.long671f-.n"
/*offset*/
.popsectionn"
671:
#defineLOCK_PREFIXLOCK_PREFIX_HERE"
ntlock;
#else/*!
CONFIG_SMP*/
#defineLOCK_PREFIX_HERE"
#defineLOCK_PREFIX"
#endif