linux驱动程序下的tasklet机制

运维 系统运维
在编写设备驱动时,tasklet机制是一种比较常见的机制。在老版本的linux中,通常中断处理分为top half handler 、bottom half handler。但是在linux2.6以后的linux采取了另外一种机制,即软机制来代替bottom half handler 的处理。本文主要介绍了tasklet机制在linux中的实现。

       Tasklet 机制 是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。

      从某种程度上讲,tasklet机制是Linux内核对BH机制的一种扩展。在2.4内核引入了softirq机制后,原有的BH机制正是通过tasklet机制这个桥梁来纳入softirq机制的整体框架中的。正是由于这种历史的延伸关系,使得tasklet机制与一般意义上的软中断有所不同,而呈现出以下两个显著的特点:

       1. 与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,而不像一般的软中断服务函数(即softirq_action结构中的action函数指针)那样——在同一时刻可以被多个CPU并发地执行。

       2. 与BH机制不同,不同的tasklet代码在同一时刻可以在多个CPU上并发地执行,而不像BH(Bottom Half)机制那样必须严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。

       Linux用数据结构 tasklet_struct 来描述一个tasklet。该数据结构定义在include/linux/interrupt.h头文件中。如下所示:

 

  1. view sourceprint?1 struct tasklet_struct   
  2.  
  3.  {   
  4.  
  5.  struct tasklet_struct *next;   
  6.  
  7.  unsigned long state;   
  8.  
  9.  atomic_t count;   
  10.  
  11.  void (*func)(unsigned long);   
  12.  
  13.  unsigned long data;   
  14.  
  15. };   
  16.  

各成员的含义如下:

      (1)next指针:指向下一个tasklet的指针。

      (2)state:定义了这个tasklet的当前状态。这一个32位的无符号长整数,当前只使用了bit[1]和bit[0]两个状态位。其中,bit[1]=1表示这个tasklet当前正在某个CPU上被执行,它仅对SMP系统才有意义,其作用就是为了防止多个CPU同时执行一个tasklet的情形出现;bit[0]=1表示这个tasklet已经被调度去等待执行了。对这两个状态位的宏定义如下所示(interrupt.h):  

  1.  
  2.  
  3. view sourceprint?1 enum   
  4.  
  5. {   
  6.  
  7. TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */   
  8.  
  9. TASKLET_STATE_RUN /* Tasklet is running (SMP only) */   
  10.  
  11.  };   
  12.  

      (3)原子计数count:对这个tasklet的引用计数值。NOTE!只有当count等于0时,tasklet代码段才能执行,也即此时tasklet是被使能的;如果count非零,则这个tasklet是被禁止的。任何想要执行一个tasklet代码段的人都首先必须先检查其count成员是否为0。

      (4)函数指针func:指向以函数形式表现的可执行tasklet代码段。

     (5)data:函数func的参数。这是一个32位的无符号整数,其具体含义可供func函数自行解释,比如将其解释成一个指向某个用户自定义数据结构的地址值。

Linux在interrupt.h头文件中又定义了两个用来定义 tasklet_struct 结构变量的辅助宏:

 

  1. view sourceprint?1 #define DECLARE_TASKLET(name, func, data)   
  2.  
  3.  struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }   
  4.  
  5.    
  6.  
  7. #define DECLARE_TASKLET_DISABLED(name, func, data)   
  8.  
  9.  struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }   
  10.  

       显然,从上述源代码可以看出,用 DECLARE_TASKLET 宏定义的tasklet在初始化时是被使能的(enabled),因为其count成员为0。而用 DECLARE_TASKLET_DISABLED 宏定义的tasklet在初始时是被禁止的(disabled),因为其count等于1。

在这里,tasklet状态指两个方面:1. state成员所表示的运行状态;2. count成员决定的使能/禁止状态。

#p#

        (1)改变一个tasklet的运行状态 state 成员中的bit[0]表示一个tasklet是否已被调度去等待执行,bit[1]表示一个tasklet是否正在某个CPU上执行。对于 state 变量中某位的改变必须是一个原子操作,因此可以用定义在include/asm/bitops.h头文件中的位操作来进行。

        由于bit[1]这一位(即TASKLET_STATE_RUN)仅仅对于SMP系统才有意义,因此Linux在Interrupt.h头文件中显示地定义了对TASKLET_STATE_RUN位的操作。如下所示:

 

 

 

 

  1. view sourceprint?1 #ifdef CONFIG_SMP   
  2.  
  3. #define tasklet_trylock(t) (!test_and_set_bit(TASKLET_STATE_RUN, &(t)->state))   
  4.  
  5. #define tasklet_unlock_wait(t) while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { /* NOTHING */ }   
  6.  
  7. #define tasklet_unlock(t) clear_bit(TASKLET_STATE_RUN, &(t)->state)   
  8.  
  9. #else   
  10.  
  11. #define tasklet_trylock(t) 1   
  12.  
  13. #define tasklet_unlock_wait(t) do { } while (0)   
  14.  
  15. #define tasklet_unlock(t) do { } while (0)   
  16.  
  17.  #endif   
  18.  

        显然,在SMP系统同,tasklet_trylock() 宏将把一个 tasklet_struct 结构变量中的state成员中的bit[1]位设置成1,同时还返回bit[1]位的非。因此,如果bit[1]位原有值为1(表示另外一个CPU正在执行这个tasklet代码),那么tasklet_trylock()宏将返回值0,也就表示上锁不成功。如果bit[1]位的原有值为0,那么tasklet_trylock()宏将返回值1,表示加锁成功。而在单CPU系统中,tasklet_trylock()宏总是返回为1。

        任何想要执行某个tasklet代码的程序都必须首先调用宏 tasklet_trylock() 来试图对这个tasklet进行上锁(即设置TASKLET_STATE_RUN位),且只能在上锁成功的情况下才能执行这个tasklet。建议!即使你的程序只在单 CPU 系统上运行,你也要在执行tasklet之前调用tasklet_trylock()宏,以便使你的代码获得良好可移植性。

        在SMP系统中,tasklet_unlock_wait() 宏将一直不停地测试 TASKLET_STATE_RUN 位的值,直到该位的值变为0(即一直等待到解锁),假如:CPU0正在执行tasklet A的代码,在此期间,CPU1也想执行tasklet A的代码,但CPU1发现tasklet A 的 TASKLET_STATE_RUN 位为1,于是它就可以通过 tasklet_unlock_wait() 宏等待tasklet A被解锁(也即TASKLET_STATE_RUN位被清零)。在单CPU系统中,这是一个空操作。

        宏 tasklet_unlock() 用来对一个 tasklet 进行解锁操作,也即将TASKLET_STATE_RUN位清零。在单CPU系统中,这是一个空操作。

(2)使能/禁止一个tasklet

       使能与禁止操作往往总是成对地被调用的,tasklet_disable() 函数如下

 

 

 

 

  1. (interrupt.h):  
  2. view sourceprint?01 static inline void tasklet_disable(struct tasklet_struct *t)   
  3.  
  4. {   
  5.  
  6. tasklet_disable_nosync(t);   
  7.  
  8. tasklet_unlock_wait(t);   
  9. }   
  10.  
  11. // 函数tasklet_disable_nosync()也是一个静态inline函数,它简单地通过原子操作将count成员变量的值减1。如下所示(interrupt.h):   
  12.  
  13. static inline void tasklet_disable_nosync(struct tasklet_struct *t)   
  14.  
  15. {   
  16.  
  17. atomic_inc(&t->count);   
  18.  
  19. }   
  20.  
  21. // 函数tasklet_enable()用于使能一个tasklet,如下所示(interrupt.h):   
  22.  
  23. static inline void tasklet_enable(struct tasklet_struct *t)   
  24.  
  25. {   
  26.  
  27. atomic_dec(&t->count);   
  28.  
  29. }   
  30.  
  31. // 函数tasklet_init()用来初始化一个指定的tasklet描述符,其源码如下所示(kernel/softirq.c):   
  32.  
  33. void tasklet_init(struct tasklet_struct *t,   
  34.  
  35. void (*func)(unsigned long),   
  36.  
  37. unsigned long data)   
  38.  
  39. {   
  40.  
  41. t->funcfunc = func;   
  42.  
  43. t->datadata = data;   
  44.  
  45. t->state = 0;   
  46.  
  47. atomic_set(&t->count, 0);   
  48.  
  49. }   
  50.  
  51. // 函数tasklet_kill()用来将一个已经被调度了的tasklet杀死,即将其恢复到未调度的状态。其源码如下所示(kernel/softirq.c):   
  52.  
  53. void tasklet_kill(struct tasklet_struct *t)   
  54.  
  55. {   
  56.  
  57. if (in_interrupt())   
  58.  
  59. printk("Attempt to kill tasklet from interruptn");   
  60.  
  61. while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {   
  62.  
  63. current->state = TASK_RUNNING;   
  64.  
  65. do {   
  66.  
  67. current->policy |= SCHED_YIELD;   
  68.  
  69. schedule();   
  70.  
  71. } while (test_bit(TASKLET_STATE_SCHED, &t->state));   
  72.  
  73. }   
  74.  
  75. tasklet_unlock_wait(t);   
  76.  
  77.  clear_bit(TASKLET_STATE_SCHED, &t->state);   
  78.  
  79. }   
  80.  
  81.  // 多个tasklet可以通过tasklet描述符中的next成员指针链接成一个单向对列。为此,Linux专门在头文件include/linux/interrupt.h中定义了数据结构tasklet_head来描述一个tasklet对列的头部指针。如下所示:   
  82.  
  83.  struct tasklet_head   
  84.  
  85.  {   
  86.  
  87.  struct tasklet_struct *list;   
  88.  
  89.  } __attribute__ ((__aligned__(SMP_CACHE_BYTES)));   
  90.  

        尽管 tasklet 机制是特定于软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一种实现,但是tasklet机制仍然属于softirq机制的整体框架范围内的,因此,它的设计与实现仍然必须坚持“谁触发,谁执行”的思想。为此,Linux为系统中的每一个CPU都定义了一个 tasklet 对列头部,来表示应该有各个CPU负责执行的tasklet对列。如下所示(kernel/softirq.c):

       

  1. struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;  

#p#

        其中,tasklet_vec[]数组用于软中断向量TASKLET_SOFTIRQ,而tasklet_hi_vec[]数组则用于软中断向量HI_SOFTIRQ。也即,如果CPUi(0≤i≤NR_CPUS-1)触发了软中断向量TASKLET_SOFTIRQ,那么对列tasklet_vec[i]中的每一个tasklet都将在CPUi服务于软中断向量TASKLET_SOFTIRQ时被CPUi所执行。同样地,如果CPUi(0≤i≤NR_CPUS-1)触发了软中断向量HI_SOFTIRQ,那么队列tasklet_vec[i]中的每一个tasklet都将CPUi在对软中断向量HI_SOFTIRQ进行服务时被CPUi所执行。

        队列tasklet_vec[I]和tasklet_hi_vec[I]中的各个tasklet是怎样被所CPUi所执行的呢?其关键就是软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ的软中断服务程序——tasklet_action()函数和tasklet_hi_action()函数。下面我们就来分析这两个函数。

       Linux为软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ实现了专用的触发函数和软中断服务函数。其中,tasklet_schedule() 函数和 tasklet_hi_schedule() 函数分别用来在当前CPU上触发软中断向量TASKLET_SOFTIRQ 和 HI_SOFTIRQ,并把指定的tasklet 加入当前CPU所对应的 tasklet 队列中去等待执行。而tasklet_action() 函数和 tasklet_hi_action() 函数则分别是软中断向量 TASKLET_SOFTIRQ 和 HI_SOFTIRQ 的软中断服务函数。在初始化函数 softirq_init() 中,这两个软中断向量对应的描述符softirq_vec[0]和softirq_vec[3]中的action函数指针就被分别初始化成指向函数 tasklet_hi_action() 和函数 tasklet_action()。

    (1)软中断向量TASKLET_SOFTIRQ的触发函数 tasklet_schedule()

       该函数实现在include/linux/interrupt.h头文件中,是一个 inline 函数。其源码如下所示:
 

 

 

 

  1. view sourceprint?01 static inline void tasklet_schedule(struct tasklet_struct *t)   
  2. {   
  3. if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {   
  4. int cpu = smp_processor_id();   
  5. unsigned long flags;   
  6. local_irq_save(flags);   
  7. t->next = tasklet_vec[cpu].list;   
  8. tasklet_vec[cpu].list = t;   
  9. __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);   
  10. local_irq_restore(flags);   
  11. }   
  12. }   

       该函数的参数t指向要在当前CPU上被执行的 tasklet。对该函数的NOTE如下:

       ①调用test_and_set_bit()函数将待调度的 tasklet 的state成员变量的bit[0]位(也即TASKLET_STATE_SCHED位)设置为1,该函数同时还返回TASKLET_STATE_SCHED位的原有值。因此如果bit[0]为的原有值已经为1,那就说明这个tasklet已经被调度到另一个CPU上去等待执行了。由于一个tasklet在某一个时刻只能由一个CPU来执行,因此tasklet_schedule()函数什么也不做就直接返回了。否则,就继续下面的调度操作。

       ②首先,调用 local_irq_save() 函数来关闭当前CPU的中断,以保证下面的步骤在当前CPU上原子地被执行。

       ③然后,将待调度的 tasklet 添加到当前CPU对应的 tasklet 队列的首部。

       ④接着,调用 __cpu_raise_softirq() 函数在当前CPU上触发软中断请求TASKLET_SOFTIRQ。

       ⑤***,调用local_irq_restore() 函数来开当前CPU的中断。

(2)软中断向量TASKLET_SOFTIRQ的服务程序tasklet_action()

       函数tasklet_action()是tasklet机制与软中断向量TASKLET_SOFTIRQ的联系纽带。正是该函数

  1. view sourceprint?01 static inline void tasklet_hi_schedule(struct tasklet_struct *t)   
  2.  
  3. {   
  4.  
  5. if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {   
  6.  
  7. int cpu = smp_processor_id();   
  8.  
  9. unsigned long flags;   
  10.  
  11. local_irq_save(flags);   
  12.  
  13. t->next = tasklet_hi_vec[cpu].list;   
  14.  
  15. tasklet_hi_vec[cpu].list = t;   
  16.  
  17. __cpu_raise_softirq(cpu, HI_SOFTIRQ);   
  18.  
  19. local_irq_restore(flags);   
  20.  
  21. }  
  22.    
  23. }   

将当前CPU的tasklet队列中的各个tasklet放到当前CPU上来执行的。该函数实现在kernel/softirq.c文件中,其源代码如下:

  1. view sourceprint?01 static void tasklet_action(struct softirq_action *a)   
  2.  
  3.  {   
  4.  
  5.  int cpu = smp_processor_id();   
  6.  
  7.  struct tasklet_struct *list;   
  8.  
  9.  local_irq_disable();   
  10.  
  11.  list = tasklet_vec[cpu].list;   
  12.  
  13.  tasklet_vec[cpu].list = NULL;   
  14.  
  15.  local_irq_enable();   
  16.  
  17.  while (list != NULL) {   
  18.  
  19.  struct tasklet_struct *t = list;   
  20.  
  21.  listlist = list->next;   
  22.  
  23.  if (tasklet_trylock(t)) {   
  24.  
  25.  if (atomic_read(&t->count) == 0) {   
  26.  
  27.  clear_bit(TASKLET_STATE_SCHED, &t->state);   
  28.  
  29.  t->func(t->data);   
  30.  
  31.  /*   
  32.  
  33.  * talklet_trylock() uses test_and_set_bit that imply   
  34.  
  35.  * an mb when it returns zero, thus we need the explicit   
  36.  
  37.  * mb only here: while closing the critical section.   
  38.  
  39.  */   
  40.  
  41.  #ifdef CONFIG_SMP   
  42.  
  43.  smp_mb__before_clear_bit();   
  44.  
  45.  #endif   
  46.  
  47.  tasklet_unlock(t);   
  48.  
  49.  continue;   
  50.  
  51.  }   
  52.  
  53.  tasklet_unlock(t);   
  54.  
  55.  }   
  56.  
  57.  local_irq_disable();   
  58.  
  59.  t->next = tasklet_vec[cpu].list;   
  60.  
  61.  tasklet_vec[cpu].list = t;   
  62.  
  63.  __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);   
  64.  
  65.  local_irq_enable();   
  66.  
  67.  }   
  68.  
  69.  }   

注释如下:

       ①首先,在当前CPU关中断的情况下,“原子”地读取当前CPU的tasklet队列头部指针,将其保存到局部变量list指针中,然后将当前CPU的tasklet队列头部指针设置为NULL,以表示理论上当前CPU将不再有tasklet需要执行(但***的实际结果却并不一定如此,下面将会看到)。

       ②然后,用一个while{}循环来遍历由list所指向的tasklet队列,队列中的各个元素就是将在当前CPU上执行的tasklet。循环体的执行步骤如下:

        用指针t来表示当前队列元素,即当前需要执行的tasklet。
        更新list指针为list->next,使它指向下一个要执行的tasklet。
        用tasklet_trylock()宏试图对当前要执行的tasklet(由指针t所指向)进行加锁,如果加锁成功(当前没有任何其他CPU正在执行这个tasklet),则用原子读函数atomic_read()进一步判断count成员的值。如果count为0,说明这个tasklet是允许执行的,于是:a先清除TASKLET_STATE_SCHED位;然后,调用这个tasklet的可执行函数func;执行barrier()操作;调用宏tasklet_unlock()来清除TASKLET_STATE_RUN位。***,执行continue语句跳过下面的步骤,回到while循环继续遍历队列中的下一个元素。如果count不为0,说明这个tasklet是禁止运行的,于是调用tasklet_unlock()清除前面用tasklet_trylock()设置的TASKLET_STATE_RUN位。
        如果tasklet_trylock()加锁不成功,或者因为当前tasklet的count值非0而不允许执行时,我们必须将这个tasklet重新放回到当前CPU的tasklet队列中,以留待这个CPU下次服务软中断向量TASKLET_SOFTIRQ时再执行。为此进行这样几步操作:先关CPU中断,以保证下面操作的原子性。把这个tasklet重新放回到当前CPU的tasklet队列的首部;调用__cpu_raise_softirq()函数在当前CPU上再触发一次软中断请求TASKLET_SOFTIRQ;开中断。 ***,回到while循环继续遍历队列。 
 

#p#

       (3)软中断向量HI_SOFTIRQ的触发函数tasklet_hi_schedule()

该函数与tasklet_schedule()几乎相同,其源码如下(include/linux/interrupt.h):
 

 

 

  1.  
  2. view sourceprint?01 static inline void tasklet_hi_schedule(struct tasklet_struct *t)   
  3.  
  4.  {   
  5.  
  6.  if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {   
  7.  
  8.  int cpu = smp_processor_id();   
  9.  
  10.  unsigned long flags;   
  11.  
  12.  local_irq_save(flags);   
  13.  
  14.  t->next = tasklet_hi_vec[cpu].list;   
  15.  
  16.  tasklet_hi_vec[cpu].list = t;   
  17.  
  18.  __cpu_raise_softirq(cpu, HI_SOFTIRQ);   
  19.  
  20.  local_irq_restore(flags);   
  21.  
  22.  }   
  23.  
  24.  }   

     (4)软中断向量HI_SOFTIRQ的服务函数tasklet_hi_action()

该函数与tasklet_action()函数几乎相同,其源码如下(kernel/softirq.c):


 

 

 

  1. view sourceprint?01 static void tasklet_hi_action(struct softirq_action *a)   
  2.  
  3.  {   
  4.  
  5.  int cpu = smp_processor_id();   
  6.  
  7.  struct tasklet_struct *list;   
  8.  
  9.  local_irq_disable();   
  10.  
  11.  list = tasklet_hi_vec[cpu].list;   
  12.  
  13.  tasklet_hi_vec[cpu].list = NULL;   
  14.  
  15.  local_irq_enable();   
  16.    
  17.  while (list != NULL)  
  18.  
  19.  {   
  20.  
  21.  struct tasklet_struct *t = list;   
  22.  
  23.  listlist = list->next;   
  24.  
  25.  if (tasklet_trylock(t)) {   
  26.  
  27.  if (atomic_read(&t->count) == 0) {   
  28.  
  29.  clear_bit(TASKLET_STATE_SCHED, &t->state);   
  30.  
  31.  t->func(t->data);   
  32.  
  33.  tasklet_unlock(t);   
  34.  
  35.  continue;   
  36.  
  37.  }   
  38.  
  39.  tasklet_unlock(t);   
  40.  
  41.  }   
  42.  
  43.  local_irq_disable();   
  44.  
  45.  t->next = tasklet_hi_vec[cpu].list;   
  46.  
  47.  tasklet_hi_vec[cpu].list = t;   
  48.  
  49.  __cpu_raise_softirq(cpu, HI_SOFTIRQ);   
  50.  
  51.  local_irq_enable();   
  52.  
  53.  }   
  54.  
  55.  }   

        Bottom Half 机制在新的softirq机制中被保留下来,并作为softirq框架的一部分。其实现也似乎更为复杂些,因为它是通过 tasklet 机制这个中介桥梁来纳入softirq框架中的。实际上,软中断向量 HI_SOFTIRQ 是内核专用于执行BH函数的。原有的32个BH函数指针被保留,定义在kernel/softirq.c文件中:static void (*bh_base[32])(void);


       但是,每个BH函数都对应有一个tasklet,并由tasklet的可执行函数func来负责调用相应的bh函数(func函数的参数指定调用哪一个BH函数)。与32个BH函数指针相对应的tasklet的定义如下所示(kernel/softirq.c):struct tasklet_struct bh_task_vec[32];


       上述tasklet数组使系统全局的,它对所有的CPU均可见。由于在某一个时刻只能有一个CPU在执行BH函数,因此定义一个全局的自旋锁来保护BH函数,如下所示(kernel/softirq.c):spinlock_t global_bh_lock = SPIN_LOCK_UNLOCKED;


       在softirq机制的初始化函数softirq_init()中将bh_task_vec[32]数组中的每一个tasklet中的func函数指针都设置为指向同一个函数bh_action,而data成员(也即func函数的调用参数)则被设置成该tasklet在数组中的索引值。因此,bh_action()函数将负责相应地调用参数所指定的bh函数。该函数是连接 tasklet机制与Bottom Half机制的关键所在。

该函数的源码如下(kernel/softirq.c):
 

  1. view sourceprint?1 void __init softirq_init()   
  2.  
  3. 2 {   
  4.  
  5. 3 ……   
  6.  
  7. 4 for (i=0; i<32; i++)   
  8.  
  9. 5 tasklet_init(bh_task_vec+i, bh_action, i);   
  10.  
  11. 6 ……   
  12.  
  13. 7 }   
  14.  
  15.  
  16.  
  17. view sourceprint?01 static void bh_action(unsigned long nr)   
  18.  
  19.  {   
  20.  
  21.  int cpu = smp_processor_id();   
  22.  
  23.  if (!spin_trylock(&global_bh_lock))   
  24.  
  25.  goto resched;   
  26.  
  27.  if (!hardirq_trylock(cpu))   
  28.  
  29.  goto resched_unlock;   
  30.  
  31.  if (bh_base[nr])   
  32.  
  33.  bh_base[nr]();   
  34.  
  35.  hardirq_endlock(cpu);   
  36.  
  37.  spin_unlock(&global_bh_lock);   
  38.  
  39.  return;   
  40.  
  41.  resched_unlock:   
  42.  
  43.  spin_unlock(&global_bh_lock);   
  44.  
  45.  resched:   
  46.  
  47.  mark_bh(nr);   
  48.  
  49.  }   

对该函数的注释如下:

         ①首先,调用spin_trylock()函数试图对自旋锁global_bh_lock进行加锁,同时该函数还将返回自旋锁global_bh_lock的原有值的非。因此,如果global_bh_lock已被某个CPU上锁而为非0值(那个CPU肯定在执行某个BH函数),那么spin_trylock()将返回为0表示上锁失败,在这种情况下,当前CPU是不能执行BH函数的,因为另一个CPU正在执行BH函数,于是执行goto语句跳转到resched程序段,以便在当前CPU上再一次调度该BH函数。

        ②调用hardirq_trylock()函数锁定当前CPU,确保当前CPU不是处于硬件中断请求服务中,如果锁定失败,跳转到resched_unlock程序段,以便先对global_bh_lock解锁,在重新调度一次该BH函数。

       ③此时,我们已经可以放心地在当前CPU上执行BH函数了。当然,对应的BH函数指针bh_base[nr]必须有效才行。

       ④从BH函数返回后,先调用hardirq_endlock()函数(实际上它什么也不干,调用它只是为了保此加、解锁的成对关系),然后解除自旋锁global_bh_lock,***函数就可以返回了。

       ⑤resched_unlock程序段:先解除自旋锁global_bh_lock,然后执行reched程序段。

       ⑥resched程序段:当某个CPU正在执行BH函数时,当前CPU就不能通过bh_action()函数来调用执行任何BH函数,所以就通过调用mark_bh()函数在当前CPU上再重新调度一次,以便将这个BH函数留待下次软中断服务时执行。

(1)init_bh()函数

        该函数用来在bh_base[]数组登记一个指定的bh函数,如下所示(kernel/softirq.c):

  1. view sourceprint?1 void init_bh(int nr, void (*routine)(void))   
  2.  
  3.  {   
  4.  
  5.  bh_base[nr] = routine;   
  6.  
  7.  mb();   
  8.  
  9.  }   

(2)remove_bh()函数

该函数用来在bh_base[]数组中注销指定的函数指针,同时将相对应的tasklet杀掉。

如下所示(kernel/softirq.c):

  1. view sourceprint?1 void remove_bh(int nr)   
  2.  
  3.  {   
  4.  
  5.  tasklet_kill(bh_task_vec+nr);   
  6.  
  7.  bh_base[nr] = NULL;   
  8.  
  9.  }   

(3)mark_bh()函数

       该函数用来向当前CPU标记由一个BH函数等待去执行。它实际上通过调用tasklet_hi_schedule()函数将相应的tasklet加入到当前CPU的tasklet队列tasklet_hi_vec[cpu]中,然后触发软中断请求HI_SOFTIRQ,如下所示(include/linux/interrupt.h):
 

  1. view sourceprint?1 static inline void mark_bh(int nr)   
  2.  
  3. {   
  4.  
  5. tasklet_hi_schedule(bh_task_vec+nr);   
  6.  
  7. }   

       在32个BH函数指针中,大多数已经固定用于一些常见的外设,比如:第0个BH函数就固定地用于时钟中断。Linux在头文件include/linux/interrupt.h中定义了这些已经被使用的BH函数所引,如下所示:

  1. view sourceprint?01 enum  
  2.  
  3. {   
  4.  
  5.  TIMER_BH = 0,   
  6.  
  7.  TQUEUE_BH,   
  8.  
  9.  DIGI_BH,   
  10.  
  11.  SERIAL_BH,   
  12.  
  13.  RISCOM8_BH,   
  14.  
  15.  SPECIALIX_BH,   
  16.  
  17.  AURORA_BH,   
  18.  
  19.  ESP_BH,   
  20.  
  21.  SCSI_BH,   
  22.  
  23.  IMMEDIATE_BH,   
  24.  
  25.  CYCLADES_BH,   
  26.  
  27.  CM206_BH,   
  28.  
  29.  JS_BH,   
  30.  
  31.  MACSERIAL_BH,   
  32.  
  33.  ISICOM_BH   
  34.  
  35.  };   

         从以上的例子可以看出,所谓小任务机制就是为下半部分函数提供的一种执行机制,也就是说推迟处理的事情由tasklet_handler实现。经过小任务封装以后再交给内核去处理。以上就是tasklet机制在linux中的实现  ,使得tasklet机制与一般意义上的软中断有所不同

【编辑推荐】

  1. 全面认识Flex事件机制
  2. 网站运维之道 监控与报警机制
  3. 三种Flex数据访问机制
  4. 提高Linux操作系统性能
  5. 热门Linux桌面环境挨个看
  6. Linux操作系统中运行ASP.NET 4

 

责任编辑:zhaolei
相关推荐

2011-04-22 17:29:37

Linux网卡

2013-10-31 16:29:10

Linux内核

2010-01-07 13:27:22

Linux驱动程序

2011-01-10 18:21:38

linux编写程序

2021-11-29 07:55:45

Linux GPIO Linux 系统

2009-12-07 09:39:04

Linux设备驱动硬件通信

2009-07-06 18:17:46

JDBC驱动程序

2021-12-06 07:47:36

Linux 驱动程序Linux 系统

2009-10-23 10:25:27

驱动程序技巧

2022-05-13 09:14:47

NVidia开源Linux

2011-08-16 16:32:13

Linux驱动程序

2018-11-26 08:45:29

Linux驱动程序命令

2009-11-30 14:51:00

Linux设置无线网卡

2022-05-23 13:17:32

Linux开源NVIDIA

2017-10-24 17:03:48

Linux驱动程序编译

2009-08-12 18:20:39

C#事件驱动程序

2017-03-03 08:40:32

2021-11-16 06:55:36

Linux字符设备

2009-07-20 18:01:38

Oracle JDBC

2023-01-17 14:27:27

Linux微软RNDIS
点赞
收藏

51CTO技术栈公众号