workqueue相对于softirq和tasklet,最大的不同点是workqueue是运行于用户空间,因此可以执行“会睡眠的函数”,tasklet就不行了,因为tasklet处于中断上下文,中断上下文是不允许发生调度的.
理解workqueue的关键是三个结构体
//这个结构体是每个CPU都有的
struct cpu_workqueue_struct {
spinlock_t lock;
struct list_head worklist; //挂着很多work_struct的头部,也就是需要做的工作
wait_queue_head_t more_work;
struct work_struct *current_work;
struct workqueue_struct *wq;
struct task_struct *thread;
} ____cacheline_aligned;
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq; //指向一个数组
//用于在workqueues上面的结点,工作队列本身是用链表串起来的
struct list_head list;
const char *name;
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
struct work_struct {
atomic_long_t data;
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry; //在worklist上的挂接点
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
上面三者之间的关系用图来表示
工作队列的使用
1、初始化工作work_struct
#define INIT_WORK(_work, _func) \
do { \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
2、调度
int schedule_work(struct work_struct *work)
{
return queue_work(keventd_wq, work);
}
调度的工作是交给系统预定义的工作队列keventd_wq 来进行的,这个工作队列在kernel\workqueue.c中定义
void __init init_workqueues(void)
{
alloc_cpumask_var(&cpu_populated_map, GFP_KERNEL);
cpumask_copy(cpu_populated_map, cpu_online_mask);
singlethread_cpu = cpumask_first(cpu_possible_mask);
cpu_singlethread_map = cpumask_of(singlethread_cpu);
hotcpu_notifier(workqueue_cpu_callback, 0);
keventd_wq = create_workqueue("events");
BUG_ON(!keventd_wq);
}