linux驱动: 中断下半部之tasklet&workqueue

Posted 超凡东皇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux驱动: 中断下半部之tasklet&workqueue相关的知识,希望对你有一定的参考价值。

前言

linux中断下半部是linux中断处理中非常重要的一个组成,如果没有下半部系统很多情况都不能正常工作,所以我们如果用到了中断,比如gpio中断、定时器中断等最好将大部分工作都放到下半部去处理,中断中只做标记跟激活下半部的工作,尤其是那些需要休眠的、有阻塞的、或者耗时长的处理必须放到下半部的work_queue中,别问我为什么,因为你不这么做的话系统跑到这里就挂掉了。。。

Linux实现下半部的机制主要有软中断、tasklet、工作队列和线程化irq,不过基于实际开发中的常用性,本篇只介绍tasklet跟工作队列,以为目前的理解,这两种操作基本可以覆盖所有的中断下半部应用场景了

1、tasklet

tasklet 是用软中断来实现的一种中断下半部机制,它的用途就相当于软中断,在tasklet处理中能响应其它中断,但是它不参与进程的调度,所以在tasklet处理中不能阻塞、不能休眠,说的专业一点就是它参与中断的上下文但是不参与进程的上下文。
tasklet 的结构体如下:定义在linux/interrupt.h中

struct tasklet_struct
{
	struct tasklet_struct *next;	//下一个 tasklet
	unsigned long state;			//tasklet 状态
	atomic_t count;					//tasklet 引用计数器
	void (*func)(unsigned long);	//tasklet 回调函数
	unsigned long data;				//回调函数参数
};

使用起来相当简单,两条语句加个函数就能搞定,如下:

//task_let回调函数
void func_task_let(unsigned long data)
{
	printk("tasklet-test: this is bottom half\\n");
}
//用宏声明一下, 实际是定义一个mytasklet的结构体,并且进行了初始化
DECLARE_TASKLET(mytasklet, func_task_let, 0);

tasklet_schedule(&mytasklet);//放到中断处理函数中, 激活tasklet

就这么几行代码就使用起来了,实际就是用DECLARE_TASKLET宏定义了一个tasklet_struct的结构体,并且进行了初始化,

DECLARE_TASKLET宏定义如下:

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

然后在中断中调用tasklet_schedule(&mytasklet);,这个函数实际是触发了一个软中断,之后就可以在中断退出之后的某个时候运行。

关于tasklet更详细的介绍可以参考:https://www.cnblogs.com/wangzahngjun/p/5120526.html

2、work_queue

相比tasklet, work_queue更加人性化,因为它就相当于一个线程,其中可以休眠、阻塞、重新调度等,以及一切在线程中可以做的,我们就把它看做一个线程就好了,其使用起来也很简单,也有稍微复杂一点的

//work_queue回调函数
void func_work_queue(struct work_struct *work)
{
	printk("work-queue-test: this is workqueue bottom half\\n");
}
//宏定义一个work_struct结构体
DECLARE_WORK(mywork, func_work_queue);

schedule_work(&mywork);//放到中断处理函数中,激活work_queue

是不是跟tasklet用法很像啊,是的,真的太像了

work_struct 结构体定义在linux/workqueue.h中, 如下:

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

稍微复杂一点的应用,创建一个线程,然后将work_queue跟线程关联起来,如下:

static struct work_struct work;
static struct workqueue_struct *wq;

//而create_workqueue为系统中的每个CPU都创建一个workqueue队列(创建一个内核线程)
wq = create_singlethread_workqueue("kworkqueue_iqs");//只创建一个内核线程

flush_workqueue(wq);//清理指定工作队列中的所有任务

//work表示要初始化的工作,func_work_queue是工作对应的处理函数
INIT_WORK(&work, func_work_queue);

//中断处理函数
static irqreturn_t tp_interrupt_handler(int irq, void *dev_id)
	if (!work_pending(&work))//判断工作是否在进行中
	{	//将工作添加入wq工作队列等待执行,作用与schedule_work()类似
		queue_work(wq, &work);
	}
}

关于work_queue更详细的介绍可以参考:
http://blog.chinaunix.net/uid-24148050-id-296982.html

以上是关于linux驱动: 中断下半部之tasklet&workqueue的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核中的下半部机制之tasklet

中断下半部之 tasklet

中断下半部之 tasklet

《深入理解Linux内核》软中断/tasklet/工作队列

Linux内核中的软中断tasklet和工作队列详解

Linux内核中的软中断tasklet和工作队列具体解释