轉自:http://blog.chinaunix.net/uid-9688646-id-4052595.html
是不是覺得很玄?像思念一樣玄?那好,我們來看點具體的,比如935行,INIT_DELAYED_WORK().這是一張新面孔.同志們大概注意到了,在hub這個故事裡,我們的講解風格略有變化,對於那些舊的東西,對於那些在usb-storage裡面講過很多次的東西,我們不會再多提,但是對於新鮮的東西,我們會花大把的筆墨去描摹.這樣做的原因很簡單,男人嘛,有幾個不是喜新厭舊呢,要不然也不會結婚前覺得適合自己的女人很少,結婚後覺得適合自己的女人很多.
所以本節我們就用大把的筆墨來講述老百姓自己的故事.就講這一行,935行.INIT_DELAYED_WORK()是一個巨集,我們給它傳遞了兩個引數.&hub->leds和led_work.對裝置驅動熟悉的人不會覺得INIT_DELAYED_WORK()很陌生,其實鴉片戰爭那會兒就有這個巨集了,只不過從2.6.20的核心開始這個巨集做了改變,原來這個巨集是三個引數,後來改成了兩個引數,所以經常在網上看見一些同志抱怨說最近某個模組編譯失敗了,說什麼make的時候遇見這麼一個錯誤:
error: macro "INIT_DELAYED_WORK" passed 3 arguments, but takes just 2
當然更為普遍的看到下面這個錯誤:
error: macro "INIT_WORK" passed 3 arguments, but takes just 2
於是就讓我們來仔細看看INIT_WORK和INIT_DELAYED_WORK.其實前者是後者的一個特例,它們涉及到的就是傳說中的工作佇列.這兩個巨集都定義於include/linux/workqueue.h中:
79 #define INIT_WORK(_work, _func) /
80 do { /
81 (_work)->data = (atomic_long_t) WORK_DATA_INIT(); /
82 INIT_LIST_HEAD(&(_work)->entry); /
83 PREPARE_WORK((_work), (_func)); /
84 } while (0)
85
86 #define INIT_DELAYED_WORK(_work, _func) /
87 do { /
88 INIT_WORK(&(_work)->work, (_func)); /
89 init_timer(&(_work)->timer); /
90 } while (0)
有時候特懷念譚浩強那本書裡的那些例子程式,因為那些程式都特簡單,不像現在看到的這些,動不動就是些複雜的函式複雜的資料結構複雜的巨集,嚴重挫傷了我這樣的有志青年的自信心.就比如眼下這幾個巨集吧,巨集裡邊還是巨集,一個套一個,不是說看不懂,因為要看懂也不難,一層一層展開,只不過確實沒必要非得都看懂,現在這樣一種朦朧美也許更美,有那功夫把這些都展開我還不如去認認真真學習三個代表呢.總之,關於工作佇列,就這麼說吧,Linux核心實現了一個核心執行緒,直觀一點,ps命令看一下您的程序,
localhost:/usr/src/linux-2.6.22.1/drivers/usb/core # ps -el
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 1 0 0 76 0 - 195 - ? 00:00:02 init
1 S 0 2 1 0 -40 - - 0 migrat ? 00:00:00 migration/0
1 S 0 3 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/0
1 S 0 4 1 0 -40 - - 0 migrat ? 00:00:00 migration/1
1 S 0 5 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/1
1 S 0 6 1 0 -40 - - 0 migrat ? 00:00:00 migration/2
1 S 0 7 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/2
1 S 0 8 1 0 -40 - - 0 migrat ? 00:00:00 migration/3
1 S 0 9 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/3
1 S 0 10 1 0 -40 - - 0 migrat ? 00:00:00 migration/4
1 S 0 11 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/4
1 S 0 12 1 0 -40 - - 0 migrat ? 00:00:00 migration/5
1 S 0 13 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/5
1 S 0 14 1 0 -40 - - 0 migrat ? 00:00:00 migration/6
1 S 0 15 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/6
1 S 0 16 1 0 -40 - - 0 migrat ? 00:00:00 migration/7
1 S 0 17 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/7
5 S 0 18 1 0 70 -5 - 0 worker ? 00:00:00 events/0
1 S 0 19 1 0 70 -5 - 0 worker ? 00:00:00 events/1
5 S 0 20 1 0 70 -5 - 0 worker ? 00:00:00 events/2
5 S 0 21 1 0 70 -5 - 0 worker ? 00:00:00 events/3
5 S 0 22 1 0 70 -5 - 0 worker ? 00:00:00 events/4
1 S 0 23 1 0 70 -5 - 0 worker ? 00:00:00 events/5
5 S 0 24 1 0 70 -5 - 0 worker ? 00:00:00 events/6
5 S 0 25 1 0 70 -5 - 0 worker ? 00:00:00 events/7
瞅見最後這幾行了嗎,events/0到events/7,0啊7啊這些都是處理器的編號,每個處理器對應其中的一個執行緒.要是您的計算機只有一個處理器,那麼您只能看到一個這樣的執行緒,events/0,您要是雙處理器那您就會看到多出一個events/1的執行緒.哥們兒這裡Dell PowerEdge 2950的機器,8個處理器,所以就是events/0到events/7了.
那麼究竟這些events代表什麼意思呢?或者說它們具體幹嘛用的?這些events被叫做工作者執行緒,或者說worker threads,更確切的說,這些應該是預設的工作者執行緒.而與工作者執行緒相關的一個概念就是工作佇列,或者叫work queue.工作佇列的作用就是把工作推後,交由一個核心執行緒去執行,更直接的說就是如果您寫了一個函式,而您現在不想馬上執行它,您想在將來某個時刻去執行它,那您用工作佇列準沒錯.您大概會想到中斷也是這樣,提供一箇中斷服務函式,在發生中斷的時候去執行,沒錯,和中斷相比,工作佇列最大的好處就是可以排程可以睡眠,靈活性更好.
就比如這裡,如果我們將來某個時刻希望能夠呼叫led_work()這麼一個我們自己寫的函式,那麼我們所要做的就是利用工作佇列.如何利用呢?第一步就是使用INIT_WORK()或者INIT_DELAYED_WORK()來初始化這麼一個工作,或者叫任務,初始化了之後,將來如果咱們希望呼叫這個led_work()函式,那麼咱們只要用一句schedule_work()或者schedule_delayed_work()就可以了,特別的,咱們這裡使用的是INIT_DELAYED_WORK(),那麼之後我們就會呼叫schedule_delayed_work(),這倆是一對.它表示,您希望經過一段延時然後再執行某個函式,所以,咱們今後會見到schedule_delayed_work()這個函式的,而它所需要的引數,一個就是咱們這裡的&hub->leds,另一個就是具體自己需要的延時.&hub->leds是什麼呢?struct usb_hub中的成員,struct delayed_work leds,專門用於延時工作的,再看struct delayed_work,這個結構體定義於include/linux/workqueue.h:
35 struct delayed_work {
36 struct work_struct work;
37 struct timer_list timer;
38 };
其實就是一個struct work_struct和一個timer_list,前者是為了往工作佇列里加入自己的工作,後者是為了能夠實現延時執行,咱們把話說得更明白一點,您看那些events執行緒,它們對應一個結構體,struct workqueue_struct,也就是說它們維護著一個佇列,完了您要是想利用工作佇列這麼一個機制呢,您可以自己建立一個佇列,也可以直接使用events對應的這個佇列,對於大多數情況來說,都是選擇了events對應的這個佇列,也就是說大家都共用這麼一個佇列,怎麼用呢?先初始化,比如呼叫INIT_DELAYED_WORK(),這麼一初始化吧,實際上就是為一個struct work_struct結構體繫結一個函式,就比如咱們這裡的兩個引數,&hub->leds和led_work()的關係,就最終讓hub_leds這個struct work_struct結構體和函式led_work()相綁定了起來,您問怎麼繫結的?您瞧,struct work_struct也是定義於include/linux/workqueue.h:
24 struct work_struct {
25 atomic_long_t data;
26 #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
27 #define WORK_STRUCT_FLAG_MASK (3UL)
28 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
29 struct list_head entry;
30 work_func_t func;
31 };
瞅見最後這個成員func了嗎,初始化的目的就是讓func指向led_work(),這就是繫結,所以以後咱們呼叫schedule_delayed_work()的時候,咱們只要傳遞struct work_struct的結構體引數即可,不用再每次都把led_work()這個函式名也給傳遞一次,一旦繫結,人家就知道了,對於led_work(),那她就嫁雞隨雞,嫁狗隨狗,嫁混蛋隨混蛋了.您大概還有一個疑問,為什麼只要這裡初始化好了,到時候呼叫schedule_delayed_work()就可以了呢?事實上,events這麼一個執行緒吧,它其實和hub的核心執行緒一樣,有事情就處理,沒事情就睡眠,也是一個死迴圈,而schedule_delayed_work()的作用就是喚醒這個執行緒,確切的說,是先把自己的這個struct work_struct插入workqueue_struct這個佇列裡,然後喚醒昏睡中的events.然後events就會去處理,您要是有延時,那麼它就給您安排延時以後執行,您要是沒有延時,或者您設了延時為0,那好,那就趕緊給您執行.咱這裡不是講了兩個巨集嗎,一個INIT_WORK(),一個INIT_DELAYED_WORK(),後者就是專門用於可以有延時的,而前者就是沒有延時的,這裡咱們呼叫的是INIT_DELAYED_WORK(),不過您別美,過一會您會看見INIT_WORK()也被使用了,因為咱們hub驅動中還有另一個地方也想利用工作佇列這麼一個機制,而它不需要延時,所以就使用INIT_WORK()進行初始化,然後在需要呼叫相關函式的時候呼叫schedule_work()即可.此乃後話,暫且不表.
基本上這一節咱們就是介紹了Linux核心中工作佇列機制提供的介面,兩對函式INIT_DELAYED_WORK()對schedule_delayed_work(),INIT_WORK()對schedule_work().
關於工作佇列機制,咱們還會用到另外兩個函式,它們是cancel_delayed_work(struct delayed_work *work)和flush_scheduled_work().其中cancel_delayed_work()的意思不言自明,對一個延遲執行的工作來說,這個函式的作用是在這個工作還未執行的時候就把它給取消掉.而flush_scheduled_work()的作用,是為了防止有競爭條件的出現,雖說哥們兒也不是很清楚如何防止競爭,可是好歹大二那年學過一門專業課,數位電子線路,儘管沒學到什麼有用的東西,怎麼說也還是記住了兩個專業名詞,競爭與冒險.您要是對競爭條件不是很明白,那也不要緊,反正基本上每次cancel_delayed_work之後您都得呼叫flush_scheduled_work()這個函式,特別是對於核心模組,如果一個模組使用了工作佇列機制,並且利用了events這個預設佇列,那麼在解除安裝這個模組之前,您必須得呼叫這個函式,這叫做重新整理一個工作佇列,也就是說,函式會一直等待,直到佇列中所有物件都被執行以後才返回.當然,在等待的過程中,這個函式可以進入睡眠.反正重新整理完了之後,這個函式會被喚醒,然後它就返回了.關於這裡這個競爭,可以這樣理解,events對應的這個佇列,人家本來是按部就班的執行,一個一個來,您要是突然把您的模組給解除安裝了,或者說你把你的那個工作從工作佇列裡取出來了,那events作為佇列管理者,它可能根本就不知道,比如說它先想好了,下午3點執行佇列裡的第N個成員,可是您突然把第N-1個成員給取走了,那您說這是不是得出錯?所以,為了防止您這種唯恐天下不亂的人做出冒天下之大不韙的事情來,提供了一個函式,flush_scheduled_work(),給您呼叫,以消除所謂的競爭條件,其實說競爭太專業了點,說白了就是防止混亂吧.
Ok,關於這些介面就講到這裡,日後咱們自然會在hub驅動裡見到這些介面函式是如何被使用的.到那時候再來看.這就是蝴蝶效應.當我們看到INIT_WORK/INIT_DELAYED_WORK()的時候,我們是沒法預測未來會發生什麼的.所以我們只能拭目以待.又想起了那句老話,大學生活就像被強姦,如果不能反抗,那就只能靜靜的去享受它.