Skip to content

Latest commit

 

History

History
134 lines (99 loc) · 6.25 KB

2014-05-09-tasklet.md

File metadata and controls

134 lines (99 loc) · 6.25 KB
layout title category description tags
post
tasklet
中断和异常
tasklet...
tasklet

软中断是将操作推迟到未来某一个时刻执行的最好方法,但延迟函数执行以及处理机制非常复杂。因为多个处理器是可以同时并且独立地处理软中断,同一个软中断的处理程序例程可以在几个CPU上同时运行。

对软中断的效率来说,这是一个关键,多处理器上的网络实现依靠于此。但处理程序例程的设计必须是完全可重入且线程安全的。另外,临界区必须使用自旋锁保护,或者其他的IPC机制1

tasklet和工作队列是延迟函数执行工作的机制,其实现基于软中断,但是tasklet更易于使用,因而是更适用于设备的驱动程序一起其他一般性的内核代码。

tasklet结构

tasklet是『小进程』,执行一些迷你的任务,对这些任务使用全功能进程会过于浪费。tasklet的结构定义如下:

<include/linux/interrupt.h>

{% 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表示任务的当前状态,类似于真正的进程但是只有两个选项。

  1. 当tasklet注册到内核,等待调度执行时,将设置TASKLET_STATE_SCHED
  2. TASKLET_STATE_RUN表示tasklet当前正在执行。

第二个状态只在SMP系统上有用,用于保护tasklet在多个处理器上并行执行。原子计数器count用于禁用已经调度的tasklet,如果其值不等于0,在接下来执行的所有等待的tasklet任务时,将忽略对应的tasklet。

注册tasklet

tasklet_schedule将一个tasklet注册到系统中:

<include/linux/interrupt.h>

{% 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->stateTASKLET_STATE_SCHED位并返回原值。如果设置了TASKLET_STATE_SCHED标志位,则已经注册了tasklet,则返回。否则,将该tasklet至于一个链表的起始,其表头特定于CPU的变量tasklet_vec向量,该链表包含了所有注册的tasklet*。

在注册了一个tasklet之后, tasklet链表即标记为即将进行处理。

执行tasklet

tasklet生命周期中最为重要的部分就是执行,因为tasklet基于软中断实现,它们总是在处理软中断时执行。

tasklet关联到TASKLET_SOFTIRQ软中断,因而,调用raise_softirq(TASKLET_SOFTIRQ)就可以在下一个恰当的实际执行当前处理器的tasklet。内核使用tasklet_action作为该软中断的action函数。

这个函数首先确定特定于CPU的链表,其中保存了标记要执行的各个tasklet,它接下来将表头重定向到函数局部的一个数据项,相当于从外部公开的链表删除了所有表项,接下来,函数在循环中逐一处理tasklet。

<kernel/softirq.h>

{% 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

<include/linux/interrupt.h>

{% 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完全相同。

  1. 使用HI_SOFTIRQ作为软中断,而不是TASKLET_SOFTIRQ。
  2. 注册tasklet在CPU的相关变量的tasklet_hi_vec中站队。

当前大部分声卡驱动程序都利用了这一选项,因为操作延迟时间太长可能损害音频输出的音质。

Footnotes

  1. 具体可以看后面的内核同步的笔记。