layout | title | category | description | tags |
---|---|---|---|---|
post |
tasklet |
中断和异常 |
tasklet... |
tasklet |
软中断是将操作推迟到未来某一个时刻执行的最好方法,但延迟函数执行以及处理机制非常复杂。因为多个处理器是可以同时并且独立地处理软中断,同一个软中断的处理程序例程可以在几个CPU上同时运行。
对软中断的效率来说,这是一个关键,多处理器上的网络实现依靠于此。但处理程序例程的设计必须是完全可重入且线程安全的。另外,临界区必须使用自旋锁保护,或者其他的IPC机制1。
tasklet和工作队列是延迟函数执行工作的机制,其实现基于软中断,但是tasklet更易于使用,因而是更适用于设备的驱动程序一起其他一般性的内核代码。
tasklet是『小进程』,执行一些迷你的任务,对这些任务使用全功能进程会过于浪费。tasklet的结构定义如下:
{% highlight c++ %} struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; {% endhighlight %}
从设备驱动程序来看,最重要的成员是func,它指向一个函数的地址,该函数的执行将被延期执行。data用作该函数执行时的参数。其中next是一个指针,用于建立一个tasklet_struct的链表,这容许多个任务能够派对执行。其中state表示任务的当前状态,类似于真正的进程但是只有两个选项。
- 当tasklet注册到内核,等待调度执行时,将设置TASKLET_STATE_SCHED。
- TASKLET_STATE_RUN表示tasklet当前正在执行。
第二个状态只在SMP系统上有用,用于保护tasklet在多个处理器上并行执行。原子计数器count用于禁用已经调度的tasklet,如果其值不等于0,在接下来执行的所有等待的tasklet任务时,将忽略对应的tasklet。
tasklet_schedule将一个tasklet注册到系统中:
{% highlight c++ %} static inline void tasklet_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); } {% endhighlight %}
其中test_and_set_bit函数是一个原子操作,其作用是设置*&t->state的TASKLET_STATE_SCHED位并返回原值。如果设置了TASKLET_STATE_SCHED标志位,则已经注册了tasklet,则返回。否则,将该tasklet至于一个链表的起始,其表头特定于CPU的变量tasklet_vec向量,该链表包含了所有注册的tasklet*。
在注册了一个tasklet之后, tasklet链表即标记为即将进行处理。
tasklet生命周期中最为重要的部分就是执行,因为tasklet基于软中断实现,它们总是在处理软中断时执行。
tasklet关联到TASKLET_SOFTIRQ软中断,因而,调用raise_softirq(TASKLET_SOFTIRQ)就可以在下一个恰当的实际执行当前处理器的tasklet。内核使用tasklet_action作为该软中断的action函数。
这个函数首先确定特定于CPU的链表,其中保存了标记要执行的各个tasklet,它接下来将表头重定向到函数局部的一个数据项,相当于从外部公开的链表删除了所有表项,接下来,函数在循环中逐一处理tasklet。
{% highlight c++ %} static void tasklet_action(struct softirq_action *a) { struct tasklet_struct *list; // 禁用本地irq local_irq_disable(); // 从外部链表删除当前CPU的tasklet项 list = __get_cpu_var(tasklet_vec).head; __get_cpu_var(tasklet_vec).head = NULL; __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head; // 启用本地irq local_irq_enable();
// 循环链表处理
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
// 清楚相应的比特位
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
// 处理注册的函数
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
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_enable();
}
} {% endhighlight %}
其中test_and_clear_bit也是一个原子操作,与test_and_set_bit相反,其作用是清除相应的比特位。
因为一个tasklet只能在一个处理器上执行一次,但其他的tasklet可以并行执行,所以需要特定于tasklet的锁。state状态用作锁变量,在执行一个tasklet的处理程序函数之前,内核使用tasklet_trylock检查tasklet的状态是否是TASKLET_STATE_RUN。
{% highlight c++ %} static inline int tasklet_trylock(struct tasklet_struct *t) { return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state); } {% endhighlight %}
tasklet_trylock函数检查如果对应的比特位尚未设置,则设置该比特位。
如果count成员不为0,则该tasklet已经停用,在这种情况下就不执行相关的代码。否则就通过t->func(t->data)执行相应的函数。如果在执行tasklet期间,有新的tasklet进入当前处理器的tasklet队列,则会尽快引发TASKLET_SOFTIRQr软中断来执行新的tasklet。
除了普通的tasklet之外,内核还使用了另一种tasklet,它具有『较高』的优先级,除以下修改之外,其实现与普通的tasklet完全相同。
- 使用HI_SOFTIRQ作为软中断,而不是TASKLET_SOFTIRQ。
- 注册tasklet在CPU的相关变量的tasklet_hi_vec中站队。
当前大部分声卡驱动程序都利用了这一选项,因为操作延迟时间太长可能损害音频输出的音质。
Footnotes
-
具体可以看后面的内核同步的笔记。 ↩