workqueue是另一種將工作推後的形式,它允許重新調度及休眠。
workqueue的實現
本文基於linux 內核4.10,workqueue 實現爲CMWQ(Concurrency Managed Workqueue),引入cmwq的原因可以參考CMWQ概述 。
workqueue數據結構
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */
struct mutex mutex; /* protects this wq */
int work_color; /* WQ: current work color */
int flush_color; /* WQ: current flush color */
atomic_t nr_pwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* WQ: first flusher */
struct list_head flusher_queue; /* WQ: flush waiters */
struct list_head flusher_overflow; /* WQ: flush overflow list */
struct list_head maydays; /* MD: pwqs requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* WQ: drain in progress */
int saved_max_active; /* WQ: saved pwq max_active */
struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
#ifdef CONFIG_SYSFS
struct wq_device *wq_dev; /* I: for sysfs interface */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[WQ_NAME_LEN]; /* I: workqueue name */
/*
* Destruction of workqueue_struct is sched-RCU protected to allow
* walking the workqueues list without grabbing wq_pool_mutex.
* This is used to dump all workqueues from sysrq.
*/
struct rcu_head rcu;
/* hot fields used during command issue, aligned to cacheline */
unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
};
可以這樣理解,
work_struct: 工作
workqueue_struct:工作的集合
pool_workqueue: 中間人,負責建立起 workqueue 和 worker_pool 之間的關係。
worker_pool: 工人的集合
worker: 工人
workqueue的使用方法
1.workqueue的創建
#define alloc_ordered_workqueue(fmt, flags, args...) \
alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
#define create_workqueue(name) \
alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
#define create_freezable_workqueue(name) \
alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND | \
WQ_MEM_RECLAIM, 1, (name))
#define create_singlethread_workqueue(name) \
alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
參數含義:
name: workqueue名字;
WQ_NON_REENTRANT:默認情況下,工作隊列只是確保在同一 CPU 上不可重入,即工作項不能在同一 CPU 上被多個工作者線程併發執行,但容許在多個 CPU 上併發執行。但該標誌標明在多個 CPU 上也是不可重入的,工作項將在一個不可重入工作隊列中排隊,並確保至多在一個系統範圍內的工作者線程被執行。
WQ_UNBOUND:沒有被限定到特定的 CPU,
WQ_FREEZEABLE:可凍結 wq 參與系統的暫停操作。該工作隊列的工作項將被暫停,除非被喚醒,否則沒有新的工作項被執行。
WQ_MEM_RECLAIM:所有的工作隊列可能在內存回收路徑上被使用。使用該標誌則保證至少有一個執行上下文而不管在任何內存壓力之下。
WQ_HIGHPRI:高優先級的工作項將被排練在隊列頭上,並且執行時不考慮併發級別;換句話說,只要資源可用,高優先級的工作項將儘可能快的執行。高優先工作項之間依據提交的順序被執行。
WQ_CPU_INTENSIVE:CPU 密集的工作項對併發級別並無貢獻,換句話說,可運行的 CPU 密集型工作項將不阻止其它工作項。這對於限定得工作項非常有用,因爲它期望更多的 CPU 時鐘週期,所以將它們的執行調度交給系統調度器。
第三個參數’1’: maxactive,決定了一個wq在per-CPU上能執行的最大工作項。比如 max_active 設置爲16,表示一個工作隊列上最多16個工作項能同時在per-CPU上同時執行。
2.workqueue的初始化
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
#define INIT_WORK_ONSTACK(_work, _func) \
__INIT_WORK((_work), (_func), 1)
#define INIT_DELAYED_WORK(_work, _func) \
__INIT_DELAYED_WORK(_work, _func, 0)
#define INIT_DELAYED_WORK_ONSTACK(_work, _func) \
__INIT_DELAYED_WORK_ONSTACK(_work, _func, 0)
#define INIT_DEFERRABLE_WORK(_work, _func) \
__INIT_DELAYED_WORK(_work, _func, TIMER_DEFERRABLE)
#define INIT_DEFERRABLE_WORK_ONSTACK(_work, _func) \
__INIT_DELAYED_WORK_ONSTACK(_work, _func, TIMER_DEFERRABLE)
3.workqueue的調度
extern bool queue_work_on(int cpu, struct workqueue_struct *wq,
struct work_struct *work);
extern bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *work, unsigned long delay);
extern bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay);
4.workqueue的銷燬
extern void destroy_workqueue(struct workqueue_struct *wq);
使用示例
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
void myfunc(struct work_struct*ws);
struct workqueue_struct *test_wq;
DECLARE_WORK(mywork,myfunc);
void myfunc(struct work_struct *ws)
{
printk("hello world, current pid is %d\n", current->pid);
}
static int __init test_init(void)
{
//struct work_struct *mywork;
test_wq = create_workqueue("test_wq");
if (!test_wq) {
printk("Error: allocate workqueue fail!\n");
return -ENOMEM;
}
queue_work(test_wq, &mywork);
}
static void __exit test_exit(void)
{
destroy_workqueue(test_wq);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");