Linux中断-常用的下文处理方法
两种常用的中断下文方法:tasklet和工作队列
设计方式:
- 中断发生
- 中断上文,处理紧急、不占太多时间的事情
- 调用下文
- 中断下文,做不太紧急、耗时间的任务
tasklet
tasklet是中断处理下文常用的一种方法,是一种特殊的软中断。
调用tasklet后,tasklet绑定的函数不会立即执行。经过一个很短且不确定的时间执行。
设计步骤:
- 定义一个tasklet结构体变量(不需要手动初始化)
- 调用对应函数宏,动态初始化tasklet(传入绑定函数、执行函数时传递的参数)
- 编写tasklet绑定的函数
- 在中断上文中调度tasklet(使用tasklet_schedule函数)
- 卸载模块时删除tasklet
相关函数和结构体
在<linux/interrupt.h>中定义
tasklet结构体
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
参数:
next:链表中下一个tasklet
state:tasklet的状态
count:tasklet的激活状态;0激活,非0非激活状态
(*func)(unsigned long):绑定的下文函数
data:传递给绑定函数的参数
动态初始化tasklet
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
参数:
t:指向tasklet_struct结构体的指针
func:tasklet绑定的函数
data:函数执行时传递的参数
调度tasklet
void tasklet_schedule(struct tasklet_struct *t);
开始调度tasklet时,调用此函数。
删除tasklet
void tasklet_kill(struct tasklet_struct *t);
这个函数会等tasklet执行完毕,再将它移除。该函数可能会引起休眠,所以禁止在中断上下文调用。
工作队列
工作队列(workqueue)是实现中断下文的机制之一。相对于tasklet:taskelet不能休眠,工作队列是可以休眠的。所以工作队列可以处理比tasklet更复杂耗时的事情。
Linux在启动期间会创建内核线程,该线程创建后就处于sleep状态,然后该线程会一直去工作队列中读取有没有需要执行的工作/任务。如果有就执行,没有就继续休眠。
工作队列有共享工作队列和自定义工作队列,共享工作队列开销相对固定,但在队列中存在多个工作时,有可能存在工作执行不及时的情况。自定义工作队列需要增加系统开销。
共享工作队列设计步骤:
- 定义work_struct工作结构体变量
- 定义要调用的函数
- 使用对应宏初始化工作(一般使用动态初始化)
- 在需要调度时执行调度函数(比如在中断上文中)
相关函数和结构体
linux/work/queue.h
work_struct
struct work_struct{
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
工作队列表示一个具体工作/任务的结构一天work_struct
主要关注func成员,它是一个函数指针,把需要完成的工作写在函数里,函数格式如下:
void (*work_func_t)(struct work_struct *work);
DECLARE_WORK宏
#define DECLARE_WORK(n, f)
参数:工作结构体,工作函数
静态定义并且初始化工作队列
INIT_WORK宏
#define INIT_WORK(_work, _func)
参数:工作结构体的指针,工作函数
动态定义并初始化工作结构
调度工作队列
int schedule_work(struct work_struct *work);
参数: 工作结构体指针
调度函数。把work_struct挂到CPU相关的工作结构队列链表上,等待工作队列线程处理。
多次调度时,如果上一次的任务没有处理完,再次调度相同工作/任务是无效的。
代码示例
//工作队列绑定函数
void worklist_test_func(struct work_struct *work){
printk("work func here\n");
}
//工作队列结构体
static struct work_struct work_test;
//tasklet下文执行函数
void tasklet_gpio_func(unsigned long dat){
unsigned int i;
for(i=0;i<dat;i++){
printk("hello tasklet i=%d\n",i);
}
}
//tasklet结构体
static struct tasklet_struct tasklet_gpio;
//中断函数,即中断上文部分
static irqreturn_t home_interrupt(int irq, void *dev_id) {
tasklet_gpio.data = 9; //修改传入的参数
tasklet_schedule(&tasklet_gpio); //调度tasklet下文
schedule_work(&work_test); //将work_test挂在工作队列,交予内核调度
return IRQ_HANDLED;
}
int drProbe(struct platform_device *dev){
int ret;
struct device_node *inter_node;
ret = request_irq(home_inter_idx, home_interrupt, IRQF_TRIGGER_FALLING, "home_key", dev); //申请GPIO中断
if(ret < 0){
printk("request interrupt failed, IRQ=%d,ret=%d\n", home_inter_idx, ret);
return -1;
}
tasklet_init(&tasklet_gpio, tasklet_gpio_func, 100); //初始化tasklet
INIT_WORK(&work_test, worklist_test_func); //动态初始化工作队列
return 0;
}