linux内核多队列,Linux Kernel 中 Workqueue 使用系统默认队列和创建队列的方法
關(guān)于workqueue,我們還是有很多話要說。
想必大家對workqueue相關(guān)的函數(shù)(schedule_work 、queue_work、INIT_WORK、create_singlethread_workqueue 等)都不陌生。但說起差異,可能還有許多話需要坐下來慢慢講。
對于workqueue,與之最為相關(guān)的兩個東西便是work和queue。
work是用來綁定實(shí)際執(zhí)行函數(shù)使用的結(jié)構(gòu)體
queue是用來鏈接work使用的隊(duì)列。
具體的結(jié)構(gòu)體,可以自己到/kernel/include/linux/workqueue.h中自行查看,這里不再贅述。
我們想關(guān)注的重點(diǎn)在于:
1:系統(tǒng)中是否有default的workqueue供我們使用
2:我們能否創(chuàng)建自己的workqueue?如何創(chuàng)建?
Yes!你說對了。linux系統(tǒng)所提供的workqueue機(jī)制中,已經(jīng)幫忙提供了一個默認(rèn)的workqueue隊(duì)列“system_wq”,并提供了一套函數(shù)來方便大家直接使用。
例子來了:
static struct work_struct?work;
INIT_WORK(&work,?run);
schedule_work(&work);
static void?run(struct work_struct *work)
{
Do something here!!
}
就這么簡單的,當(dāng)然,你也可以用DECLARE_WORK來完成和INIT_WORK同樣初始化work的工作。區(qū)別是DECLARE_WORK是預(yù)編譯命令,而INIT_WORK可以在code中動態(tài)初始化。
那么除了調(diào)用schedule_work直接把work放到系統(tǒng)default的workqueue中外,我們還有什么辦法可以初始化自己的workqueue,并且放入work呢?
我們看看函數(shù)schedule_work的定義,一切就真相大白了!
static inline bool?schedule_work(struct work_struct *work)
{
return?queue_work(system_wq, work);
}
哈哈,原來schedule_work是把傳入的work直接放入了系統(tǒng)的default workqueue “system_wq”中而已。
自然,我們只需要調(diào)用queue_work函數(shù)來綁定workqueue和work就ok啦!
初始化work的方法和前面一樣,只要調(diào)用DECLARE_WORK或INIT_WORK就好了。
那么我們?nèi)绾稳?chuàng)建自己的workqueue呢?
答案是:
#define?alloc_ordered_workqueue(fmt, flags, args…)?????????????? \
alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
#define?create_workqueue(name)????????????????????????????? \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
#define?create_freezable_workqueue(name)??????????????????? \
alloc_workqueue((name), WQ_FREEZABLE | WQ_UNBOUND | WQ_MEM_RECLAIM, 1)
#define?create_singlethread_workqueue(name)??????????????????? \
alloc_workqueue((name), WQ_UNBOUND | WQ_MEM_RECLAIM, 1)
我們只要調(diào)用如上幾種方法的某一種,即可創(chuàng)建屬于自己的workqueue了。kernel中最常見到的函數(shù)是create_singlethread_workqueue。
我們只要拿這個函數(shù)舉個例子就美好了,栗子來了!
static struct workqueue_struct *time_sync_wq;
time_sync_wq?=?create_singlethread_workqueue(“timesync“); //timesync就是workqueue的名字
static?DECLARE_WORK(etr_work,?etr_work_fn);
queue_work(time_sync_wq, &etr_work);
這樣一來,我們的work和自己的workqueue就綁在一起了。
個人認(rèn)為,自己新建wq最大的好處:可以避免system_wq中被掛的work過多,或者由于某個被掛上去的work處理函數(shù)質(zhì)量不高導(dǎo)致死鎖,而導(dǎo)致掛在同一個queue上的我們自己work的handler因?yàn)闊o法被調(diào)度到而完蛋了。
當(dāng)然,建太多自己的workqueue,必然會導(dǎo)致系統(tǒng)調(diào)度開銷的變大,所以需要取舍。
至于DELAYED_WORK
#define?INIT_DELAYED_WORK(_work, _func)???????????????????????? \
__INIT_DELAYED_WORK(_work, _func, 0)
queue_delayed_work
schedule_delayed_work
都和前面類似,就不再贅述了。
補(bǔ)充下:
咱們可以調(diào)用cancel_delayed_work來把還未執(zhí)行的work給取消掉。
基本上每次?cancel_delayed_work?之后您都得調(diào)用flush_scheduled_work() 這個函數(shù) , 特別是對于內(nèi)核模塊 , 如果一個模塊使用了工作隊(duì)列機(jī)制 , 并且利用了系統(tǒng)的default隊(duì)列 , 那么在卸載這個模塊之前 , 您必須得調(diào)用這個函數(shù) , 這叫做刷新一個工作隊(duì)列 , 也就是說 , 該函數(shù)會一直等待 , 直到隊(duì)列中所有對象都被執(zhí)行以后才返回 ,從而避免隊(duì)列調(diào)度錯誤。
函數(shù)cancel_delayed_work_sync的出現(xiàn),讓新的流程變得更加簡單了,大家參照kernel中的代碼,很容易知道應(yīng)該怎么用。
最后別忘了調(diào)用destroy_workqueue等清尾的函數(shù)哦~~
問題來了:
如果我們在handler的執(zhí)行過程中,同時再次調(diào)用調(diào)度函數(shù)queue_work,那么我們的handler會被執(zhí)行多少次呢?(執(zhí)行被調(diào)度的次數(shù),還是就只執(zhí)行一次呢?)
解答:
這個問題比較有意思,寫了這個例子來驗(yàn)證答案(例子跑在android 4.4 code base中,不排除后續(xù)kernel函數(shù)被修改)
sample test code:
static struct workqueue_struct *test_wq;
static void try_to_test(struct work_struct *work){printk(“[bevis] :wq into \n”);
msleep(5*1000);? //5s
printk(“[bevis] :wq out \n”);
}
static DECLARE_WORK(mytest_work, try_to_test);
gsensor probe function end add :
test_wq =alloc_ordered_workqueue(“test_wq”, 0);//初始化一個單獨(dú)的工作隊(duì)列
int a = 0;
for(a=0 ; a<3 ; a++){
printk(“[bevis] : read func (%d) before \n”,a);
queue_work(test_wq, &mytest_work);//讓這個隊(duì)列開始被調(diào)度
printk(“[bevis] : read func (%d) msleep 2s \n”,a);
msleep(2*1000);
printk(“[bevis] : read func (%d) after \n”,a);
}
log如下:
10-16 14:10:41.940 I/KERNEL? (? 109): [??? 6.954658] [bevis] : read func (0) before
10-16 14:10:41.940 I/KERNEL? (? 109): [??? 6.954727] [bevis] : read func (0) msleep 2s
10-16 14:10:41.940 I/KERNEL? (? 109): [??? 6.954742] [bevis] :wq into
10-16 14:10:43.950 I/KERNEL? (? 109): [??? 8.960997] [bevis] : read func (0) after
10-16 14:10:43.950 I/KERNEL? (? 109): [??? 8.961085] [bevis] : read func (1) before
10-16 14:10:43.950 I/KERNEL? (? 109): [??? 8.961155] [bevis] : read func (1) msleep 2s
10-16 14:10:45.960 I/KERNEL? (? 109): [?? 10.971954] [bevis] : read func (1) after
10-16 14:10:45.960 I/KERNEL? (? 109): [?? 10.972076] [bevis] : read func (2) before
10-16 14:10:45.960 I/KERNEL? (? 109): [?? 10.972132] [bevis] : read func (2) msleep 2s
10-16 14:10:46.950 I/KERNEL? (??? 6): ?[?? 11.961884] [bevis] :wq out
10-16 14:10:46.950 I/KERNEL? (??? 6): ?[?? 11.961953] [bevis] :wq into
10-16 14:10:47.970 I/KERNEL? (? 109): [?? 12.982276] [bevis] : read func (2) after
10-16 14:10:51.960 I/KERNEL? (??? 6): ?[?? 16.973719] [bevis] :wq out
看到了吧,雖然我們使用queue_work函數(shù)調(diào)度了三次handler,但實(shí)際上wq的handler只被執(zhí)行了兩次。
如果把probe函數(shù)的delay直接拿掉,你更加會發(fā)現(xiàn),即使wq被調(diào)度三次,handler卻實(shí)際上只跑了一次。
結(jié)論:
如果wq被調(diào)度的時候,wq中的這個handler正在執(zhí)行過程中,則這次調(diào)度會被遺棄。只有handler執(zhí)行完成并返回后,下次調(diào)度才會真正的生效。
kernel這么做的原因,我猜想應(yīng)該是為了防止,檔某個wq的handler在執(zhí)行過程中因?yàn)橘Y源無法獲取而暫時阻塞時,
不會因?yàn)槠渌M(jìn)程再次調(diào)度了該wq而導(dǎo)致出現(xiàn)線程實(shí)例的不斷累加。
實(shí)際上,在絕大多數(shù)情況下,我們只需要一個handler實(shí)例來幫忙做事就夠了,例如earlysuspend的處理函數(shù)中,只要userspace進(jìn)行想睡眠,那就直接調(diào)度suspend wq的handler,而不必管再關(guān)心上次的suspend過程是否有阻塞。
這樣一來,邏輯就清爽多了。
如需轉(zhuǎn)載,請注明出處。
總結(jié)
以上是生活随笔為你收集整理的linux内核多队列,Linux Kernel 中 Workqueue 使用系统默认队列和创建队列的方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统:Linux环境变量相关知识总结
- 下一篇: Linux环境安装zookeeper3.