Linux学习第25节,内核中的“中断”下半部tasklet机制
发表于: 2019-02-16 20:10:37 | 已被阅读: 464 | 分类于: Linux笔记
上一节较为详细的讨论了 Linux 内核中的“软中断”机制,通过这种模拟硬件中断的设计,中断处理程序可以设计的尽可能小而快,而将余下的较为复杂的工作放入“稍后”执行的软中断中。
 
            不过,软中断在不同的处理器上可以同时运行多个,所以任何共享数据都需要做好严格的同步管理,如果通过互斥的加锁方式来防止它自身的并发执行,那么使用软中断就没有意义了。
因此,大部分软中断处理程序,一般都不显式的加锁,而是通过一些技巧,例如采取单处理器数据的方式来实现数据共享。既然如此,Linux 内核倒不如统一提供一个机制用于避免使用显式加锁同步共享数据了,事实上,内核的确提供了 tasklet 机制。
Linux 内核的 tasklet 机制
 
            还记得吗,上一节提到软中断的信息存储在数组 softirq_vec[32] 里,do_softirq() 函数是按照先后顺序执行 softirq_vec 存储的软中断处理函数的,所以数组 softirq_vec 的索引实际上也是一种形式的“优先级”。
Linux 内核中 tasklet 的数据结构由结构体 tasklet_struct 给出,它的 C语言代码如下,请看:
struct tasklet_struct
{
     struct tasklet_struct *next;
     unsigned long state;
     atomic_t count;
     void (*func)(unsigned long);
     unsigned long data;
};
 
            state 则在 0、TASKLET_STATE_SCHED 和 TASKLET_STATE_RUN 之间取值,TASKLET_STATE_SCHED 标志位表示该 tasklet 已准备好投入运行,TASKLET_STATE_RUN 则表示该 tasklet 已被投入运行,处理器在执行 func 之前会检测该标志位,防止同一类型的 tasklet 同时在多个处理器上被执行。
tasklet 的调度
已调度的 tasklet 存放在两个但处理器数据结构 tasklet_vec 和 tasklet_hi_vec 中,Linux 内核是如下定义的,请看C语言代码如下:
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec) = { NULL };
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec) = { NULL };
 
             
            因为 tasklet_vec 和 tasklet_hi_vec 非常相似,只是优先级不同而已,所以下文主要以 tasklet_vec 的相关设计和实现为例做分析,tasklet_hi_vec 的分析是类似的。
tasklet 的调度主要由 tasklet_schedule() 函数实现,它的 C语言代码如下,请看:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
     if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
         __tasklet_schedule(t);
}
 
            
             void __tasklet_schedule(struct tasklet_struct *t)
{       
     unsigned long flags;
     local_irq_save(flags);
     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_restore(flags);
}
 
            
            
            运行 tasklet 处理函数
现在知道 Linux 内核是如何调度 tasklet 的了,那内核是如何执行 tasklet 的处理函数的呢?这一工作其实是由 tasklet_action() 函数完成的,它的C语言代码如下,请看:
    396 static void tasklet_action(struct softirq_action *a)
-   397 {   
|   398     struct tasklet_struct *list;
|   399 
|   400     local_irq_disable();
|   401     list = __get_cpu_var(tasklet_vec).head;
|   402     __get_cpu_var(tasklet_vec).head = NULL;
|   403     __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
|   404     local_irq_enable();
|   405 
|-  406     while (list) {
||  407         struct tasklet_struct *t = list;
||  408 
||  409         list = list->next;
||  410 
||- 411         if (tasklet_trylock(t)) {
23- 412             if (!atomic_read(&t->count)) {
234 413                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
234 414                     BUG();
234 415                 t->func(t->data);
234 416                 tasklet_unlock(t);
234 417                 continue;
234 418             }
||| 419             tasklet_unlock(t);
||| 420         }
||  421 
||  422         local_irq_disable();
||  423         t->next = NULL;
||  424         *__get_cpu_var(tasklet_vec).tail = t;
||  425         __get_cpu_var(tasklet_vec).tail = &(t->next);
||  426         __raise_softirq_irqoff(TASKLET_SOFTIRQ);
||  427         local_irq_enable();
||  428     }
|   429 }
 
            那么 tasklet_action() 函数什么时候会运行呢?按照前面说的,tasklet 本质上也是一种“软中断”,那么 tasklet_action() 函数肯定也被注册到 softiqr_vec 了,查看相关调用也的确如此,请看下面的 C语言代码:
    495 void __init softirq_init(void)
-   496 {   
|   497     int cpu;
|   498 
|-  499     for_each_possible_cpu(cpu) {
||  500         per_cpu(tasklet_vec, cpu).tail =
||  501             &per_cpu(tasklet_vec, cpu).head;
||  502         per_cpu(tasklet_hi_vec, cpu).tail =
||  503             &per_cpu(tasklet_hi_vec, cpu).head;
||  504     }
|   505     
|   506     open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
|   507     open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
|   508 }
 
            
            tasklet 处理函数
现在知道了 Linux 内核中的 tasklet 是如何被调度和执行的了,显然,tasklet 处理函数的原型如下,请看C语言代码:
void tasklet_handler(unsigned long data);
需要说明的是,tasklet 是依赖软中断实现的,而软中断仍然处于中断上下文中,因此 tasklet 虽然允许响应中断,但是和软中断一样不能睡眠,因此我们不能在 tasklet_handler() 中使用信号量或者其他形式的阻塞函数。