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.
444
445 Main feature differing them of generic softirqs: tasklet-----1
446 is running only on one CPU simultaneously.
447
448 Main feature differing them of BHs: different tasklets-------2
449 may be run simultaneously on different CPUs.
450
451 Properties:
452 * If tasklet_schedule() is called, then tasklet is guaranteed
453 to be executed on some cpu at least once after this.
454 * If the tasklet is already scheduled, but its execution is still not
455 started, it will be executed only once.
456 * If this tasklet is already running on another CPU (or schedule is called
457 from tasklet itself), it is rescheduled for later.
458 * Tasklet is strictly serialized wrt itself, but not
459 wrt another tasklets. If client needs some intertask synchronization,
460 he makes it with spinlocks.
461 */
462
463 struct tasklet_struct
464 {
465 struct tasklet_struct *next;
466 unsigned long state;
467 atomic_t count;
468 void (*func)(unsigned long);
469 unsigned long data;
470 };
471
472 #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)) ------1
512 __tasklet_schedule(t);
513 }
446 void __tasklet_schedule(struct tasklet_struct *t)
447 {
448 unsigned long flags;
449
450 local_irq_save(flags); -------2
451 t->next = NULL; -------3
452 *__this_cpu_read(tasklet_vec.tail) = t; -------4
453 __this_cpu_write(tasklet_vec.tail, &(t->next));
454 raise_softirq_irqoff(TASKLET_SOFTIRQ); -------5
455 local_irq_restore(flags); -------6
456 }
457 EXPORT_SYMBOL(__tasklet_schedule);
443 static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); -------4
435 /*
436 * Tasklets
437 */
438 struct tasklet_head {
439 struct tasklet_struct *head;
440 struct tasklet_struct **tail;
441 };
- tasklet_schedule表示将要schedule传入的tasklet,如果该tasklet已经被设置了TASKLET_STATE_SCHED,则退出;否则调用__tasklet_schedule。这说明,同一个tasklet,在处于schedule状态下,再尝试进行schedule,实际上只处理一次。
- 禁用本地CPU中断
- 只schedule一个tasklet,将其next指针设置成NULL
- 将该tasklet加入到tasklet_vec链表中尾部。kernel为每个CPU定义了一个tasklet_vec链表,保存需要schedule的tasklet。
- 触发TASKLET_SOFTIRQ对应的软中断,前几篇分析的很详细了。
- 打开本地中断
tasklet_action
TASKLET_SOFTIRQ软中断触发以后会调用对应的中断处理函数,即tasklet_action
482 static void tasklet_action(struct softirq_action *a)
483 {
484 struct tasklet_struct *list;
485
486 local_irq_disable(); ------------1
487 list = __this_cpu_read(tasklet_vec.head); ----2
488 __this_cpu_write(tasklet_vec.head, NULL); -----3
489 __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
490 local_irq_enable(); --------4
491
492 while (list) { ---------------------5
493 struct tasklet_struct *t = list; -----5.1
494
495 list = list->next; -----5.2
496
497 if (tasklet_trylock(t)) { -----5.3
498 if (!atomic_read(&t->count)) { -----5.4
499 if (!test_and_clear_bit(TASKLET_STATE_SCHED,-----5.5
500 &t->state))
501 BUG();
502 t->func(t->data); -------5.6
503 tasklet_unlock(t); -------5.7
504 continue;
505 }
506 tasklet_unlock(t);
507 }
508
509 local_irq_disable(); --------6
510 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 }
- 关闭本地CPU中断
- 获取tasklet_vec链表
- 清空tasklet_vec链表
- 打开本地CPU中断
- 判断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进入下一次循环。 - 走到这一步,有两种可能:
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上并发执行时没问题的。