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;
}
上一篇:十、【工作队列】tasklet、上半部和下半部


下一篇:移动端js调试工具:eruda