linux中断控制之tasklet

定义tasklet
定义于#include<linux/interrupt.h>  内核3.1.4

先看看tasklet的结构体
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 is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running ( SMP only) */
};

既可以静态创建,也可以动态创建。
如果静态创建一个tasklet,使用以下两个宏:
#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 }    //禁止状态

动态创建:
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;
}

tasklet队列
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

编写处理函数tasklet_handler
void tasklet_handler(unsigned long data)
因为是靠软中断实现,所以tasklet 不能睡眠。这意味着不能在tasklet中使用信号量或者其他阻塞函数。
tasklet运行时运行可以响应中断。
但如果tasklet和中断处理程序之间共享了某些数据的话,要做好预防工作。


调度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()源码在kernel/softirq.c
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);
}

EXPORT_SYMBOL(__tasklet_schedule);    //把内核函数的符号导出,符号的意思就是函数的入口地址,让其他地方可以调用

__tasklet_schedule(stuct tasklet_struct *t)的实现是通过 获取当前CPU的tasklet_vec链表,将需要调度的tasklet插入当前CPU的tasklet_vec链表头部,并执行TASKLET_SOFTIRQ软中断。

禁止/使能 删除tasklet
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic_inc();
}
用来禁止指定的tasklet,不用等待tasklet执行完毕就返回,不过不安全

static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
禁止某个指定的tasklet,如果该tasklet正在执行,会等待它执行完毕再返回

static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
激活一个tasklet。

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)) {
do {
yield();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);        //等待tasklet执行完毕,再去移除它,能引起休眠,禁止在中断上下文中使用
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
从挂起的队列中去掉一个tasklet。

tasklet核心处理函数
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;

local_irq_disable();
list = __this_cpu_read(tasklet_vec.head);    //得到tasklet链表
__this_cpu_write(tasklet_vec.head, NULL);    //清空链表
__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
local_irq_enable();

while (list) {
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_unlock(t);
continue;
}
tasklet_unlock(t);    //以上保证了同一时间,相同类型的tasklet只能有一个执行
}

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

tasklet流程
驱动程序在初始化时,通过函数tasklet_init建立一个tasklet,然后调用函数tasklet_schedule将这个tasklet 放在 tasklet_vec链表的头部,并唤醒后台线程ksoftirqd。当后台线程ksoftirqd运行调用__do_softirq时,会执行在中断 向量表softirq_vec里中断号TASKLET_SOFTIRQ对应的tasklet_action函数,然后tasklet_action遍历 tasklet_vec链表,调用每个tasklet的函数完成软中断操作。
其中:ksoftirqd 是一个后台运行的内核线程,它会周期的遍历软中断的向量列表。

注意
1.Tasklet 可被hi-schedule和一般schedule(调度),hi-schedule一定比一般shedule早运行
2.同一个Tasklet可同时被hi-schedule和一般schedule
3.同一个Tasklet若被同时hi-schedule多次,等同于只hi-shedule一次,因为,在tasklet未 运行时,hi-shedule同一tasklet无意义,会冲掉前一个tasklet
4.不同的tasklet不按先后shedule顺序运行,而是并行运行
5.Taskelet的hi-schedule 使用softirq 0, 一般schedule用softirq 30

tasklet是作为中断下半部的一个很好的选择,它在性能和易用性之间有着很好的平衡。较之于softirq,tasklet不需要考虑SMP下的并行问题,而又比workqueues有着更好的性能。
tasklet通常作为硬中断的下半部来使用,在硬中断中调用tasklet_schedule(t)。每次硬中断都会触发一次tasklet_schedule(t),但是每次中断它只会向其中的一个CPU注册,而不是所有的CPU。
完成注册后的tasklet由tasklet_action()来执行,在SMP环境下,它保证同一时刻,同一个tasklet只有一个副本在运行,这样就避免了使用softirq所要考虑的互斥的问题。
再者,tasklet在执行tasklet->func()前,再一次允许tasklet可调度(注册),但是在该tasklet已有一个副本在其他CPU上运行的情况下,它只能退后执行。
总之,同一个硬中断引起的一个tasklet_schedule()动作只会使一个tasklet被注册,而不同中断引起的tasklet则可能在不同的时刻被注册而多次被执行。


用例
定义tasklet和底半部函数,并关联
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);

中断处理底半部
void xxx_do_tasklet(unsigned long)
{
    ……
}
中断处理顶半部
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
      ……
      tasklet_schedule(&xxx_tasklet);    //调度tasklet
      ……
}
设备驱动加载模块
int __init xxx_init(void)
{
      ……
      result=request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,”xxx”,NULL)
      ……
    return IRQ_HANDLED;
}

void __exit xxx_exit(void)
{
      ……
      free_irq(xxx_irq,xxx_irq_interrupt);
      ……
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值