• Linux 中断下半部tasklet


    tasklet

    tasklet 是中断下半部的一种实现机制,主要用于小任务处理,耗时较短、不能阻塞的任务,用tasklet处理较合适。对于耗时较长,可以用work queue(工作队列)来处理。

    tasklet和内核定时器timer_list都是通过软中断方式来实现的。

    tasklet结构体

    中断下半部用结构体tasklet_struct来表示

    #include <linux/interrupt.h>
    
    struct tasklet_struct
    {
        struct tasklet_struct *next;
        unsigned long state;
        atomic_t count;
        void (*func)(unsigned long);
        unsigned long data;
    };
    

    其中,state有2位:
    1)bit0:表示TASKLET_STATE_SCHED
    等于1,表示已经执行了tasklet_schedule,该把tasklet放入队列了。tasklet_schedule会判断该位,如果已经等于1,那么它就不会再次把tasklet放入队列。

    2)bit1:表示TASKLET_STATE_RUN
    等于1,表示正在运行tasklet中的func函数。函数执行完毕后,内核会把该位清0。

    count表示该tasklet是否使能:值0表示使能了,非0表示被禁止了。对于count非0的tasklet,func()不会被执行。

    data 是传递给func()的参数。

    初始化tasklet_strcut

    宏初始化tasklet_struct

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

    使用DECLARE_TASKLET定义的tasklet结构体,它是使能的。
    使用DECLARE_TASKLET_DISABLED定义的tasklet结构体,它是禁止的。使用之前要先调用tasklet_enable使能之。

    也可用函数tasklet_init()初始化tasklet结构体:

    tasklet_init(struct tasklet_struct *t,
                 void (*func)(unsigned long), unsigned long data);
    

    使能/禁止tasklet

    tasklet_enable将count加1;tasklet_disable将count减1。

    static inline void tasklet_enable(struct tasklet_struct *t);  // 使能
    
    static inline void tasklet_disable(struct tasklet_struct *t); // 禁止
    

    调度tasklet

    将tasklet放入链表,并设置它的TASKLET_STATE_SCHED状态为1。

    static inline void tasklet_schedule(struct tasklet_struct *t);
    

    kill tasklet

    从链表中删除tasklet。如果一个tasklet未被调度,tasklet_kill会将它的TASKLET_STATE_SCHED状态清0;如果一个tasklet已被调度,tasklet_kill会等待它执行完毕,再把它的TASKLET_STATE_SCHED状态清0。
    通常,在卸载驱动程序(module_exit)时,调用task_kill。

    tasklet_kill_immediate 与tasklet_kill区别是,前者会立即移除tasklet,二不论tasklet是否处于TASKLET_STATE_SCHED状态。

    void tasklet_kill(struct tasklet_struct *t); // 移除tasklet
    
    void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu); // 立即移除tasklet
    

    tasklet使用方法

    先定义tasklet实例,需要使用时调用tasklet_schedule,驱动卸载前调用tasklet_kill。tasklet_schedule只是将tasklet放入内核队列,其func函数会在软中断执行过程中被调用。

    tasklet内部实现机制

    前面讲过,tasklet是通过软中断实现,属于TASKLET_SOFTIRQ类型的软中断。入口函数tasklet_action。

    // kernel/softirq.c
    
    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_SOFTIRQ类型软中断(普通软中断)及其处理函数
        open_softirq(HI_SOFTIRQ, tasklet_hi_action);   // 高优先级软中断
    }
    

    在软中断的初始化(softirq_init)末尾,通过open_softirq注册TASKLET_SOFTIRQ类型的软中断(即tasklet)及其处理函数(即tasklet_action)。

    驱动程序调用tasklet_schedule时,会设置tasket的state为TASKLET_STATE_SCHED,并把它放入某个链表:

    static inline void tasklet_schedule(struct tasklet_struct *t)
    {
        // 1. 如果未设置为SCHED,则设置为SCHED并放入队列
        if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) // 设置tasklet.state为TASKLET_STATE_SCHED
            __tasklet_schedule(t);
    }
    
    void __tasklet_schedule(struct tasklet_struct *t)
    {
        unsigned long flags;
    
        local_irq_save(flags); // 保存中断
        // 2. 放入队列
        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); // 恢复中断到flags状态
    }
    EXPORT_SYMBOL(__tasklet_schedule);
    

    产生硬件中断时,讹你好处理完硬件中断后,会处理软中断。对于TASKLET_SOFTIRQ软中断,会调用tasklet_action函数。

    执行过程是:从队列中找到tasklet,进行状态判断后执行func函数,从队列中删除tasklet。
    可知:
    1)tasklet_schedule 调度tasklet时,其中的函数并不会立即执行,而只是把tasklet放入队列;
    2)调用一次tasklet_schedule,只会导致tasklet的函数被执行一次;
    3)如果tasklet的函数尚未执行,多次调用tasklet_schedule也是少的,只会放入队列一次。

    普通软中断处理函数tasklet_action:

    static __latent_entropy void tasklet_action(struct softirq_action *a)
    {
        struct tasklet_struct *list;
    
        local_irq_disable();
        list = __this_cpu_read(tasklet_vec.head);
        __this_cpu_write(tasklet_vec.head, NULL);
        __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
        local_irq_enable();
    
        while (list) {
            struct tasklet_struct *t = list;
    
            list = list->next; // 1. 从列表中去除每一项
    
            if (tasklet_trylock(t)) {
                if (!atomic_read(&t->count)) {
                    // 2. 判断:如果不是SCHED状态,就是有BUG
                    if (!test_and_clear_bit(TASKLET_STATE_SCHED,
                                &t->state))
                        BUG();
                    t->func(t->data); // 3. 执行tasklet的func
                    tasklet_unlock(t);
                    continue;
                }
                tasklet_unlock(t);
            }
    
            local_irq_disable();
            // 4. 从队列中取出
            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();
        }
    }
    
  • 相关阅读:
    闰年测试
    EditBox的测试用例设计
    测试工程中的评审
    测试框架
    github
    第一次上机实验
    对软件测试的初步认识
    白盒测试
    Date : 日期对象
    C++ 格式化输出 及 输入 流
  • 原文地址:https://www.cnblogs.com/fortunely/p/16490356.html
Copyright © 2020-2023  润新知