linux驱动学习之tasklet分析

tasklet是中断处理下半部分最常用的一种方法,驱动程序一般先申请中断,在中断处理函数内完成中断上半部分的工作后调用tasklet。tasklet有如下特点:

1.tasklet只可以在一个CPU上同步地执行,不同的tasklet可以在不同地CPU上同步地执行。

2.tasklet的实现是建立在两个软件中断的基础之上的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,本质上没有什么区别,只不过HI_SOFTIRQ的优先级更高一些

3.由于tasklet是在软中断上实现的,所以像软中断一样不能睡眠、不能阻塞,处理函数内不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。

4.一个 tasklet 能够被禁止并且之后被重新使能; 它不会执行直到它被使能的次数与被禁止的次数相同.

5.tasklet的串行化使tasklet函数不必是可重入的,因此简化了设备驱动程序开发者的工作。

6.每个cpu拥有一个tasklet_vec链表,具体是哪个cpu的tasklet_vec链表,是根据当前线程是运行在哪个cpu来决定的。


1.tasklet结构体

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

tasklet结构变量是tasklet_vec链表的一个节点,next是链表的下一节点,state使用了两个位如下
enum
{
	TASKLET_STATE_SCHED,	/* 1已经被调度,0表示还没调度*/
	TASKLET_STATE_RUN	/* 1tasklet正在执行,0表示尚未执行,只针对SMP有效,单处理器无意义 */
};

count用于禁止使能,每禁止一次计数加一,没使能一次计数减一,只有禁止次数和使能次数一样(count等于0)时tasklet才会执行调用函数。
func 执行函数不能有导致睡眠、不能阻塞的代码。
data 执行函数的参数


2.tasklet的定义

定义时初始化   
    定义变量名为name的tasklets_struct变量,并初始化调用函数为func,参数为data,使能tasklet
    DECLARE_TASKLET(name, func, data);     #define DECLARE_TASKLET(name, func, data) \
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

    定义变量名为name的tasklets_struct变量,并初始化调用函数为func,参数为data,禁止tasklet
    DECLARE_TASKLET_DISABLED(name, func, data);
    #define DECLARE_TASKLET_DISABLED(name, func, data) \
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

运行中初始化    先定义    struct tasklet_struct name ;
    后初始化  

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;              //调用函数参数
}

3.tasklet的调用过程

static inline void tasklet_schedule(struct tasklet_struct *t);使用此函数即可完成调用
static inline void tasklet_schedule(struct tasklet_struct *t)
{
    /*test_and_set_bit设置调度位TASKLET_STATE_SCHED,test_and_set_bit返回t->state设置前状态,如果设置前状态为1(已被调用)那么直接退出否则进入__tasklet_schedule函数*/
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
        __tasklet_schedule(t);
}


void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;
    local_irq_save(flags);                      //关中断保存中断状态
    t->next = __get_cpu_var(tasklet_vec).list;  //这两行用于将新插入的节点 放置在tasklet_vec链表的头部
    __get_cpu_var(tasklet_vec).list = t;        // 
    raise_softirq_irqoff(TASKLET_SOFTIRQ);      //触发一个软终端
    local_irq_restore(flags);                   //使能中断的同时还恢复了由 local_irq_save() 所保存的中断状态
}
至此调度函数已经触发了一个软中断,具体中断函数看tasklet的初始化
void __init softirq_init(void)
{
        open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);//可以看到软中断触发后会执行tasklet_action这个函数
        open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}


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

    local_irq_disable();                       //这里先关中断 保证原子操作
    list = __get_cpu_var(tasklet_vec).list;    //取出tasklet_vec链表表头
    __get_cpu_var(tasklet_vec).list = NULL;    //因为下面将会一次处理完,这里可以预先清空tasklet_vec链表,对于为处理完的会重新加入链表
                                               //也可以实现在tasklet的处理函数中重新加入自己。
    local_irq_enable();



    while (list) {
        struct tasklet_struct *t = list;       //取一节点

        list = list->next;                     //循环遍历全部节点 

        if (tasklet_trylock(t)) {              //这里只是测试TASKLET_STATE_RUN标记,防止tasklet重复调用  
                                               //疑问:这里如果判断tasklet已经在上运行了,trylock失败,那么为什么后面会被重新加入链表呢,那不是下次又执行了?
            if (!atomic_read(&t->count)) {     //疑问: 如果tasklet被禁止了那么后面有把它加回链表中重新触发一次软中断,这样不是一直有软中断了吗?为什么不在禁止的时候移出链表,使能时候在加入呢?  
                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) //检查可调度位是否设置了,正常应该设置了的
                     BUG();                   
                t->func(t->data);              //处理调用函数
                tasklet_unlock(t);             //清TASKLET_STATE_RUN标记
                continue;
            }
            tasklet_unlock(t);
        }

        local_irq_disable();
        t->next = __get_cpu_var(tasklet_vec).list; //对于trylock失败和tasklet禁止的节点会被重新加入链表
        __get_cpu_var(tasklet_vec).list = t;
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);   //发起新的软中断,这里有两条链表一条是处理中的链表list,一个是当前tasklet_vec中的链表,当出现不能处理的节点时将节点重新加入tasklet_vec中后发起新的软中断,那么未处理的节点也会在下次中断中处理。
        local_irq_enable();
    }
}

4.相关函数

/*和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU */
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
    atomic_inc(&t->count);      //减少计数后,t可能正在运行
    smp_mb__after_atomic_inc(); //保证在多处理器时同步
}
/*函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出*/

static inline void tasklet_disable(struct tasklet_struct *t){
   tasklet_disable_nosync(t); 
   tasklet_unlock_wait(t);  //等待TASKLET——STATE_RUN标记清零   
   smp_mb();
}

static inline int tasklet_trylock(struct tasklet_struct *t){
   return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}

static inline void tasklet_unlock(struct tasklet_struct *t){   
        smp_mb__before_clear_bit();     
        clear_bit(TASKLET_STATE_RUN, &(t)->state);
}

static inline void tasklet_unlock_wait(struct tasklet_struct *t){
    while (test_bit(TASKLET_STATE_RUN, &(t)->state)) {
          barrier(); 
     }
}

/*使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会很快运行。tasklet_enable和tasklet_disable必须匹配调用, 因为内核跟踪每个tasklet的"禁止次数"*/ 
static inline void tasklet_enable(struct tasklet_struct *t)
{
    smp_mb__before_atomic_dec();
    atomic_dec(&t->count);
}

/*和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet 在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期*/
void tasklet_hi_schedule(struct tasklet_struct *t);

/*确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill 前它重新调度它自己,如同使用 del_timer_sync*/
void tasklet_kill(struct tasklet_struct *t)
{
    if (in_interrupt())
        printk("Attempt to kill tasklet from interrupt\n");

        while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { //检测t是否被调度
        do
            yield();
        while (test_bit(TASKLET_STATE_SCHED, &t->state));          //等待t调度位清零,还未执行调用函数
    }
    tasklet_unlock_wait(t);                                        //等待t调用函数执行完
    clear_bit(TASKLET_STATE_SCHED, &t->state);                     //函数调用完可能t被重新加入链表,所以再清一次保证不再调用
}
这个函数不是真的去杀掉被调度的tasklet,而是保证tasklet不再调用







  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值