4.4.8. tasklet

由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的,因此诞生了tasklet. 他具有以下特性

  1. 一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行

  2. 多个不同类型的tasklet可以并行在多个cpu上

  3. 软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(如添加模块时)

tasklet是利用软中断实现的一种下半部机制,本质上是软中断的一个变种,运行在中断上下文。如果不需要软中断的并行特性,tasklet就是一种好的选择

4.4.8.1. 数据结构

struct tasklet_struct
{
    struct tasklet_struct *next;       //将多个tasklet链接成单向循环链表
    unsigned long state;               //TASKLET_STATE_SCHED  TASKLET_STATE_RUN
    atomic_t count;                    //0激活tasklet 非0禁用tasklet
    void (*func)(unsigned long);       //用户自定义函数
    unsigned long data;                //函数入参
};
//kernel/softirq.c
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);//低优先级
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);//高优先级

4.4.8.2. 定义一个tasklet

  • 静态声明

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

//tasklet->count初始化为0,表示tasklet处于激活状态

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
  • 动态声明

void tasklet_init(struct tasklet_struct *t,
          void (*func)(unsigned long), unsigned long data)
{
    t->next = NULL;
    t->state = 0;
    atomic_set(&t->count, 0);
    t->func = func;
    t->data = data;
}

4.4.8.3. tasklet软中断注册

void __init softirq_init(void)
{
        int cpu;

        for_each_possible_cpu(cpu) {
                per_cpu(tasklet_vec, cpu).tail =
                        &per_cpu(tasklet_vec, cpu).head;
                per_cpu(tasklet_hi_vec, cpu).tail =
                        &per_cpu(tasklet_hi_vec, cpu).head;
        }

        open_softirq(TASKLET_SOFTIRQ, tasklet_action);
        open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

start_kernel执行时会执行softirq_init它会初始化各个cpu core的tasklet和tasklet_hi链表,另外还会注册TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断,他们的软中断处理函数分别为 tasklet_action和tasklet_hi_action,在这两个软中断处理函数中分别会遍历tasklet_vec链表和tasklet_hi_vec链表

4.4.8.4. tasklet执行

//kernel/softirq.c
static __latent_entropy void tasklet_action(struct softirq_action *a)
|--tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
                |--struct tasklet_struct *list;
                |  //关中断的前提下获取tasklet_vec链表头保存在list
                |--local_irq_disable();
                |  list = tl_head->head;
                |  tl_head->head = NULL;
                |  tl_head->tail = &tl_head->head;
                |  local_irq_enable();
                |--while (list)
                                struct tasklet_struct *t = list;
                                list = list->next;
                                //保证在同一个cpu上运行此tasklet
                                if (tasklet_trylock(t))
                                        //t->count为0表示该tasklet处于可执行状态
                                        if (!atomic_read(&t->count))
                                                if (!test_and_clear_bit(TASKLET_STATE_SCHED,&t->state))
                                                        BUG();
                                                t->func(t->data);
                                                //清除tasklet->state的TASKLET_STATE_RUN标记
                                                tasklet_unlock(t);
                                                continue;
                                local_irq_disable();
                                t->next = NULL;
                                *tl_head->tail = t;
                                tl_head->tail = &t->next;
                                __raise_softirq_irqoff(softirq_nr);
                                local_irq_enable();

4.4.8.5. tasklet调度

static inline void tasklet_schedule(struct tasklet_struct *t)
|--if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
                __tasklet_schedule(t);
                        |--__tasklet_schedule_common(t, &tasklet_vec,TASKLET_SOFTIRQ);
                                        |--local_irq_save(flags);
                                        |--head = this_cpu_ptr(headp);
                                        |--t->next = NULL;
                                        |--*head->tail = t;
                                        |--head->tail = &(t->next);
                                        |--raise_softirq_irqoff(softirq_nr);
                                        |--local_irq_restore(flags);
  1. taslet_schedule首先会检测tasklet_struct->state有没有置位TASKLET_STATE_SCHED标志位,如果已经置位TASKLET_STATE_SCHED,则会退出,因此在tasklet_action执行时要先清除TASKLET_STATE_SCHED标志,以让其它的tasklet执行tasklet_schedule

  2. __tasklet_schedule:如果没有设置表示还没有执行,置位的同时调用__tasklet_schedule只是触发软中断,即将tasklet挂入到tasklet_vec链表,由于tasklet_vec链表是per cpu的,因此会加入到当前的CPU的tasklet_vec,执行时也会有对应的CPU执行。如果已经置位TASKLET_STATE_SCHED标志位,直接退出,所以如果一个tasklet没有执行,多次执行tasklet_schedule,也不会将这个tasklet挂载到其它的cpu的tasklet_vec链表,除非在本cpu的tasklet_vec链表的这个tasklet执行完了,清空了tasklet->state的TASKLET_STATE_SCHED标志位,下次执行tasklet_schedule时才有机会链入其它的cpu上的tasklet_vec链表

以一个常见的设备驱动为例,在硬件中断处理函数中调用tasklet_schedule函数去触发tasklet来处理一些数据,例如数据复制,数据转换等

static irqreturn_t scdrv_event_interrupt(int irq, void *subch_data)
{
        struct subch_data_s *sd = subch_data;
        unsigned long flags;
        int status;

        spin_lock_irqsave(&sd->sd_rlock, flags);
        status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch);

        if ((status > 0) && (status & SAL_IROUTER_INTR_RECV)) {
                //触发tasklet来处理
                tasklet_schedule(&sn_sysctl_event);
        }
        spin_unlock_irqrestore(&sd->sd_rlock, flags);
        return IRQ_HANDLED;
}