linux自從引入工作隊列之後,越來越多的工作都交給了它,工作隊列有什麼優點?有程序上下文?可延遲?是的,這些都是它的優點,但是總不能因為它的這些有限的優勢而将所有的工作都交給它來做吧,可是驅動或者子系統的開發者卻把它當成了銀彈,動不動就create_workqueue,然後随便一個什麼小小不言的動作就queue_work,結果呢?結果導緻了核心空間充斥了大量的工作隊列,這些工作隊列的本質是每個工作隊列(僅限于MT)都會在每一個cpu上建立一個核心線程,實際上沒有被queue_work根本就沒有機會工作,進而大量的核心線程中隻有少量的會投入工作,這就浪費了寶貴的核心記憶體資源,而且還加重了排程器的排程負擔,這還不是最令人遺憾的,試想,如果一些任務對在哪個cpu上執行并不是很在意,但是這些任務湊巧被queue到了同一個cpu上的一個工作隊列上,然後該cpu上的該工作隊列上的目前工作陷入了sleep,這就導緻後續queue的工作無法被執行,因為對于同一個工作隊列來講,其上的工作是嚴格串行執行的,隻有一個工作完成之後,其它的工作才能被執行。本質上,每一個工作隊列在每一個cpu上都有且隻有一個隊列,而其它工作隊列的每cpu隊列不能互相幫助的,即使它們閑着也不行。這就類似嚴格的流水線作業,最近看了郎鹹平的一本書,他批判了嚴格的流水線作業,比如上世紀20年代福特和現在富士康使用的那種,這種流水線的每一個環節的勞工做的事情很單調,很簡單,然而他們很忙,一個請假或離崗将會導緻整個流水線停滞斷掉,流水線作業沒有到達的勞工将會閑着而不能休息一下或者做點别的,同時郎鹹平贊揚了佳能的“圓桌”,佳能沒有使用流水線生産,而是讓所有的勞工能分成若幹組,一組的成員圍坐在一個圓桌邊上,大家邊聊邊工作,一個人會所有的工作,即使一個人請假别人也可以幫他完成工作,如果一個人閑着,他可以去接一些别人目前的工作,最後,效率提高了,勞工們的工作更輕松了。我們的linux核心的工作隊列能否做類似的改進呢?
核心的開發者總是那麼的睿智,2.6核心引入了擁有程序上下文的工作隊列,2.6.20之後,工作隊列的資料結構進行了一次瘦身,管理機構瘦身了,然而工作隊列仍然浪費了大量的資源,并且效率不高,總的原因就在于大家各行其是,絲毫沒有合作的可能,為了為工作隊列徹底減負,核心開發者決定在2.6.36以後将所有的工作隊列合并成一個全局的隊列,僅僅按照工作重要性和時間緊迫性等做簡單的區分,每一類這樣的工作僅擁有一個工作隊列,而不管具體的工作,也就是說,每一類這樣的工作組成一個“圓桌”,這樣大家就可以互相幫忙了,不會出現牽一發而動全身的被動局面,也不會出現一個人很閑的情況。 也就是說,新的核心不按照具體工作建立隊列,而是按照cpu建立隊列,然後在這個每cpu的唯一隊列中按照工作的性質做一個簡單的區分,這個區分将影響工作被執行的順序。
新核心中的所有工作被排隊到一個global_cwq的每cpu的隊列當中,你仍然可以調用create_workqueue建立很多具體的工作隊列,然而這樣建立的所謂工作隊列除了其參數中的flag起作用之外,對排隊其中的具體工作沒有任何的限制性,所有的工作都排隊到了一個每cpu隊列中,然後原則上按照排隊的順序進行執行,期間受到排隊workqueue的flag進行微調,比如說如果一個工作排入了一個工作隊列w1,該w1建立時使用highpri标志,那麼它就在queue之後馬上執行...新工作隊列的另一大特點就是在建立工作隊列的時候并不建立工作者核心線程,這些核心線程由核心建立,原則上在核心初始化的時候,每一個cpu将會建立至少一個工作隊列線程-worker0,當建立一個工作隊列的時候,實際上什麼也沒有做,當建立一個具體的工作的時候,會将該工作委派給一個工作隊列,該工作隊列的具體執行者就是worker0,如果有必要(比如每cpu的全局隊列中有很多工作要做,比如有很多高優先級的工作要做),核心将會在worker0中建立更多的workerN,由這些worker盡可能并行完成所有的工作,具體我們可以看一下工作隊列的執行函數,注意,該核心線程并不依賴任何工作隊列,而是在系統初始化的時候被建立的:
static int worker_thread(void *__worker)
{
struct worker *worker = __worker;
struct global_cwq *gcwq = worker->gcwq;
worker->task->flags |= PF_WQ_WORKER;
woke_up:
spin_lock_irq(&gcwq->lock);
...
worker_leave_idle(worker);
recheck:
if (!need_more_worker(gcwq)) //是否需要處理,如果有高優先級的工作,如果工作隊列中有工作要做然而該cpu的全局隊列中卻已經沒有空閑處理核心線程,那就有必要處理了
goto sleep;
if (unlikely(!may_start_working(gcwq)) && manage_workers(worker)) //如果需要建立一個新的工作者核心線程,那麼就建立一個,該建立的核心線程為了處理更多的工作,核心絕不像以前那樣浪費核心資源,隻有在有必要的時候才會建立核心線程來處理手頭比較緊急的工作。
goto recheck;
do {
struct work_struct *work =
list_first_entry(&gcwq->worklist, struct work_struct, entry);
...
} else {
move_linked_works(work, &worker->scheduled, NULL);
process_scheduled_works(worker); //處理一個一個的工作
}
} while (keep_working(gcwq));
worker_set_flags(worker, WORKER_PREP, false);
sleep: //如果目前沒有工作要做,那麼繼續睡眠
if (unlikely(need_to_manage_workers(gcwq)) && manage_workers(worker))
worker_enter_idle(worker);
__set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irq(&gcwq->lock);
schedule();
goto woke_up;
}
static void process_one_work(struct worker *worker, struct work_struct *work)
...
if (unlikely(gcwq->flags & GCWQ_HIGHPRI_PENDING)) {
struct work_struct *nwork = list_first_entry(&gcwq->worklist,
struct work_struct, entry);
if (!list_empty(&gcwq->worklist) &&
get_work_cwq(nwork)->wq->flags & WQ_HIGHPRI)
wake_up_worker(gcwq); //有必要的話,比如有高優先級的工作,那麼喚醒另一個空閑的工作者線程來工作,這樣很好,隻按照工作性質來處理,而不管它具體屬于哪個工作隊列
}
if (unlikely(cpu_intensive)) //如果是原始的cpu型工作,那麼将按照原始的分時處理,此時有必要喚醒另一個工作者來處理其它工作
worker_set_flags(worker, WORKER_CPU_INTENSIVE, true);
f(work); //執行工作
可以看出,在新的工作隊列的體系結構中,在處理具體工作之前,首先看看有沒有必要開啟另一個工作者線程進行處理,這一切之是以可以被做到的根源在于所有工作隊列的工作排程全部屬于一個中央機構,而不再由以前的各個工作隊列分而治之,不該建立的工作者核心線程可以不建立,核心工作者線程隻有在需要的時候才被建立。在新工作隊列的體系下如果一個工作隊列的一個工作陷入了sleep,那麼其它的工作可以被其它的工作者核心線程執行,核心的程序排程器會排程到這個該cpu上“其它”的工作者核心線程的,if (!need_more_worker(gcwq))判斷會使得該核心線程繼續下去,進而執行别的任務,由于屬于所有工作隊列的工作都排在一個隊列中,那麼任何的工作者核心線程都可以執行任意的工作,這就不存在誰阻塞誰的問題了,隻要排程器和工作隊列的核心邏輯密切配合,隻要知道在什麼時候該建立一個工作者核心線程就可以了。新工作隊列的queue核心代碼如下:
static void insert_work(struct cpu_workqueue_struct *cwq,
struct work_struct *work, struct list_head *head,
unsigned int extra_flags)
struct global_cwq *gcwq = cwq->gcwq;
set_work_cwq(work, cwq, extra_flags);
list_add_tail(&work->entry, head); //head參數表示的是每cpu一個的全局隊列
if (__need_more_worker(gcwq)) //如果是諸如高優先級之類的工作或者目前已經沒有空閑的工作者了,那麼喚醒一個工作者,系統起碼要保持一個空閑的工作者程序以備用。
wake_up_worker(gcwq);
就是這樣,this is it!如果需要更詳細的資訊,我覺得核心代碼文檔中(2.6.36以上)的workqueue.txt是一個不錯的選擇,另外還有兩篇線上文檔:http://lwn.net/Articles/403891/;http://lwn.net/Articles/355347/;http://lwn.net/Articles/355700/,它們都很不錯的。最關鍵的,我們不應該用純技術的觀點來改進技術,小的方面說生活中的方方面面都是一緻,大的方面說,曆史中的很多事實都是相通的,隻要我們用這種心理考慮技術,所有的技術創新我們就會發現原來是這麼簡單。佳能的管理者和linux2.6.36工作隊列子系統研發者的思路是一緻的,我想我們的工作和生活中還會有很多這樣的一緻,沒準...
現在的女人如此喜歡逛街,我覺得這也許是她們遺留了蒙昧時期食物采集的傳統,男人不喜歡逛街,隻喜歡瞄準之後買了走人,我覺得這也許也是遺留了蒙昧時期狩獵的傳統,因為既然在狩獵,獵物們根本不會給你逛的機會...
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271150