先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
一 why
前一篇博文中, 我们介绍了中断下半部以及tasklet的实现方式,同时我们知道除了tasklet可以实现中断下半部处理之外,workqueue也可以实现中断下半部处理。那么为什么还需要workqueue呢?workqueue和tasklet之间有什么区别呢?
1. tasklet运行在中断上下文,在tasklet的处理函数中不能调用休眠函数,比如sleep函数或者kmalloc函数等等
2. workqueue运行在进程上下文,可以执行休眠的动作
二 what
实际上workqueue的基本实现在内核中很类似,驱动中只要初始化一个workqueue的对象,内核会将这个workqueue放到一个队列上去,workqueue的方法调用是由内核自己调度的,驱动一般无法控制,只能启动一个workqueue,如下
三 how
a. 初始化
// 结构体
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; //下半部的实现方法
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
// 方法
typedef void (*work_func_t)(struct work_struct *work);
// 初始化
struct work_struct myworkqueue;
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
b. 在上半部中放入到内核线程中----启动
schedule_work(struct work_struct * work);
一般步骤
1. 驱动中先注册一个中断
2. 注册中断的时候,同时初始化中断下半部workqueue
3. 实现中断处理函数,在中断处理程序中,启动下半部workqueue
4. 实现下半部处理函数
一般框架如下
static int __init key_drv_init(void)
{
int ret = 0;
//1. 设定一个全局的设备对象
key_dev = (key_desc_t *)kzalloc(sizeof(key_desc_t), GFP_KERNEL);
if (key_dev == NULL) {
printk("kzalloc failure\n");
return -EINVAL;
}
......
//4. 注册中断
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_exit10", NULL);
// 初始化中断下半部workqueue
INIT_WORK(&key_dev->myworkqueue, key_irq_bottom_half_workqueue);
return 0;
}
中断处理函数
irqreturn_t key_irq_handler(int irq_no, void *devid)
{
printk("-----------------\n");
//启动中断下半部开始调度
schedule_work(&key_dev->myworkqueue);
......
return IRQ_HANDLED;
}
中断下半部处理函数
void key_irq_bottom_half_workqueue(struct work_struct *work)
{
......
}
我们发现,workqueue的初始化过程基本上和tasklet一模一样,只是调用的接口函数不一样。