软中断/tasklet/工作队列

软中断、tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者。本文重点在于介绍这三者之间的关系。(函数细节将不会在本文中出现,可以参考文献,点这里

 

(1)上半部和下半部的区别
上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。
两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面四条:
a)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
b)如果一个任务和硬件相关,将其放在中断处理程序中执行。
c)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
d)其他所有任务,考虑放在下半部去执行。

 

(2)为什么要使用软中断?
软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

 

(3)为什么要使用tasklet?(tasklet和软中断的区别)
由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
b)多个不同类型的tasklet可以并行在多个CPU上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。

 

(4)为什么要使用工作队列work queue?(work queue和软中断的区别)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
因此在2.6版的内核中出现了在内核态运行的工作队列(替代了2.4内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,以完成不同的工作。

 

对于softirq,linux kernel中是在中断处理程序执行的,具体的路径为:

  1. do_IRQ() --> irq_exit() --> invoke_softirq() --> do_softirq() --> __do_softirq()


在__do_softirq()中有这么一段代码:

  1.         do {
  2.                 if (pending & 1) {
  3.                         h->action(h);
  4.                         rcu_bh_qsctr_inc(cpu);
  5.                 }
  6.                 h++;
  7.                 pending >>= 1;
  8.         } while (pending);
复制代码


你看,这里就是对softirq进行处理了,因为pengding是一个__u32的类型,所以每一位都对应了一种softirq,正好是32种(linux kernel中实际上只使用了前6种 ).
h->action(h),就是运行softirq的处理函数。

对于tasklet,前面已经说了,是一种特殊的softirq,具体就是第0和第5种softirq,所以说tasklet是基于softirq来实现的。
tasklet既然对应第0和第5种softirq,那么就应该有对应的处理函数,以便h->action()会运行tasklet的处理函数。
我们看代码:

  1. softirq.c
  2. void __init softirq_init(void)  
  3. {
  4.         open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
  5.         open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
  6. }
复制代码


这里注册了两种tasklet所在的softirq的处理函数,分别对应高优先级的tasklet和低优先级的tasklet。

我们看低优先级的吧(高优先级的也一样)。

  1. static void tasklet_action(struct softirq_action *a)
  2. {
  3.         struct tasklet_struct *list;
  4.         local_irq_disable();
  5.         list = __get_cpu_var(tasklet_vec).list;
  6.         __get_cpu_var(tasklet_vec).list = NULL;
  7.         local_irq_enable();
  8.         while (list) {
  9.                 struct tasklet_struct *t = list;
  10.                 list = list->next;
  11.                 if (tasklet_trylock(t)) {
  12.                         if (!atomic_read(&t->count)) {
  13.                                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
  14.                                         BUG();
  15.                                 t->func(t->data);
  16.                                 tasklet_unlock(t);
  17.                                 continue;
  18.                         }
  19.                         tasklet_unlock(t);
  20.                 }
  21.                 local_irq_disable();
  22.                 t->next = __get_cpu_var(tasklet_vec).list;
  23.                 __get_cpu_var(tasklet_vec).list = t;
  24.                 __raise_softirq_irqoff(TASKLET_SOFTIRQ);  
  25.                 local_irq_enable();
  26.         }
  27. }
复制代码


你看,在运行softirq的处理时(__do_softirq),对于

  1.         do {
  2.                 if (pending & 1) {
  3.                         h->action(h);
  4.                         rcu_bh_qsctr_inc(cpu);
  5.                 }
  6.                 h++;
  7.                 pending >>= 1;
  8.         } while (pending);
复制代码


如果tasklet有任务需要处理,会运行到h->action(),这个函数指针就会指向tasklet_action(),然后在tasklet_action()里再去执行tasklet对应的各个任务,这些任务都是挂在一个全局链表里面的,具体的代码这里就不分析了。

另外, softirq在smp中是可能被同时运行的,所以softirq的处理函数必须被编写成可重入的函数。
但tasklet是不会在多个cpu之中同时运行的,所以tasklet的处理函数可以编写成不可重入的函数,这样就减轻了编程人员的负担。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章