一文講解tasklet_等待隊(duì)列工作隊(duì)列
tasklet
和定時(shí)器相關(guān)的另一個(gè)內(nèi)核設(shè)施是tasklet(小任務(wù))機(jī)制。中斷管理中大量使用了這種機(jī)制。
tasklet在很多方面類似內(nèi)核定時(shí)器:它們始終在中斷期間運(yùn)行 ,始終會在調(diào)度它們的同一CPU上運(yùn)行 ,而且都接收一個(gè)unsigned long參數(shù),tasklet也會在“軟件中斷”上下文以原子模式執(zhí)行。 和內(nèi)核定時(shí)器不同的是,我們不能要求tasklet在某個(gè)給定時(shí)間執(zhí)行。
軟件中斷是指打硬件中斷的同時(shí)執(zhí)行某些異步任務(wù)的一種內(nèi)核機(jī)制。
tasklet的數(shù)據(jù)結(jié)構(gòu)如下,使用前必須初始化,調(diào)用特定的函數(shù)或使用特定的宏來聲明該結(jié)構(gòu),可以完成tasklet的初始化:
tasklet為我們提供了許多有意思的特性:
一個(gè)tasklet可以稍后被禁止或者重新啟用;只有啟用的次數(shù)和禁止的次數(shù)相同時(shí),tasklet才會被執(zhí)行
和定時(shí)器類似,tasklet可以注冊自身
tasklet可被調(diào)度以在通常的優(yōu)先級或者高優(yōu)先級執(zhí)行。高優(yōu)先級的tasklet總會首先執(zhí)行。
如果系統(tǒng)負(fù)荷不重,則tasklet會立即得到執(zhí)行,但始終不會晚于下一個(gè)定時(shí)器滴答。
一個(gè)tasklet可以和其他tasklet并發(fā),但對自身來講是嚴(yán)格串行處理的,也就是說,同一tasklet永遠(yuǎn)不會在多個(gè)處理器上同時(shí)運(yùn)行。當(dāng)然我們已經(jīng)指出,tasklet始終會在調(diào)度自己的同一CPU上運(yùn)行。
下面描述了tasklet相關(guān)的內(nèi)核接口,可在tasklet結(jié)構(gòu)被初始化后使用:
void tasklet_disable(struct tasklet_struct *t);禁用指定的tasklet。該tasklet仍然可以用tasklet_schedule調(diào)度,但其執(zhí)行被推遲,直到該tasklet被重新啟用。如果tasklet當(dāng)前正在運(yùn)行,該函數(shù)會進(jìn)入忙等待直到tasklet退出為止;因此在調(diào)用tasklet_disable之后,我們可以確信該tasklet不會在系統(tǒng)任何地方運(yùn)行。
void tasklet_disable_nosync(struct tasklet_struct *t);禁用指定的tasklet,但不會等待任何正在運(yùn)行的tasklet退出。該函數(shù)返回后,tasklet是禁用的,而且在重新啟用前,不會再次被調(diào)度。但是,當(dāng)該函數(shù)返回時(shí),指定的tasklet可能仍在其他CPU上運(yùn)行。
void tasklet_enable(struct tasklet_struct *t);啟用一個(gè)先前被禁止的tasklet。如果該tasklet已經(jīng)被調(diào)度,它很快就會運(yùn)行。對tasklet_enable的調(diào)用必須和每個(gè)對tasklet_disable的調(diào)用匹配,因?yàn)閮?nèi)核對每個(gè)tasklet保存有一個(gè)“禁用計(jì)數(shù)”。
*void tasklet_schedule(struct tasklet_struct t); 調(diào)度執(zhí)行指定的tasklet。如果在獲得運(yùn)行機(jī)會之前,某個(gè)tasklet被再次調(diào)度,則該tasklet只會運(yùn)行一次。但是如果在該tasklet運(yùn)行時(shí)被調(diào)度,就會在完成后再次運(yùn)行。這樣,可確保正在處理事件時(shí)發(fā)生的其他事件也會被接收并注意到。這種行為也允許tasklet重新調(diào)度自身。
void tasklet_hi_schedule(struct tasklet_struct *t); 調(diào)度指定的tasklet以高優(yōu)先級執(zhí)行。當(dāng)軟件中斷處理例程運(yùn)行時(shí),它會在處理其他軟件中斷任務(wù)(包括”通?!暗膖asklet)之前處理高優(yōu)先級的tasklet。理想狀態(tài)下,只有具備低延遲需求的任務(wù)(比如填充音頻緩沖區(qū))才能使用這個(gè)函數(shù),這樣可避免由其他軟件中斷處理例程引入 的額外延遲。
void tasklet_kill(struct tasklet_struct *t);該函數(shù)確保指定的tasklet不會被再次調(diào)度運(yùn)行;當(dāng)設(shè)備要被關(guān)閉或者模塊要被移除時(shí),我們通常調(diào)用這個(gè)函數(shù)。如果tasklet正被調(diào)度執(zhí)行,該函數(shù)會等待其退出。如果tasklet重新調(diào)度自己,則應(yīng)該避免在調(diào)用tasklet_kill之前完成重新調(diào)度,這和del_timer_sync的處理類似。
tasklet在實(shí)現(xiàn)在kernel/softirq.c中。其中有兩個(gè)(通常優(yōu)先級和高優(yōu)先級)tasklet鏈表,它們作為per-CPU數(shù)據(jù)結(jié)構(gòu)而聲明,并且使用了類似內(nèi)核定時(shí)器那樣的CPU相關(guān)聯(lián)機(jī)制。tasklet管理中使用的數(shù)據(jù)結(jié)構(gòu)是個(gè)簡單鏈表,因?yàn)閠asklet不必像內(nèi)核定時(shí)器那樣來處理時(shí)間問題。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)??


工作隊(duì)列
從表面上來看,工作隊(duì)列(workqueue)類似于tasklet,它們都允許內(nèi)核代碼請求某個(gè)函數(shù)在將來的時(shí)間被調(diào)用。但是,兩者之間存在一些非常重要的區(qū)別,其中包括:
tasklet在軟件中斷上下文中運(yùn)行,因此,所有的tasklet代碼都必須是原子的。相反,工作隊(duì)列函數(shù)在一個(gè)特殊內(nèi)核進(jìn)程的上下文中運(yùn)行,因此它們具有更好的靈活性。尤其是工作隊(duì)列函數(shù)可以休眠。
tasklet始終運(yùn)行在被初始提交的同一處理器上,但這只是工作隊(duì)列的默認(rèn)方式。
內(nèi)核代碼可以請求工作隊(duì)列函數(shù)的執(zhí)行延遲給定的時(shí)間間隔。
兩者的關(guān)鍵區(qū)別在于:tasklet會在很短的時(shí)間段內(nèi)很快的執(zhí)行,并且以原子模式執(zhí)行,而工作隊(duì)列函數(shù)可具有更長的延遲并且不必原子化。兩種機(jī)制有各自適合的情形。
工作隊(duì)列的數(shù)據(jù)結(jié)構(gòu)為struct workqueue_struct類型,定義在<linux/workqueue.h>中。在使用前之前,我們必須顯式地創(chuàng)建一個(gè)工作隊(duì)列,可使用下面兩個(gè)函數(shù)之一:
每個(gè)工作隊(duì)列有一個(gè)或多個(gè)專用的進(jìn)程(“內(nèi)核線程”),這些進(jìn)程運(yùn)行提交到該隊(duì)列的函數(shù)。create_workqueue內(nèi)核會在系統(tǒng)中的每個(gè)處理器上為工作隊(duì)列創(chuàng)建專用線程。在許多情況下,眾多的線程可能對性能具有某種程度的殺傷力;因此如果單個(gè)工作線程足夠使用,那么應(yīng)該使用create_singlethread_workqueue創(chuàng)建工作隊(duì)列。
要向一個(gè)工作隊(duì)列提交一個(gè)任務(wù),需要填充一個(gè)work_struct結(jié)構(gòu),這可通過下面的宏在編譯時(shí)完成:
name是要聲明的結(jié)構(gòu)名稱,function是要從工作隊(duì)列中調(diào)用的函數(shù),而data是要傳遞給該函數(shù)的值。如果要在運(yùn)行時(shí)構(gòu)造work_struct結(jié)構(gòu),可使用下面兩個(gè)宏:
如果要將工作提交到工作隊(duì)列,則可使用下面的函數(shù)之一:
它們都是將work提交到給定的queue。但是如果使用queue_delayed_work,則實(shí)際的工作至少會在經(jīng)過指定的jiffies(由delayed決定)之后才會被執(zhí)行。如果工作被成功添加到隊(duì)列,則上述函數(shù)返回值為1。返回值為非零時(shí)意味著給定的work_struct結(jié)構(gòu)已要等待在該隊(duì)列中,從而不能兩次加入該隊(duì)列。
在將業(yè)的某個(gè)時(shí)間,工作函數(shù)會被調(diào)用,并傳入給定的data值。該函數(shù)會在工作線程的上下文運(yùn)行,因此如果有必要,它可以休眠,當(dāng)然我們應(yīng)該仔細(xì)考慮休眠會不會影響提交到同一工作隊(duì)列的其他任務(wù)。但是函數(shù)不能訪問用戶空間,這是因?yàn)樗\(yùn)行在內(nèi)核線程,而該線程沒有對應(yīng)的用戶空間可以訪問。
如果要取消某個(gè)掛起的工作隊(duì)列入口項(xiàng),可調(diào)用:
如果該入口項(xiàng)在開始執(zhí)行前被取消,則上述函數(shù)返回非零值。在調(diào)用它后內(nèi)核會確保不會執(zhí)行給定的初始化入口項(xiàng),但是如果該入口項(xiàng)已經(jīng)在其他處理器上運(yùn)行,則它返回0,并且返回后該入口項(xiàng)可能仍在運(yùn)行。為了絕對確保在cancel_delayed_work返回0之后,工作函數(shù)不會在系統(tǒng)中的任何地方運(yùn)行,則應(yīng)該隨后調(diào)用下面的函數(shù):
在該函數(shù)返回后,任何在該調(diào)用前被提交的工作函數(shù)都不會在系統(tǒng)任何地方運(yùn)行。
在結(jié)束工作隊(duì)列的使用后,可調(diào)用下面的函數(shù)釋放相關(guān)資源:
共享隊(duì)列
在許多情況下,設(shè)備驅(qū)動程序不需要有自己的工作隊(duì)列。如果我們只是偶爾需要向隊(duì)列中提交任務(wù),則一種更簡單、更有效的辦法是使用內(nèi)核提供的共享的默認(rèn)工作隊(duì)列。但是如果使用它則應(yīng)該記住我們正在和其他人共享該工作隊(duì)列。這意味著,我們不應(yīng)該長期獨(dú)占該隊(duì)列,即不能長時(shí)間休眠,而且我們的任務(wù)可能需要更長的時(shí)間才能獲得處理器時(shí)間。
使用共享隊(duì)列,工作的聲明和初始化和前面介紹的一樣,提交工作使用的是下面函數(shù):
取消已提交到共享隊(duì)列的工作和之前介紹過的一樣是cancel_delayed_work函數(shù)。但是刷新共享工作隊(duì)列需要另一個(gè)函數(shù):
void flush_scheduled_work(void);
因?yàn)槲覀儫o法知道其他人是否在使用該隊(duì)列,因此我們也無法知道在flush_scheduled_work返回前到底要花費(fèi)多少時(shí)間。
