kernel中断分析七——tasklet

本文深入解析了Linux Kernel中Tasklet的工作原理与实现机制,对比了Tasklet与软中断的区别,阐述了Tasklet如何确保相同的Tasklet在不同处理器上串行执行,而不同的Tasklet则可以在不同处理器上并行执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 };
  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;
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 }
  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上并发执行时没问题的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值