linux中断下半部是linux中断处理中非常重要的一个组成,如果没有下半部系统很多情况都不能正常工作,所以我们如果用到了中断,比如gpio中断、定时器中断等最好将大部分工作都放到下半部去处理,中断中只做标记跟激活下半部的工作,尤其是那些需要休眠的、有阻塞的、或者耗时长的处理必须放到下半部的work_queue中,别问我为什么,因为你不这么做的话系统跑到这里就挂掉了。。。
Linux实现下半部的机制主要有软中断、tasklet、工作队列和线程化irq,不过基于实际开发中的常用性,本篇只介绍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
相比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