workqueue和其他的bottom half最大的不同是它是运行在进程上下文中的,它可以睡眠,这和其他bottom half机制有本质的不同,大大方便了驱动工程师撰写中断处理代码。当然,驱动模块也可以自己创建一个kernel thread来解决defering work,但是,如果每个driver都创建自己的kernel thread,那么内核线程数量过多,这会影响整体的性能。因此,最好的方法就是把这些需求汇集起来,提供一个统一的机制,也就是传说中的work queue了
具体的流程是这样的 分为工作队列(workqueue)、工作(work)、工作线程池和工作队列渠道(pool_workqueue)、worker(工作线程)、worker_pool(工作线程池)
我们这里分析的是CMWQ机制 也就是concurrent-manage workqueue
工作队列
1、workqueue。定义如下:
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq; -----per-cpu work queue struct
struct list_head list; ---workqueue list 负责加入链表
const char *name;
int singlethread; ----single thread or multi thread
int freezeable; ----和电源管理相关的一个flag
};
系统在启动的时候会调用init_workqueues函数来初始化几个默认的workqueue 通过create_workqueue()等一些接口创建
2.workqueue
struct workqueue_struct {
struct list_head pwqs; //所有的pool_workqueue 都挂入链表中
struct list_head list; //所有的workqueue挂入该链表
…
struct pool_workqueue __percpu *cpu_pwqs; -----指向per cpu的pool workqueue
struct pool_workqueue __rcu *numa_pwq_tbl[]; ----指向per node的pool workqueue
};
3、work
struct work_struct {
atomic_long_t data; //低比特位是标志位 高比特位是worker_pool的ID号或者其他
struct list_head entry;
work_func_t func; //调度函数 work被调度执行的时候就是调用这个函数
};
work_func_t 定义
typedef void (*work_func_t)(struct work_struct *work);
4、pool_workqueue
struct pool_workqueue {
struct worker_pool pool; / I: the associated pool */
struct workqueue_struct wq; / I: the owning workqueue */
int work_color; /* L: current color */
int flush_color; /* L: flushing color */
int refcnt; /* L: reference count */
int nr_in_flight[WORK_NR_COLORS];
/* L: nr of in_flight works */
int nr_active; /* L: nr of active works */
int max_active; /* L: max active works */
struct list_head delayed_works; /* L: delayed works */
struct list_head pwqs_node; /* WR: node on wq->pwqs */
struct list_head mayday_node; /* MD: node on wq->maydays */
/*
* Release of unbound pwq is punted to system_wq. See put_pwq()
* and pwq_unbound_release_workfn() for details. pool_workqueue
* itself is also sched-RCU protected so that the first pwq can be
* determined without grabbing wq->mutex.
*/
struct work_struct unbound_release_work;
struct rcu_head rcu;
} __aligned(1 << WORK_STRUCT_FLAG_BITS);
WORK_STRUCT_FLAG_BITS的值为8
因此pool_workqueue 是按照256Byte对齐的 这样方便吧bit[8:31]放进work->data中
5、worker
struct worker {
/* on idle list while idle, on busy hash table while busy */
union {
struct list_head entry; /* L: while idle */
struct hlist_node hentry; /* L: while busy */
};
struct work_struct *current_work; /* L: work being processed */
work_func_t current_func; /* L: current_work's fn */
struct pool_workqueue *current_pwq; /* L: current_work's pwq */
bool desc_valid; /* ->desc is valid */
struct list_head scheduled; /* L: scheduled works */
/* 64 bytes boundary on 64bit, 32 on 32bit */
struct task_struct *task; /* I: worker task */
struct worker_pool *pool; /* I: the associated pool */
/* L: for rescuers */
struct list_head node; /* A: anchored at pool->workers */
/* A: runs through worker->node */
unsigned long last_active; /* L: last active timestamp */
unsigned int flags; /* X: flags */
int id; /* I: worker id */
/*
* Opaque string set with work_set_desc(). Printed out with task
* dump for debugging - WARN, BUG, panic or sysrq.
*/
char desc[WORKER_DESC_LEN];
/* used only by rescuers to point to the target workqueue */
struct workqueue_struct *rescue_wq; /* I: the workqueue to rescue */
};
系统中存在若干worker pool,不和特定的workqueue关联,而是所有的workqueue共享 通过pool_workqueue作为桥梁 工作队列workqueue选择合适的woker_pool并且选择合适的worker thread
来执行队列上的work
使用CMWQ机制的好处是
减少了资源的浪费 :没有创建那么多woker thread
保持了并发的灵活性:worker pool会根据情况灵活的创建thread来处理work
如何解决资源的浪费::当thread pool中处于运行状态的worker thread等于0,并且有需要处理的work的时候,thread pool就会创建新的worker线程。当worker线程处于idle的时候,不会立刻销毁它,而是保持一段时间,如果这时候有创建新的worker的需求的时候,那么直接wakeup idle的worker即可。一段时间过去仍然没有事情处理,那么该worker thread会被销毁。
有需要的时候才创建
并发解决:
我们用某个cpu上的bound workqueue来描述该问题。假设有A B C D四个work在该cpu上运行,缺省的情况下,thread pool会创建一个worker来处理这四个work。在旧的workqueue中,A B C D四个work毫无疑问是串行在cpu上执行,假设B work阻塞了,那么C D都是无法执行下去,一直要等到B解除阻塞并执行完毕。
对于CMWQ,当B work阻塞了,thread pool可以感知到这一事件,这时候它会创建一个新的worker thread来处理C D这两个work,从而解决了并发的问题。由于解决了并发问题,实际上也解决了由于竞争一个execution context而引入的各种问题(例如dead lock)。
一个是阻塞等待 一个是创建新的worker处理
在bounded的的线程池下
系统在初始化的时候会为per cpu 创建两个线程池 一个是普通优先级的线程池 一个是高优先级的线程池 例如如果该workqueue是bounded类型并且设定了high priority,那么挂入该workqueue的work将由per cpu的highpri worker-pool来处理。
在系统初始化的时候就会创建两个线程池 初始化函数是init_workqueues
static int __init init_workqueues(void)
{
//per有两种优先级 一个是普通优先级 一个是高优先级
int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
int i, cpu;
WARN_ON(__alignof__(struct pool_workqueue) < __alignof__(long long));
BUG_ON(!alloc_cpumask_var(&wq_unbound_cpumask, GFP_KERNEL));
cpumask_copy(wq_unbound_cpumask, cpu_possible_mask);
//创建一个pool_workqueue的slab缓存对象
pwq_cache = KMEM_CACHE(pool_workqueue, SLAB_PANIC);
cpu_notifier(workqueue_cpu_up_callback, CPU_PRI_WORKQUEUE_UP);
hotcpu_notifier(workqueue_cpu_down_callback, CPU_PRI_WORKQUEUE_DOWN);
//考虑了NUMA系统的特殊吃力
wq_numa_init();
/* initialize CPU pools */
//为每个cpu创建两个worker_pool 根据cpu号
for_each_possible_cpu(cpu) {
struct worker_pool *pool;
i = 0;
for_each_cpu_worker_pool(pool, cpu) {
BUG_ON(init_worker_pool(pool));
pool->cpu = cpu;
cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
pool->attrs->nice = std_nice[i++];
pool->node = cpu_to_node(cpu);
/* alloc pool ID */
mutex_lock(&wq_pool_mutex);
BUG_ON(worker_pool_assign_id(pool));
mutex_unlock(&wq_pool_mutex);
}
}
//为系统每一个在线CPU中的每个worker_pool分别创建一个工作线程
/* create the initial worker */
for_each_online_cpu(cpu) {
struct worker_pool *pool;
for_each_cpu_worker_pool(pool, cpu) {
pool->flags &= ~POOL_DISASSOCIATED;
BUG_ON(!create_worker(pool));
}
}
/* create default unbound and ordered wq attrs */
//创建UNBOUND类型和ORDERED类型的workqueue属性
//UNBOUND属性就是线程池不与CPU匹配 worker线程可以调度到任意的cpu上去
//ORDERED就是说明
for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
struct workqueue_attrs *attrs;
BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
attrs->nice = std_nice[i];
unbound_std_wq_attrs[i] = attrs;
/*
* An ordered wq should have only one pwq as ordering is
* guaranteed by max_active which is enforced by pwqs.
* Turn off NUMA so that dfl_pwq is used for all nodes.
*/
BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
attrs->nice = std_nice[i];
attrs->no_numa = true;
ordered_wq_attrs[i] = attrs;
}
//分配workqueue
system_wq = alloc_workqueue(“events”, 0, 0);
system_highpri_wq = alloc_workqueue(“events_highpri”, WQ_HIGHPRI, 0);
system_long_wq = alloc_workqueue(“events_long”, 0, 0);
system_unbound_wq = alloc_workqueue(“events_unbound”, WQ_UNBOUND,
WQ_UNBOUND_MAX_ACTIVE);
system_freezable_wq = alloc_workqueue(“events_freezable”,
WQ_FREEZABLE, 0);
system_power_efficient_wq = alloc_workqueue(“events_power_efficient”,
WQ_POWER_EFFICIENT, 0);
system_freezable_power_efficient_wq = alloc_workqueue(“events_freezable_power_efficient”,
WQ_FREEZABLE | WQ_POWER_EFFICIENT,
0);
BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
!system_unbound_wq || !system_freezable_wq ||
!system_power_efficient_wq ||
!system_freezable_power_efficient_wq);
return 0;
}
early_initcall(init_workqueues);
最终是调用alloc_workqueue()函数来创建相关的函数
#define alloc_workqueue(fmt, flags, max_active, args…) \
__alloc_workqueue_key((fmt), (flags), (max_active), \
NULL, NULL, ##args)
#endif
workqueue的使用示例
1.使用系统默认的workqueue
方法
void do_save(struct work_struct *p_work)
{
struct my_work_struct *p_test_work = container_of(p_work, struct my_work_stuct, save);
printk(“%d\n”,p_test_work->test);
}
struct my_work_stuct{
int test;
struct work_stuct save;
};
struct my_work_stuct test_work;
INIT_WORK(&(test_work.save), do_save);
//调度这个work
schedule_work(&(test_work.save);
//然后再取消这个work
cancel_work_sync(&(test_work.save);
2.自己创建workqueue 但是这样的话 会创建比较多的内核线程 会导致性能下降 一般的话使用系统自带的workqueue就好了
struct my_work_stuct{
int test;
struct work_stuct save;
};
struct my_work_stuct test_work;
struct workqueue_struct *test_workqueue;
void do_save(struct work_struct *p_work)
{
struct my_work_struct *p_test_work = container_of(p_work, struct my_work_stuct, save);
printk(“%d\n”,p_test_work->test);
}
void test_init()
{
test_workqueue = create_workqueue(“test_workqueue”);
if (!test_workqueue)
panic(“Failed to create test_workqueue\n”);
INIT_WORK(&(test_work.save), do_save);
//在新的workqueue上调度一个work
queue_work(test_workqueue, &(test_work.save));
}
void test_destory(void)
{
if(test_workqueue)
destroy_workqueue(test_workqueue);
}