kernel中断分析七——tasklet

来源:互联网 发布:正的网络外部性 编辑:程序博客网 时间:2024/06/04 21:02

Abstract

在Kernel 中断分析六——softirq中,分析了软中断的处理流程,那么bottom half还剩下tasklet与workqueue。tasklet是在软中断基础上实现的一种延迟机制,当然同样运行在中断上下文,而workqueue运行在进程上下文,允许睡眠。

Tasklet

kernel中有定义了十种软中断类型,其中HI_SOFTIRQ、TASKLET_SOFTIRQ用于实现tasklet,区别在于HI_SOFTIRQ的优先级较高。

tasklet_struct

kernel中用tasklet_struct来描述一个tasklet,用DECLARE_TASKLET来声明一个tasklet。

443 /* Tasklets --- multithreaded analogue of BHs.444445    Main feature differing them of generic softirqs: tasklet-----1446    is running only on one CPU simultaneously.447448    Main feature differing them of BHs: different tasklets-------2449    may be run simultaneously on different CPUs.450451    Properties:452    * If tasklet_schedule() is called, then tasklet is guaranteed453      to be executed on some cpu at least once after this.454    * If the tasklet is already scheduled, but its execution is still not455      started, it will be executed only once.456    * If this tasklet is already running on another CPU (or schedule is called457      from tasklet itself), it is rescheduled for later.458    * Tasklet is strictly serialized wrt itself, but not459      wrt another tasklets. If client needs some intertask synchronization,460      he makes it with spinlocks.461  */462463 struct tasklet_struct464 {465     struct tasklet_struct *next;466     unsigned long state;467     atomic_t count;468     void (*func)(unsigned long);469     unsigned long data;470 };471472 #define DECLARE_TASKLET(name, func, data) \473 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

在分析代码之前先说明tasklet的特点,以便带着问题分析。
以上的注释概括了tasklet的核心思想:
1. 与普通软中断的区别在于:相同的tasklet同一时间只能在一个CPU上执行,即相同tasklet不具有并发性,相同的tasklet串行执行。
2. 与其他BHs(其实就是softirq和workqueue)的区别在于:不同的tasklet可以同时运行在不同的CPU上。不同的tasklet并行执行。

总结以上两条,可以发现软中断和tasklet的区别:
softirq:软中断可以并发运行在不同CPU上,即多个CPU可能同时调用同一块软中断处理的代码,所以软中断处理是可重入的,这就要求软中断处理函数中访问数据时做好保护工作。
tasklet:相同的tasklet在不同处理器上串行执行,不同的tasklet可在不同处理器上并行执行。即tasklet不需要考虑重入,比如,对于某个driver的tasklet,任何时刻,只有一个CPU会执行相关的代码片。

tasklet_schedule

509 static inline void tasklet_schedule(struct tasklet_struct *t)510 {511     if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))     ------1512         __tasklet_schedule(t);513 }
446 void __tasklet_schedule(struct tasklet_struct *t)447 {448     unsigned long flags;449450     local_irq_save(flags);                                    -------2451     t->next = NULL;                                           -------3452     *__this_cpu_read(tasklet_vec.tail) = t;                   -------4453     __this_cpu_write(tasklet_vec.tail, &(t->next));454     raise_softirq_irqoff(TASKLET_SOFTIRQ);                    -------5455     local_irq_restore(flags);                                 -------6456 }457 EXPORT_SYMBOL(__tasklet_schedule);443 static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);      -------4435 /*436  * Tasklets437  */438 struct tasklet_head {439     struct tasklet_struct *head;440     struct tasklet_struct **tail;441 };
  1. tasklet_schedule表示将要schedule传入的tasklet,如果该tasklet已经被设置了TASKLET_STATE_SCHED,则退出;否则调用__tasklet_schedule。这说明,同一个tasklet,在处于schedule状态下,再尝试进行schedule,实际上只处理一次。
  2. 禁用本地CPU中断
  3. 只schedule一个tasklet,将其next指针设置成NULL
  4. 将该tasklet加入到tasklet_vec链表中尾部。kernel为每个CPU定义了一个tasklet_vec链表,保存需要schedule的tasklet。
  5. 触发TASKLET_SOFTIRQ对应的软中断,前几篇分析的很详细了。
  6. 打开本地中断

tasklet_action

TASKLET_SOFTIRQ软中断触发以后会调用对应的中断处理函数,即tasklet_action

482 static void tasklet_action(struct softirq_action *a)483 {484     struct tasklet_struct *list;485486     local_irq_disable();                 ------------1487     list = __this_cpu_read(tasklet_vec.head);    ----2488     __this_cpu_write(tasklet_vec.head, NULL);   -----3489     __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);490     local_irq_enable();                      --------4491492     while (list) {          ---------------------5493         struct tasklet_struct *t = list;    -----5.1494495         list = list->next;                  -----5.2496497         if (tasklet_trylock(t)) {           -----5.3498             if (!atomic_read(&t->count)) {  -----5.4499                 if (!test_and_clear_bit(TASKLET_STATE_SCHED,-----5.5500                             &t->state))501                     BUG();502                 t->func(t->data);           -------5.6503                 tasklet_unlock(t);          -------5.7504                 continue;505             }506             tasklet_unlock(t);507         }508509         local_irq_disable();                --------6510         t->next = NULL;                     511         *__this_cpu_read(tasklet_vec.tail) = t;512         __this_cpu_write(tasklet_vec.tail, &(t->next));513         __raise_softirq_irqoff(TASKLET_SOFTIRQ);514         local_irq_enable();515     }516 }
  1. 关闭本地CPU中断
  2. 获取tasklet_vec链表
  3. 清空tasklet_vec链表
  4. 打开本地CPU中断
  5. 判断list是否为空
    5.1 获取第一个tasklet
    5.2 获取下一个tasklet,方便下一次遍历
    5.3 检测TASKLET_STATE_RUN有没有被设置,如果有,说明该tasklet正在其他CPU上处理。否则设置该状态位为1,表示该tasklet正在本地CPU上被处理。此时其他CPU如果调用tasklet_action,会tasklet_trylock失败,进入step 6。
    5.4 tasklet的count表示该tasklet是否被disable,0 enable,1 disable。如果被disable,那么清除TASKLET_STATE_RUN,进入step6。
    5.5 检测TASKLET_STATE_SCHED标志位是否为0,如果为0,那么此时抛一个bug出来,因为正常情况下,前面的处理流程中设置了TASKLET_STATE_SCHED为1,并且其他CPU是没有机会再设置TASKLET_STATE_SCHED的。如果为1,那么清0,此时TASKLET_STATE_SCHED为0,TASKLET_STATE_RUN为1,tasklet从schedule状态变成了run状态。
    5.6 执行tasklet的处理函数
    5.7 清除该tasklet的TASKLET_STATE_RUN标志位,并且调用continue进入下一次循环。
  6. 走到这一步,有两种可能:
    a. tasklet_trylock失败,该tasklet正在被其他CPU处理
    b. 该tasklet被disable了
    以上两种情况都会将TASKLET_STATE_RUN标志位清0,并将该tasklet添加到tasklet_vec的尾部,重新触发一次TASKLET_SOFTIRQ软中断,等待下一次处理。所以,无论tasklet能否顺利执行,该tasklet始终运行在schedule它的CPU上

可以想象一下,tasklet_vec中有N个tasklet,满足条件的tasklet会调用它的处理函数。如果有tasklet正在其他CPU上运行,那么本CPU上与之相同的tasklet放到tasklet_vec尾部,下一次再处理。如此就实现了相同tasklet串行执行。至于不同的tasklet,在多个CPU上并发执行时没问题的。

0 0
原创粉丝点击