现在的位置: 首页 > 综合 > 正文

高级字符设备驱动–中断下半部机制之tasklet(一)

2013年05月03日 ⁄ 综合 ⁄ 共 3261字 ⁄ 字号 评论关闭

综述 

Linux把中断处理例程分两部分

上半分:实际响应中断的例程。
下半分:被顶部分调用,通过开中断的方式进行。

两种机制实现:
Tasklet
工作队列work queue

上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。

下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

 

Tasklet(小任务机制)

 -- 内核在BH机制的基础上进行了扩展, 实现“软中断请求”(softirq)机制。利用软中断代替 bottom half handler 的处理。

 -- tasklet 机制正是利用软中断来完成对驱动 bottom half 的处理。

 -- tasklet会让内核选择某个合适的时间来执行给定的小任务。

 

小任务tasklet的实现

其数据结构为struct tasklet_struct,每一个结构体代表一个独立的小任务,在<linux/interrupt.h>定义如下
 
struct tasklet_struct
{
    struct tasklet_struct *next;/*指向下一个链表结构*/
    unsigned long state;/*小任务状态*/
    atomic_t count;/*引用计数器*/
    void (*func)(unsigned long);/*小任务的处理函数*/
    unsigned long data;/*传递小任务函数的参数*/
};

state的取值参照下边的枚举型:

enum
{
    TASKLET_STATE_SCHED,    /* 小任务已被调用执行*/
    TASKLET_STATE_RUN   /*仅在多处理器上使用*/
};

count域是小任务的引用计数器。只有当它的值为0的时候才能被激活,并其被设置为挂起状态时,才能够被执行,否则为禁止状态。

 

Tasklet

 -- ksoftirqd()是一个后台运行的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了( pend ),就执行对应的处理函数。

 -- tasklet 所对应的处理函数就是tasklet_action,这个处理函数在系统启动时初始化软中断时,就在软中断向量表中注册。
 
   -- tasklet_action() 遍历一个全局的 tasklet_vec 链表。链表中的元素为 tasklet_struct结构体。

一、声明和使用小任务tasklet

静态的创建一个小任务的宏有一下两个:

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

 name 是 tasklet 的名字,
 Func  是执行 tasklet 的函数;
 data 是 unsigned long 类型的 function 参数。

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

这两个宏的区别在于计数器设置的初始值不同,前者为0,后者为1。
为0的表示激活状态,为1的表示禁止状态。

其中ATOMIC_INIT宏为:
#define ATOMIC_INIT(i)   { (i) }

此宏在include/asm-generic/atomic.h中定义。这样就创建了一个名为name的小任务,其处理函数为func。当该函数被调用的时候,data参数就被传递给它。

 

二、小任务处理函数程序

处理函数的的形式为:void my_tasklet_func(unsigned long data)。这样DECLARE_TASKLET(my_tasklet, my_tasklet_func, data)实现了小任务名和处理函数的绑定,而data就是函数参数。

三、调度编写的tasklet

调度小任务时引用tasklet_schedule(&my_tasklet)函数就能使系统在合适的时候进行调度。函数原型为:

static inline void tasklet_schedule(struct tasklet_struct *t)
{
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
       __tasklet_schedule(t);
}
调度执行指定的tasklet。
将定义后的 tasklet 挂接到 cpu 的 tasklet_vec 链表。而且会引起一个软 tasklet 的软中断 , 既把 tasklet 对应的中断向量挂起 (pend) 。

这个调度函数放在中断处理的上半部处理函数中,这样中断申请的时候调用处理函数(即irq_handler_t handler)后,转去执行下半部的小任务。

tasklet 的接口

 void tasklet_disable(struct tasklet_struct *t);
这个函数禁止给定的 tasklet. tasklet ,但仍然可以被 tasklet_schedule 调度, 但是它的执行被延后直到这个 tasklet 被再次激活。
 void tasklet_enable(struct tasklet_struct *t);
激活一个之前被禁止的 tasklet. 如果这个 tasklet 已经被调度, 它会很快运行.
 一个对tasklet_enable 的调用必须匹配每个对 tasklet_disable 的调用, 因为内核跟踪每个 tasklet 的"禁止次数".
 void tasklet_hi_schedule(struct tasklet_struct *t);
调度 tasklet 在更高优先级执行. 当软中断处理运行时, 它在其他软中断之前处理高优先级 tasklet。
 void tasklet_kill(struct tasklet_struct *t);
这个函数确保了这个 tasklet 没被再次调度来运行; 它常常被调用当一个设备正被关闭或者模块卸载时. 如果这个 tasklet 被调度来运行, 这个函数等待直到它已执行.

模板

使用tasklet作为下半部的处理中断的设备驱动程序模板如下:

/*定义tasklet和下半部函数并关联*/
void my_do_tasklet(unsigned long);
DECLARE_TASKLET(my_tasklet, my_do_tasklet, 0);

/*中断处理下半部*/
void my_do_tasklet(unsigned long)
{
  ……/*编写自己的处理事件内容*/
}

/*中断处理上半部*/
irpreturn_t my_interrupt(unsigned int irq,void *dev_id)
{
 ……
/*调度my_tasklet函数,根据声明将去执行my_tasklet_func函数*/
 tasklet_schedule(&my_tasklet)
 ……
}

/*设备驱动的加载函数*/
int __init xxx_init(void)
{
 ……
 /*申请中断, 转去执行my_interrupt函数并传入参数*/
result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);
 ……
}

/*设备驱动模块的卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irq(my_irq,my_interrupt);
……
}

 

抱歉!评论已关闭.