softIRQ tasklet work_queue

来源:互联网 发布:幸运牛抽奖软件注册码 编辑:程序博客网 时间:2024/05/16 11:14

Soft Irq、tasklet和work queue



Soft Irq、tasklet和work queue并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者。本文重点在于介绍这三者之间的关系。(函数细节将不会在本文中出现,可以参考文献,点这里)

(1)上半部和下半部的区别
上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。
两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面四条:
a)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
b)如果一个任务和硬件相关,将其放在中断处理程序中执行。
c)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
d)其他所有任务,考虑放在下半部去执行。

(2)为什么要使用软中断?
软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

(3)为什么要使用tasklet?(tasklet和软中断的区别)
由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
b)多个不同类型的tasklet可以并行在多个CPU上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
tasklet是在两种软中断类型的基础上实现的,但是由于其特殊的实现机制(将在4.3节详细介绍),所以具有了这样不同于软中断的特性。而由于这种特性,所以降低了设备驱动程序开发者的负担,因此如果不需要软中断的并行特性,tasklet就是最好的选择。

(4)可延迟函数(软中断及tasklet)的使用
一般而言,在可延迟函数上可以执行四种操作:初始化/激活/执行/屏蔽。屏蔽我们这里不再叙述,前三个则比较重要。下面将软中断和tasklet的三个步骤分别进行对比介绍。

(4.1)初始化
初始化是指在可延迟函数准备就绪之前所做的所有工作。一般包括两个大步骤:首先是向内核声明这个可延迟函数,以备内核在需要的时候调用;然后就是调用相应的初始化函数,用函数指针等初始化相应的描述符。
如果是软中断则在内核初始化时进行,其描述符定义如下:

  struct softirq_action
          {
                   
void (*action)(struct softirq_action *);
                   
void*data;
          };

在\kernel\softirq.c文件中包括了32个描述符的数组static struct softirq_action softirq_vec[32];但实际上只有前6个已经被内核注册使用(包括tasklet使用的HI_SOFTIRQ/TASKLET_SOFTIRQ和网络协议栈使用的NET_TX_SOFTIRQ/NET_RX_SOFTIRQ,还有SCSI存储和系统计时器使用的两个),剩下的可以由内核开发者使用。需要使用函数:
         void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
初始化数组中索引为nr的那个元素。需要的参数当然就是action函数指针以及data。例如网络子系统就通过以下两个函数初始化软中断(net_tx_action/net_rx_action是两个函数):

    open_softirq(NET_TX_SOFTIRQ,net_tx_action);
     open_softirq(NET_RX_SOFTIRQ,net_rx_action);

这样初始化完成后实际上就完成了一个一一对应的关系:当内核中产生到NET_TX_SOFTIRQ软中断之后,就会调用net_tx_action这个函数。
tasklet则可以在运行时定义,例如加载模块时。定义方式有两种:
静态声明

DECLARE_TASKET(name, func, data)
DECLARE_TASKLET_DISABLED(name, func, data)

动态声明

voidtasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)

其参数分别为描述符,需要调用的函数和此函数的参数—必须是unsigned long类型。也需要用户自己写一个类似net_tx_action的函数指针func。初始化最终生成的结果就是一个实际的描述符,假设为my_tasklet(将在下面用到)。

(4.2)激活
激活标记一个可延迟函数为挂起(pending)状态,表示内核可以调用这个可延迟函数(即使在中断过程中也可以激活可延迟函数,只不过函数不会被马上执行);这种情况可以类比处于TASK_RUNNING状态的进程,处在这个状态的进程只是准备好了被CPU调度,但并不一定马上就会被调度。
软中断使用raise_softirq()函数激活,接收的参数就是上面初始化时用到的数组索引nr。
tasklet使用tasklet_schedule()激活,该函数接受tasklet的描述符作为参数,例如上面生成的my_tasklet:

tasklet_schedule(&my_tasklet)

(4.3)执行
执行就是内核运行可延迟函数的过程,但是执行只发生在某些特定的时刻(叫做检查点,具体有哪些检查点?详见《深入》p.177)。
每个CPU上都有一个32位的掩码__softirq_pending,表明此CPU上有哪些挂起(已被激活)的软中断。此掩码可以用local_softirq_pending()宏获得。所有的挂起的软中断需要用do_softirq()函数的一个循环来处理。
而对于tasklet,由于软中断初始化时,就已经通过下面的语句初始化了当遇到TASKLET_SOFTIRQ/HI_SOFTIRQ这两个软中断所需要执行的函数:

    open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
     open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

因此,这两个软中断是要被区别对待的。tasklet_action和tasklet_hi_action内部实现就是为什么软中断和tasklet有不同的特性的原因(当然也因为二者的描述符不同,tasklet的描述符要比软中断的复杂,也就是说内核设计者自己多做了一部分限制的工作而减少了驱动程序开发者的工作)。

(5)为什么要使用工作队列work queue?(work queue和软中断的区别)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
因此在2.6版的内核中出现了在内核态运行的work queue(替代了2.4内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,以完成不同的工作。



Workqueue机制分析


什么是workqueue?


Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化。

workqueue是内核中实现简单而有效的机制,他显然简化了内核daemon的创建,方便了用户的编程,


Workqueue机制的实现

Workqueue机制中定义了两个重要的数据结构,分析如下:


1、         
cpu_workqueue_struct结构。该结构将CPU和内核线程进行了绑定。在创建workqueue的过程中,Linux根据当前系统CPU的个数创建cpu_workqueue_struct。在该结构主要维护了一个任务队列,以及内核线程需要睡眠的等待队列,另外还维护了一个任务上下文,即task_struct。


2、         
work_struct结构是对任务的抽象。在该结构中需要维护具体的任务方法,需要处理的数据,以及任务处理的时间。该结构定义如下:


struct work_struct {

              unsigned
long pending;

               struct
list_head entry;                  /* 将任务挂载到queue的挂载点 */

               void
(*func)(void *);                   /* 任务方法 */

               void
*data;                                  /*
任务处理的数据*/

               void
*wq_data;                           /*
work的属主 */

               strut
timer_list timer;                   /* 任务延时处理定时器 */

};


       当用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue对workqueue队列进行初始化时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后Linux根据当前CPU的情况,为workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个cpu_workqueue_struct对象都会存在一条任务队列。紧接着,Linux为每个cpu_workqueue_struct对象分配一个内核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化接口将workqueue初始化完毕,返回workqueue的指针。


       在初始化workqueue过程中,内核需要初始化内核线程,注册的内核线程工作比较简单,就是不断的扫描对应cpu_workqueue_struct中的任务队列,从中获取一个有效任务,然后执行该任务。所以如果任务队列为空,那么内核daemon就在cpu_workqueue_struct中的等待队列上睡眠,直到有人唤醒daemon去处理任务队列。


       Workqueue初始化完毕之后,将任务运行的上下文环境构建起来了,但是具体还没有可执行的任务,所以,需要定义具体的work_struct对象。然后将work_struct加入到任务队列中,Linux会唤醒daemon去处理任务。


       上述描述的workqueue内核实现原理可以描述如下:




    在Workqueue机制中,提供了一个系统默认的workqueue队列——keventd_wq,这个队列是Linux系统在初始化的时候就创建的。用户可以直接初始化一个work_struct对象,然后在该队列中进行调度,使用更加方便。

Workqueue编程接口



序号


接口函数


说明


1


create_workqueue


用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数:


@name:workqueue的名称


2


create_singlethread_workqueue


用于创建workqueue,只创建一个内核线程。输入参数:


@name:workqueue名称


3


destroy_workqueue


释放workqueue队列。输入参数:


@ workqueue_struct:需要释放的workqueue队列指针


4


schedule_work


调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数:


@ work_struct:具体任务对象指针


5


schedule_delayed_work


延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数:


@work_struct:具体任务对象指针


@delay:延迟时间


6


queue_work


调度执行一个指定workqueue中的任务。输入参数:


@ workqueue_struct:指定的workqueue指针


@work_struct:具体任务对象指针


7


queue_delayed_work


延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。




 workqueue 工作队列

workqueue,中文称其为工作队列,是一个用于创建内核线程的接口,通过它创建的内核线程来执行内核其他模块排列到队列里的工作,创建的内核线程被称为工作者线程。要理解工作队列的实现,重点在于理解相关的三个数据结构的含义及关系。

 
1 表示工作队列类型的数据结构:struct workqueue_struct
  1. /*
  2.  * The externally visible workqueue abstraction is an array of
  3.  * per-CPU workqueues:
  4.  */
  5. struct workqueue_struct {
  6.     struct cpu_workqueue_struct *cpu_wq;   /*工作者线程数组*/
  7.     struct list_head list; /*连接工作队列类型的链表*/
  8.     const char *name;        /*工作者线程的名称*/          
  9.     int singlethread;         /*是否创建新的工作者线程,0表示采用默认的工作者线程event/n*/
  10.     int freezeable; /* Freeze threads during suspend */
  11.     int rt;
  12. #ifdef CONFIG_LOCKDEP
  13.     struct lockdep_map lockdep_map;
  14. #endif
  15. };

内核中默认的工作队列为:

  1. static struct workqueue_struct *keventd_wq __read_mostly;

其对应的工作者线程为:event/n    其中,n代表当前cpu中processor的个数。

2. 表示工作者线程的数据结构:struct cpu_workqueue_struct

  1. /*
  2.  * The per-CPU workqueue (if single thread, we always use the first
  3.  * possible cpu).
  4.  */
  5. struct cpu_workqueue_struct {
  6.     spinlock_t lock;          /*因为工作者线程需要频繁的处理连接到其上的工作,所以需要枷锁保护*/
  7.     struct list_head worklist;
  8.     wait_queue_head_t more_work;
  9.     struct work_struct *current_work; /*当前工作线程需要处理的工作*/
  10.     struct workqueue_struct *wq;   /*该工作者线程属于那种类型的工作者队列*/
  11.     struct task_struct *thread;    /*指向工作者线程的任务结构体*/
  12. } ____cacheline_aligned;

3. 表示工作的数据结构,即工作者线程处理的对象:struct work_struct

  1. struct work_struct {
  2.     atomic_long_t data;       /*工作处理函数func的参数*/
  3. #define WORK_STRUCT_PENDING 0        /* T if work item pending execution */
  4. #define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */
  5. #define WORK_STRUCT_FLAG_MASK (3UL)
  6. #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
  7.     struct list_head entry;        /*连接工作的指针*/
  8.     work_func_t func;              /*工作处理函数*/
  9. #ifdef CONFIG_LOCKDEP
  10.     struct lockdep_map lockdep_map;
  11. #endif
  12. };

再分析了以上三个对象后,重点掌握三者之间的关系。工作队列类型,工作者线程以及工作三个数据对象之间的关系如图所示。


workqueue的执行非常简单,即在每次运行工作者线程的时候,去遍历工作者线程对应的工作链表上的工作,逐一进行处理即可,从这里我们也可以猜到,工作队列是没有优先级的,基本按照FIFO的方式进行处理。


tasklet and workqueue example 

#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/interrupt.h>static struct tasklet_struct my_tasklet;static void tasklet_handler(unsigned long data){    printk("<0>this is tasklet working !!!!!!!!!!!!1%ld",data);}struct workqueue_struct* my_queue=NULL;static struct work_struct work;static void work_handler(struct work_struct *data){    printk("<0>this is work working !!!!!!!!!!!\n");}static int work_init(void){    tasklet_init(&my_tasklet,tasklet_handler,200);    tasklet_schedule(&my_tasklet);    my_queue=create_workqueue("queue");    INIT_WORK(&work,work_handler);    //schedule_delayed_work(&work,10);    queue_work(my_queue,&work);        return 0;}static void work_exit(void){    tasklet_kill(&my_tasklet);    destroy_workqueue(my_queue);}MODULE_LICENSE("GPL");MODULE_AUTHOR("ANYWIND");module_init(work_init);module_exit(work_exit);

tasklet原理和源码分析

原文地址:tasklet原理和源码分析 作者:liujunwei1234

    在文章《softirq原理和源码分析》中对中断的下半部机制softirq进行了简单分析,在Linux内核中比较有名的中断下半部机制还有tasklet和workqueue等,本文重点围绕tasklet的原理和源码进行了详细的分析。
 
一 tasklet基本概念
    tasklet是建立在softirq基础上的一种中断的下半部机制,在本质上与softirq基本相同,但却有简单的编程接口和宽松的锁规则。
    tasklet是通过两种softirq来实现的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,因此,tasklet也被分为两种:高优先级的tasklet(基于HI_SOFTIRQ)和普通优先级的tasklet(基于TASKLET_SOFTIRQ)。在Linux内核中,tasklet通过结构体struct tasklet_struct来描述:
  1. struct tasklet_struct
  2. {
  3.     struct tasklet_struct *next/*构成tasklet的链表*/
  4.     unsigned long state;         /*tasklet的状态*/
  5.     atomic_t count;              /*使能计数*/
  6.     void (*func)(unsigned long); /*tasklet处理函数*/
  7.     unsigned long data; /*处理函数的参数*/
  8. };

每个成员的含义参见注释。这里详细介绍下state成员,它表示tasklet的状态,当前Linux内核中支持两种状态:

(1)TASKLET_STATE_SCHED:表示tasklet已经准备好了,只要被选择到就可以运行了,类似于进程的就绪态。

(2)TASKLET_STATE_RUN:表示tasklet正在执行,类似于进程的RUN态,需要注意的是,TASKLET_STATE_RUN态仅仅在Linux支持SMP的情况下才有定义,因为在UP上处理器能够明确知道当前执行的tasklet是哪个。

另外,只有count=0的时候,tasklet才能被执行,因为对某个tasklet,内核或驱动开发者希望能够显示的使能/禁止其执行,所以才在tasklet结构体中增加了此成员。可以使得count成员变化的接口如下:

  1. static inline void tasklet_disable_nosync(struct tasklet_struct *t)
  2. {
  3.     atomic_inc(&t->count);
  4.     smp_mb__after_atomic_inc();
  5. }

  6. static inline void tasklet_disable(struct tasklet_struct *t)
  7. {
  8.     tasklet_disable_nosync(t);
  9.     tasklet_unlock_wait(t);
  10.     smp_mb();
  11. }

  12. static inline void tasklet_enable(struct tasklet_struct *t)
  13. {
  14.     smp_mb__before_atomic_dec();
  15.     atomic_dec(&t->count);
  16. }
  17. static inline void tasklet_hi_enable(struct tasklet_struct *t)
  18. {
  19.     smp_mb__before_atomic_dec();
  20.     atomic_dec(&t->count);
  21. }

从这些接口的名称也可以看出count成员的主要功能,而不能简单的将count理解为引用计数,容易操作误解,所以理解为使能计数更贴切些!

二、tasklet声明和定义

1、静态和动态创建tasklet的两种方式

(1)静态创建方式

  1. #define DECLARE_TASKLET(name, func, data) \
  2. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

  3. #define DECLARE_TASKLET_DISABLED(name, func, data) \
  4. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

从代码中可以清楚的看到,两个宏的区别在于tasklet的count不同,前面已经讲过count表示tasklet的使能计数,只有当count=0,表示tasklet创建后处于使能状态;count=1,表示tasklet创建后处于禁止状态。

(2)动态创建

  1. struct tasklet_struct t;
  2. void (*func)(unsigned long);
  3. tasklet_init(t,func,NULL);

2. tasklet处理函数

与softirq一样,tasklet不能睡眠和阻塞,因此在设计tasklet处理函数时必须格外小心,不能使用信号或其他引起阻塞的函数。另外,同一个tasklet不可能同时在两个processor上运行,如果两个不同的tasklet之间有数据共享,需要注意采用合适的锁机制。

三、tasklet的执行过程

Linux内核中采用两个每CPU变量来存储属于当前CPU的tasklet:

(1)tasklet_vec:普通的tasklet,即基于TASKLET_SOFTIRQ实现的tasklet

(2)tasklet_hi_vec:高优先级的tasklet,即基于HI_SOFTIRQ实现的tasklet

在Linux内核中通过显示的调用tasklet_schedule()和tasklet_hi_schedule()函数来实现tasklet的调度,然后将在之后的某个时间,该tasklet将会被执行。需要注意一点:tasklet一定是在调度它的CPU上执行

  1. static inline void tasklet_schedule(struct tasklet_struct *t)
  2. {
  3.     if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  4.         __tasklet_schedule(t);
  5. }

  6. extern void __tasklet_hi_schedule(struct tasklet_struct *t);

  7. static inline void tasklet_hi_schedule(struct tasklet_struct *t)
  8. {
  9.     if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  10.         __tasklet_hi_schedule(t);
  11. }
从代码可以看到,首先检查当前tasklet是否处于TASKLET_STATE_SCHED状态,即test_and_set_bit(TASKLET_STATE_SCHED, &t->state),个人感觉只有深入分析了这个函数才能真正了解其实现过程。
  1. static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
  2. {
  3.     int oldbit;

  4.     asm volatile(LOCK_PREFIX "bts %2,%1\n\t"
  5.          "sbb %0,%0" : "=r" (oldbit), ADDR : "Ir" (nr) : "memory");

  6.     return oldbit;
  7. }

这里首先介绍两个比较少用的汇编指令:(内联汇编的知识就不多讲了,不理解的同学可以专门去查阅这方面的知识)

(1)bts %2,%1

bts汇编指令的原型为:bts dest,src  表示将目的操作数dest的第src位赋值给CF,然后再职位dest的第src位。在这里2%表示是输入操作数nr,值为0。1%表示的是输出操作数ADDR,即&t->state。那么,这句指令的意思即是将t->state的第0位先赋值给CF,然后将t->state的第0位置1。如果之前t->state = 0,那么此时CF=0,如果此前CF指向的第0位为1,那么CF=1。

(2)sbb %0,%0

指令格式:sbb 操作对象1,操作对象2 功能:操作对象1=操作对象1-操作对象2-CF

这里0%表示是输出操作数oldbit,即oldbit = oldbit - oldbit - CF,因此返回值只于CF有关,如果为0,返回值就为0,CF不为0,返回值就不为0。

根据bts的讲解,当CF=t->state的第nr位的值,即如果原来这个tasklet被设置为TASKLET_STATE_SCHED状态,那么CF=1,那么test_and_set_bit(TASKLET_STATE_SCHED, &t->state)的返回值就非0,即不能调度执行,如果原来这个tasklet没有被设置为TASKLET_STATE_SCHED状态,那么CF=0,那么那么test_and_set_bit(TASKLET_STATE_SCHED, &t->state)的返回值就为0,这个tasklet就可以调度执行。(不知道有没有讲清楚???)

下面来分析函数__tasklet_schedule()

  1. void __tasklet_schedule(struct tasklet_struct *t)
  2. {
  3.     unsigned long flags;

  4.     local_irq_save(flags);
  5.     t->next = NULL;
  6.     *__get_cpu_var(tasklet_vec).tail = t;
  7.     __get_cpu_var(tasklet_vec).tail = &(t->next);
  8.     raise_softirq_irqoff(TASKLET_SOFTIRQ);
  9.     local_irq_restore(flags);
  10. }
先通过local_irq_save(flags)保存并禁止当前cpu上的中断,然后把该tasklet添加当per-CPU变量tasklet_vec链表的末尾,然后激活tasklet对应的softirq,最后恢复并使能当前cpu上的中断。这里来分析以下函数raise_softirq_irqoff(TASKLET_SOFTIRQ):
  1. /*
  2.  * This function must run with irqs 
  3.  */
  4. inline void raise_softirq_irqoff(unsigned int nr)
  5. {
  6.     __raise_softirq_irqoff(nr);

  7.     /*
  8.      * If we're in an interrupt or softirq, we're done
  9.      * (this also catches softirq-disabled code). We will
  10.      * actually run the softirq once we return from
  11.      * the irq or softirq.
  12.      *
  13.      * Otherwise we wake up ksoftirqd to make sure we
  14.      * schedule the softirq soon.
  15.      */
  16.     if (!in_interrupt())
  17.         wakeup_softirqd();
  18. }
  19. #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

首先在__raise_softirq_irqoff(nr)中激活这个softirq,然后,激活软中断处理内核线程。其中in_interrupt在上一篇文章中已经详细分析过了。(in_interrupt判断当有硬件中断嵌套,其他软中断以及不可屏蔽中断的情况下,返回非0值)

再往下分析就是softirq相关的知识了。

四、用于实现tasklet的softirq的处理函数

  1. void __init softirq_init(void)
  2. {
  3.     int cpu;

  4.     for_each_possible_cpu(cpu) {
  5.         int i;

  6.         per_cpu(tasklet_vec, cpu).tail =
  7.             &per_cpu(tasklet_vec, cpu).head;
  8.         per_cpu(tasklet_hi_vec, cpu).tail =
  9.             &per_cpu(tasklet_hi_vec, cpu).head;
  10.         for (= 0; i < NR_SOFTIRQS; i++)
  11.             INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
  12.     }

  13.     register_hotcpu_notifier(&remote_softirq_cpu_notifier);

  14.     open_softirq(TASKLET_SOFTIRQ, tasklet_action);
  15.     open_softirq(HI_SOFTIRQ, tasklet_hi_action);
  16. }
从上面代码可以看出,Linux内核在进行softirq的初始化的时候,就实现指定了用于实现tasklet的响应的softirq的处理函数为:tasklet_action和tasklet_hi_action。
  1. static void tasklet_action(struct softirq_action *a)
  2. {
  3.     struct tasklet_struct *list;

  4.     local_irq_disable();
  5.     list = __get_cpu_var(tasklet_vec).head;
  6.     __get_cpu_var(tasklet_vec).head = NULL;
  7.     __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
  8.     local_irq_enable();

  9.     while (list) {
  10.         struct tasklet_struct *= list;

  11.         list = list->next;

  12.         if (tasklet_trylock(t)) {
  13.             if (!atomic_read(&t->count)) {
  14.                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
  15.                     BUG();
  16.                 t->func(t->data);
  17.                 tasklet_unlock(t);
  18.                 continue;
  19.             }
  20.             tasklet_unlock(t);
  21.         }

  22.         local_irq_disable();
  23.         t->next = NULL;
  24.         *__get_cpu_var(tasklet_vec).tail = t;
  25.         __get_cpu_var(tasklet_vec).tail = &(t->next);
  26.         __raise_softirq_irqoff(TASKLET_SOFTIRQ);
  27.         local_irq_enable();
  28.     }
这个函数清晰的展示了Linux内核中tasklet的处理过程,即在while循环中,执行了tasklet的处理函数t->func。具体代码就不去分析了,估计大家都能看明白!

softirq原理以及源码分析

原文地址:softirq原理以及源码分析 作者:liujunwei1234

      Linux 的softirq机制是与SMP紧密不可分的。为此,整个softirq机制的设计与实现中自始自终都贯彻了一个思想:“谁触发,谁执行”(Who marks,Who runs),也即触发软中断的那个CPU负责执行它所触发的软中断,而且每个CPU都由它自己的软中断触发与控制机制。这个设计思想也使得softirq 机制充分利用了SMP系统的性能和特点。 多个softirq可以并行执行,甚至同一个softirq可以在多个processor上同时执行。
 
一、softirq的实现
     每个softirq在内核中通过struct softirq_action来表示,另外,通过全局属组softirq_vec标识当前内核支持的所有的softirq。
  1. /* softirq mask and active fields moved to irq_cpustat_t in
  2.  * asm/hardirq.to get better cache usage. KAO
  3.  */

  4. struct softirq_action
  5. {
  6.     void    (*action)(struct softirq_action *);
  7. };

  8. static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
Linux内核最多可以支持32个softirq(思考:为什么是32个?),但当前只实现了10个,如下:
  1. enum
  2. {
  3.     HI_SOFTIRQ=0,
  4.     TIMER_SOFTIRQ,
  5.     NET_TX_SOFTIRQ,
  6.     NET_RX_SOFTIRQ,
  7.     BLOCK_SOFTIRQ,
  8.     BLOCK_IOPOLL_SOFTIRQ,
  9.     TASKLET_SOFTIRQ,
  10.     SCHED_SOFTIRQ,
  11.     HRTIMER_SOFTIRQ,
  12.     RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

  13.     NR_SOFTIRQS
  14. };
二、softirq处理函数
 
    struct softirq_action结构体中,只有一个函数指针成员action,即指向用户定义的softirq处理函数。当执行时,可以通过如下代码:
                     softirq_vec[i]->action(i);
    一个注册的softirq在执行之前必须被激活,术语称为"raise the softirq"。被激活的softirq通常并不会立即执行,一般会在之后的某个时刻检查当前系统中是否有被pending的softirq,如果有就去执行,Linux内核中检查是否有softirq挂起的检查点主要有以下三类:
(1)硬件中断代码返回的时候
  1. /*
  2.  * Exit an interrupt context. Process softirqs if needed and possible:
  3.  */
  4. void irq_exit(void)
  5. {
  6.     account_system_vtime(current);
  7.     trace_hardirq_exit();
  8.     sub_preempt_count(IRQ_EXIT_OFFSET);
  9.     if (!in_interrupt() && local_softirq_pending())
  10.         invoke_softirq();

  11.     rcu_irq_exit();
  12. #ifdef CONFIG_NO_HZ
  13.     /* Make sure that timer wheel updates are propagated */
  14.     if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
  15.         tick_nohz_stop_sched_tick(0);
  16. #endif
  17.     preempt_enable_no_resched();
  18. }
(2)ksoftirqd内核服务线程运行的时候
  1. static int run_ksoftirqd(void * __bind_cpu)
  2. {
  3.     ... ...
  4.         while (local_softirq_pending()) {
  5.             /* Preempt disable stops cpu going offline.
  6.              If already offline, we'll be on wrong CPU:
  7.              don't process */
  8.             if (cpu_is_offline((long)__bind_cpu))
  9.                 goto wait_to_die;
  10.             do_softirq();
  11.             preempt_enable_no_resched();
  12.             cond_resched();
  13.             preempt_disable();
  14.             rcu_note_context_switch((long)__bind_cpu);
  15.         }
  16.         preempt_enable();
  17.         set_current_state(TASK_INTERRUPTIBLE);
  18.     }
  19.     __set_current_state(TASK_RUNNING);
  20.     return 0;
  21. ... ...
  22. }
(3)在一些内核子系统中显示的去检查挂起的softirq
  1. int netif_rx_ni(struct sk_buff *skb)
  2. {
  3.     int err;

  4.     preempt_disable();
  5.     err = netif_rx(skb);
  6.     if (local_softirq_pending())
  7.         do_softirq();
  8.     preempt_enable();

  9.     return err;
  10. }
下面重点分析以下do_softirq(),了解Linux内核到底是怎么来处理softirq的。
  1. asmlinkage void do_softirq(void)
  2. {
  3.     unsigned long flags;
  4.     struct thread_info *curctx;
  5.     union irq_ctx *irqctx;
  6.     u32 *isp;

  7.     if (in_interrupt()) /*这个函数需要仔细理解???*/
  8.         return;

  9.     local_irq_save(flags);

  10.     if (local_softirq_pending()) {
  11.         curctx = current_thread_info();
  12.         irqctx = __get_cpu_var(softirq_ctx);
  13.         irqctx->tinfo.task = curctx->task;
  14.         irqctx->tinfo.previous_esp = current_stack_pointer;

  15.         /* build the stack frame on the softirq stack */
  16.         isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));

  17.         call_on_stack(__do_softirq, isp);
  18.         /*
  19.          * Shouldnt happen, we returned above if in_interrupt():
  20.          */
  21.         WARN_ON_ONCE(softirq_count());
  22.     }

  23.     local_irq_restore(flags);
  24. }
do_softirq主要是完成了以下几个功能:
(1)检查当前processor上是否有pending的softirq
(2)如果有pending的softirq,为softirq的处理建立新的堆栈,即建立新的软中断上下文环境
(3)处理软中断__do_softirq
这里需要重点分析一下in_interrupt()函数的含义。在linux内核中,为了方便判断当前执行路径在哪个上下文环境中,定义了几个接口:
  1. #define hardirq_count() (preempt_count() & HARDIRQ_MASK)
  2. #define softirq_count() (preempt_count() & SOFTIRQ_MASK)
  3. #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
  4.      | NMI_MASK))
  5. /*
  6.  * Are we doing bottom half or hardware interrupt processing?
  7.  * Are we in a softirq context? Interrupt context?
  8.  */
  9. #define in_irq() (hardirq_count())
  10. #define in_softirq() (softirq_count())
  11. #define in_interrupt() (irq_count())
  12. /*
  13.  * Are we in NMI context?
  14.  */
  15. #define in_nmi() (preempt_count() & NMI_MASK)
从注释可以看出包括:硬件中断上下文,软件中断上下文,不可屏蔽上下文等。在这些宏中,都涉及到了preempt_count()这个宏,这个宏是一个比较重要的宏,在Linux源码中对其做了详细的注释:
  1. /*
  2.  * We put the hardirq and softirq counter into the preemption
  3.  * counter. The bitmask has the following meaning:
  4.  *
  5.  * - bits 0-7 are the preemption count (max preemption depth: 256)
  6.  * - bits 8-15 are the softirq count (max # of softirqs: 256)
  7.  *
  8.  * The hardirq count can in theory reach the same as NR_IRQS.
  9.  * In reality, the number of nested IRQS is limited to the stack
  10.  * size as well. For archs with over 1000 IRQS it is not practical
  11.  * to expect that they will all nest. We give a max of 10 bits for
  12.  * hardirq nesting. An arch may choose to give less than 10 bits.
  13.  * m68k expects it to be 8.
  14.  *
  15.  * - bits 16-25 are the hardirq count (max # of nested hardirqs: 1024)
  16.  * - bit 26 is the NMI_MASK
  17.  * - bit 28 is the PREEMPT_ACTIVE flag
  18.  *
  19.  * PREEMPT_MASK: 0x000000ff
  20.  * SOFTIRQ_MASK: 0x0000ff00
  21.  * HARDIRQ_MASK: 0x03ff0000
  22.  * NMI_MASK: 0x04000000
  23.  */
从注释可以看出,preempt_count各个bit位的含义:
(1)bit0~7位表示抢占计数,即支持最大的抢占深度为256
(2)bit8~15位表示软中断计数,即支持最大的软中断的个数为256,需要注意的是,由于软中断还受制于pending状态,一个32位的变量,因此实际最大只能支持32个软中断。
(3)bit16~25位表示硬件中断嵌套层数,即最大可支持的嵌套层次为1024,实际情况下这是不可能的,因为中断的嵌套层数还受制于中断处理的栈空间的大小。
    介绍了这么多,现在了重点分析下上面提到的in_interrupt到底表示什么意思?
  1. #define in_interrupt() (irq_count())

  2. #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \

  3. | NMI_MASK))
从其宏定义可以看出,in_interrupt宏的值是硬件中断嵌套层数,软中断计数以及可屏蔽中断三者之和。回到do_softirq的代码中,如果in_interrupt的值大于0,就不会处理软中断,意思是当有硬件中断嵌套,其他软中断以及不可屏蔽中断的情况下,不会去处理软中断。对于中断的嵌套层数以及不可屏蔽中断是比较好理解的,对于软中断,应该去分析以下,在什么地方软中断的计数会增加:
  1. __local_bh_disable((unsigned long)__builtin_return_address(0));
  2. static inline void __local_bh_disable(unsigned long ip)
  3. {
  4.     add_preempt_count(SOFTIRQ_OFFSET);
  5.     barrier();
  6. }
  7. # define add_preempt_count(val)    do { preempt_count() += (val); } while (0)
从代码可以看出,禁止中断下半部分的函数会增加软中断的计数,即当有软中断的do_softirq在进行处理时,如果此时被硬件中断打断,而且在硬件中断中又激活了优先级更高的软中断,当硬件中断退出时,那么当再去执行do_softirq时,此时in_interrupt > 0,岂不是死锁了!!!希望大家指教。
实际的处理函数为__do_softirq:
  1. asmlinkage void __do_softirq(void)
  2. {
  3.     struct softirq_action *h;
  4.     __u32 pending;
  5.     int max_restart = MAX_SOFTIRQ_RESTART; /*不启动ksoftirqd之前,最大的处理softirq的次数,经验值*/
  6.     int cpu;
  7.     /*取得当前被挂起的softirq,同时这里也解释了为什么Linux内核最多支持32个softirq,因为pending只有32bit*/
  8.     pending = local_softirq_pending()
  9.     account_system_vtime(current);

  10.     __local_bh_disable((unsigned long)__builtin_return_address(0));
  11.     lockdep_softirq_enter();

  12.     cpu = smp_processor_id();
  13. restart:
  14.     /* Reset the pending bitmask before enabling irqs */
  15.     set_softirq_pending(0);/*获取了pending的softirq之后,清空所有pending的softirq的标志*/

  16.     local_irq_enable();

  17.     h = softirq_vec;

  18.     do {
  19.         if (pending & 1) { /*从最低位开始,循环右移逐位处理pending的softirq*/
  20.             int prev_count = preempt_count();
  21.             kstat_incr_softirqs_this_cpu(- softirq_vec);

  22.             trace_softirq_entry(h, softirq_vec);
  23.             h->action(h); /*执行softirq的处理函数*/
  24.             trace_softirq_exit(h, softirq_vec);
  25.             if (unlikely(prev_count != preempt_count())) {
  26.                 printk(KERN_ERR "huh, entered softirq %td %s %p"
  27.                  "with preempt_count %08x,"
  28.                  " exited with %08x?\n", h - softirq_vec,
  29.                  softirq_to_name[- softirq_vec],
  30.                  h->action, prev_count, preempt_count());
  31.                 preempt_count() = prev_count;
  32.             }

  33.             rcu_bh_qs(cpu);
  34.         }
  35.         h++;
  36.         pending >>= 1;  /*循环右移*/
  37.     } while (pending);

  38.     local_irq_disable();

  39.     pending = local_softirq_pending();
  40.     if (pending && --max_restart)  /*启动ksoftirqd的阈值*/
  41.         goto restart;

  42.     if (pending)  /*启动ksoftirqd去处理softirq,此时说明pending的softirq比较多,比较频繁,上面的处理过程中,又不断有softirq被pending*/
  43.         wakeup_softirqd();

  44.     lockdep_softirq_exit();

  45.     account_system_vtime(current);
  46.     _local_bh_enable();
三、使用softirq
     softirq一般用在对实时性要求比较强的地方,当前的Linux内核中,只有两个子系统直接使用了softirq:网络子系统和块设备子系统。另外,增加新的softirq需要重新编译内核,因此,除非必须需要,最好考虑tasklet和kernel timer是否适合当前需要。
     如果必须需要使用softirq,那么需要考虑的一个重要的问题就是新增加的softirq的优先级,默认情况下,softirq的数值越小优先级越高,根据实际经验,新增加的softirq最好在BLOCK_SOFTIRQ和TASKLET_SOFTIRQ之间。
     softirq的处理函数通过open_softirq进行注册,此函数接收两个参数,一个是softirq的整数索引,另一个是该softirq对应的处理函数。例如在网络子系统中,注册了如下两个softirq及其处理函数:
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
    前面提到,软中断处理函数注册后,还需要将该软中断激活,此软中断才能被执行,激活操作是通过raise_softirq函数来实现,在网络子系统中激活代码如下:
  1. /* Called with irq disabled */
  2. static inline void ____napi_schedule(struct softnet_data *sd,
  3.                  struct napi_struct *napi)
  4. {
  5.     list_add_tail(&napi->poll_list, &sd->poll_list);
  6.     __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  7. }
这里的__raise_softirq_irqoff和raise_softirq的区别是,前者在事先已经关中断的情况下可以被使用,后者自己完成中断的关闭和恢复。
 

softirq/tasklet/workqueue的区别

原文地址:softirq/tasklet/workqueue的区别 作者:cainiao413

softirq和tasklet都属于软中断,tasklet是softirq的特殊实现;

workqueue是普通的工作队列。

1、softirq

软中断支持SMP,同一个softirq可以在不同的CPU上同时运行,softirq必须是可重入的。软中断是在编译期间静态分配的,它不像tasklet那样能被动态的注册或去除。kernel/softirq.c中定义了一个包含32个softirq_action结构体的数组。每个被注册的软中断都占据该数组的一项。因此最多可能有32个软中断。2.6版本的内核中定义了六个软中断:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、 SCSI_SOFTIRQ、TASKLET_SOFTIRQ。 
一般情况下,在硬件中断处理程序后都会试图调用do_softirq()函数,每个CPU都是通过执行这个函数来执行软中断服务的。由于软中断不能进入硬中断部分,且同一个CPU上软中断的执行是串行的,即不允许嵌套,因此,do_softirq()函数一开始就检查当前CPU是否已经正出在中断服务中,如果是则 do_softirq()函数立即返回。这是由do_softirq()函数中的 if (in_interrupt()) return; 保证的。

2、tasklet

引入tasklet,最主要的是考虑支持SMP,提高SMP多个cpu的利用率;不同的tasklet可以在不同的cpu上运行。tasklet可以理解为softirq的派生,所以它的调度时机和软中断一样。对于内核中需要延迟执行的多数任务都可以用tasklet来完成,由于同类tasklet本身已经进行了同步保护,所以使用tasklet比软中断要简单的多,而且效率也不错。 tasklet把任务延迟到安全时间执行的一种方式,在中断期间运行,即使被调度多次,tasklet也只运行一次,不过tasklet可以在SMP系统上和其他不同的tasklet并行运行。在SMP系统上,tasklet还被确保在第一个调度它的CPU上运行,因为这样可以提供更好的高速缓存行为,从而提高性能。 


    与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,但不同的tasklet代码在同一时刻可以在多个CPU上并发地执行。 Kernel/softirq.c中用tasklet_trylock()宏试图对当前要执行的tasklet(由指针t所指向)进行加锁,如果加锁成功(当前没有任何其他CPU正在执行这个tasklet),则用原子读函数atomic_read()进一步判断count成员的值。如果count为0,说明这个tasklet是允许执行的。如果tasklet_trylock()宏加锁不成功,或者因为当前tasklet的count值非0而不允许执行时,我们必须将这个tasklet重新放回到当前CPU的tasklet队列中,以留待这个CPU下次服务软中断向量TASKLET_SOFTIRQ时再执行。为此进行这样几步操作:

(1)先关 CPU中断,以保证下面操作的原子性。

(2)把这个tasklet重新放回到当前CPU的tasklet队列的首部;

(3)调用 __cpu_raise_softirq()函数在当前CPU上再触发一次软中断请求TASKLET_SOFTIRQ;

(4)开中断。 
软中断和tasklet都是运行在中断上下文中,它们与任一进程无关,没有支持的进程完成重新调度。所以软中断和tasklet不能睡眠、不能阻塞,它们的代码中不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。也正是由于它们运行在中断上下文中,所以它们在同一个CPU上的执行是串行的,这样就不利于实时多媒体任务的优先处理。

3、workqueue

什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet

 

 

*******************************************************************************************************

 

 当前的2.6版内核中,有三种可能的选择:softirq、tasklet和work queue。 tasklet基于softirq实现,所以两者很相近。work queue与它们完全不同,它靠内核线程实现。

1、softirq

       软中断支持SMP,同一个softirq可以在不同的CPU上同时运行,softirq必须是可重入。软中断是在编译期间静态分配的,它不像tasklet那样能被动态的注册或去除。kernel/softirq.c中定义了一个包含32个 softirq_action结构体的数组。每个被注册的软中断都占据该数组的一项。因此最多可能有32个软中断。2.6版本的内核中定义了六个软中断:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、 TASKLET_SOFTIRQ。

       软中断的特性:
       1).一个软中断不会抢占另外一个软中断。
       2).唯一可以抢占软中断的是中断处理程序。
       3).其他软中断(包括相同类型的)可以在其他的处理其上同时执行。
       4).一个注册的软中断必须在被标记后才能执行。
       5).软中断不可以自己休眠(即调用可阻塞的函数或sleep等)。
       6).索引号小的软中断在索引号大的软中断之前执行。

2、tasklet

      引入tasklet,最主要的是考虑支持SMP,提高SMP多个cpu的利用率;两个相同的tasklet决不会同时执行。tasklet可以理解为softirq的派生,所以它的调度时机和软中断一样。对于内核中需要延迟执行的多数任务都可以用tasklet来完成,由于同类tasklet本身已经进行了同步保护,所以使用tasklet比软中断要简单的多,而且效率也不错。tasklet把任务延迟到安全时间执行的一种方式,在中断期间运行,即使被调度多次,tasklet也只运行一次,不过tasklet可以在SMP系统上和其他不同的tasklet并行运行。在SMP系统上,tasklet还被确保在第一个调度它的CPU上运行,因为这样可以提供更好的高速缓存行为,从而提高性能。

       tasklet的特性:.不允许两个两个相同类型的tasklet同时执行,即使在不同的处理器上。

3、work queue

       如果推后执行的任务需要睡眠,那么就选择工作队列。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。work queue造成的开销最大,因为它要涉及到内核线程甚至是上下文切换。这并不是说work queue的低效,但每秒钟有数千次中断,就像网络子系统时常经历的那样,那么采用其他的机制可能更合适一些。 尽管如此,针对大部分情况工作队列都能提供足够的支持。

      工作队列特性:
      1).工作队列会在进程上下文中执行!
      2).可以阻塞。(前两种机制是不可以阻塞的)
      3).可以被重新调度。(前两种只可以被中断处理程序打断)
      4).使用工作队列的两种形式:
             1>缺省工作者线程(works threads)
             2>自建的工作者线程
      5).在工作队列和内核其他部分之间使用锁机制就像在其他的进程上下文一样。
      6).默认允许响应中断。
      7).默认不持有任何锁。

4、softirq和tasklet共同点

       软中断和tasklet都是运行在中断上下文中,它们与任一进程无关,没有支持的进程完成重新调度。所以软中断和tasklet不能睡眠、不能阻塞,它们的代码中不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。也正是由于它们运行在中断上下文中,所以它们在同一个CPU上的执行是串行的,这样就不利于实时多媒体任务的优先处理。


tasklet source code


/* 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;unsigned long state;/* 可以是 */atomic_t count;void (*func)(unsigned long);unsigned long data;};

state 可以是
enum
{
TASKLET_STATE_SCHED,/* Tasklet is scheduled for execution */
TASKLET_STATE_RUN/* Tasklet is running (SMP only) */
};


state 防止多次调度

防止多CPU执行, 在中断期间运行,即使被调度多次,tasklet也只运行一次


count 防止多次运行

count 为0才可以执行,保证本cpu只执行一份, 两个相同的tasklet决不会同时执行 


static void tasklet_action(struct softirq_action *a)


while (list) {struct tasklet_struct *t = list;list = list->next;if (tasklet_trylock(t)) {if (!atomic_read(&t->count)) {if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))BUG();t->func(t->data);tasklet_unlock(t);continue;}tasklet_unlock(t);}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();}