Kernel tasklet

原创
2019/05/15 20:49
阅读数 109

    内核 tasklet 机制是在软中断的基础上实现的。我们知道软中断有如下两点,导致比较难用:

    (1)软中断在内核中静态注册。如果要增加新的软中断,必须修改内核代码,重新编译内核镜像。

    (2)SMP系统中,软中断action被所有CPU共享(即,同一个软中断同时可以在多个CPU上运行),需要用户自己保证软中断处理函数的可重入性。

    tasklet 设计目标就是解决软中断如上两点不足之处,使得中断处理简单易用。针对第一点,tasklet设计了链表结构,提供 tasklet_schedule() 和 tasklet_hi_schedule() 等接口供用户动态注册 tasklet 处理函数;针对第二点,tasklet 设计了per-cpu变量(即,每个CPU都维护自己的一份 tasklet链表结构),从而避免了同一个 tasklet 被多个CPU共享,使得 tasklet 的处理函数不必是可重入的。

    tasklet 占用两种软中断类型,一种是 HI_SOFTIRQ,代表高优先级的tasklet;另外一种是 TASKLET_SOFTIRQ,代表低优先级的 tasklet,两种类型的 tasklet 在实现原理上一样,以下仅以 TASKLET_SOFTIRQ 为例分析其工作原理。

    tasklet 数据关系如下图所示:

    1,tasklet及链表结构:

struct tasklet_struct
{
    struct tasklet_struct *next;    // 链表指针
    unsigned long state;    // 状态:是否运行,是否被调度
    atomic_t count;    // 引用计数
    void (*func)(unsigned long);    // tasklet处理函数
    unsigned long data;    // tasklet处理数据
};

struct tasklet_head {
    struct tasklet_struct *head;    // 指向tasklet链表头
    struct tasklet_struct **tail;    // 指向tasklet链表尾
};

    2,tasklet软中断注册:

    由于tasklet是基于软中断实现的,那么tasklet首先得在软中断上注册一个软中断处理函数 tasklet_action(),然后,被软中断调度运行的 tasklet_action 在per-cpu变量 tasklet_vec中找到当前CPU对应的 tasklet_head 链表,一次处理完该链表上所有的 tasklet_struct:

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);    // 注册低优先级 tasklet 处理函数 tasklet_action
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);    // 注册高优先级 tasklet 处理函数 tasklet_hi_action
}

    3,调度运行每个 tasklet_struct:

static void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;

    local_irq_disable();     // 关中断操作链表
    list = __this_cpu_read(tasklet_vec.head);    // 从 tasklet_vec 中一次性获取当前CPU上的所有 tasklet_struct
    __this_cpu_write(tasklet_vec.head, NULL);
    __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
    local_irq_enable();    // 开中断

    while (list) {    // 循环处理链表中所有 tasklet_struct
        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 处理函数
                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }

        local_irq_disable();
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_enable();
    }
}
    4,tasklet的用户初始化和动态注册:

    (1)用户初始化 tasklet:

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;
}
 

    (2)用户注册 tasklet:

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;
    *__this_cpu_read(tasklet_vec.tail) = t;
    __this_cpu_write(tasklet_vec.tail, &(t->next));
    raise_softirq_irqoff(TASKLET_SOFTIRQ);
    local_irq_restore(flags);
}
    tasklet_schedule() 将用户定义的 tasklet_struct 加入当前CPU的 tasklet 链表中,等待 tasklet_action()在适当的时候调度执行。

    注意:由于 tasklet_action() 处理方式是:一次性退出所有当前CPU上的 tasklet_struct,所以调用一次 tasklet_schedule(),该tasklet_struct只会被调度运行一次,如果需要重复执行,需要用户自己重复调用 tasklet_schedule。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部