对于linux中的工作队列,当然有很多书上都写了,网上也有很多文章反复的写.但是这里还是要写一写,作为内核中虽然小但是很重要的一个应用,基础要打扎实,理清原理.在3.1.1内核中和以前内核中有了些许变化. 这里参考资料:《深入linux设备驱动程序内核机制》、《深入理解linux内核》 《精通linux驱动程序开发》等.
内核中基本结构定义和基本操作函数:kernel/workqueue.c
include/linux/workqueue.c
这里并不准备先介绍结构体定义和操作函数或者宏,而是先看一个实际代码的例子:
drivers/isdn/capi/kcapi.c
/*
* The notifier will result in adding/deleteing of devices. Devices can
* only removed in user process, not in bh.
*/
static int notify_push(unsigned int event_type, u32 controller)
{
struct capictr_event *event = kmalloc(sizeof(*event), GFP_ATOMIC);
if (!event)
return -ENOMEM;
INIT_WORK(&event->work, do_notify_work);
event->type = event_type;
event->controller = controller;
queue_work(kcapi_wq, &event->work);
return 0;
}
这里我们只关注粗体部分,INIT_WORK故名思意初始化工作,我们看看它具体做了什么.
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
/*
* initialize all of a work item in one go
*
* NOTE! No point in using "atomic_long_set()": using a direct
* assignment of the work data initializer allows the compiler
* to generate better code.
*/
#ifdef CONFIG_LOCKDEP
#define __INIT_WORK(_work, _func, _onstack) \
do { \
static struct lock_class_key __key; \
\
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT();\
lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0);\
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
#else
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT();\
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
#endif
我们继续看PREPARE_WORK:
/*
* initialize a work item's function pointer
*/
#define PREPARE_WORK(_work, _func) \
do { \
(_work)->func = (_func); \
} while (0)
不用多说,很简单.不过这里觉得有必要把工作的结构体贴出来:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
还有间接使用的结构体:
struct capictr_event {
struct work_struct work;
unsigned int type;
u32 controller;
};
初始化完工作及其延时要执行的函数,这里我们看下延时执行的函数(也就是我们想要完成的任务).
static void do_notify_work(struct work_struct *work)
{
struct capictr_event *event =
container_of(work, struct capictr_event, work);
blocking_notifier_call_chain(&ctr_notifier_list, event->type,
(void *)(long)event->controller);
kfree(event);
}
这里也不多解释,只需要注意下系统宏 container_of使用的意义就行.
我们回到刚开始的部分,接着是
queue_work(kcapi_wq, &event->work);
也就是把我们的工作加入到工作队列中,然后由队列管理延时操作.
/**
* queue_work - queue work on a workqueue
* @wq: workqueue to use
* @work: work to queue
*
* Returns 0 if @work was already on a queue, non-zero otherwise.
*
* We queue the work to the CPU on which it was submitted, but if the CPU dies
* it can be processed by another CPU.
*/
int queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
int ret;
ret = queue_work_on(get_cpu(), wq, work);
put_cpu();
return ret;
}
下面我们就看看工作队列的创建:
基本的创建工作队列函数有两个:
#define create_workqueue(name) \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
******************************
我们看具体的初始化:
static struct workqueue_struct *kcapi_wq;
static int __init kcapi_init(void)
{
int err;
kcapi_wq = alloc_workqueue("kcapi", 0, 0);
if (!kcapi_wq)
return -ENOMEM;
register_capictr_notifier(&capictr_nb);
err = cdebug_init();
if (err) {
unregister_capictr_notifier(&capictr_nb);
destroy_workqueue(kcapi_wq);
return err;
}
kcapi_proc_init();
return 0;
}
到这里我想大家已经清晰了.
说到工作队列,我们就不得不提下软中断、tasklet. 基本原理是一样的.有兴趣的可以自己看相关代码分析学习.这里就简单总结下它们的区别和应用场景:
工作队列:延时操作运行在进程的上下文中,运行睡眠.
tasklet:动态分配,延时操作运行在中断上下文中,不能睡眠,并且一个tasklet任意时刻,只能运行一个实例.
软中断:它是静态分配的,可以并发的在多个cpu上运行,可重入,必须明确的使用自旋锁.