轉自: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()的時候,我們是沒法預測未來會發生什麼的.所以我們只能拭目以待.又想起了那句老話,大學生活就像被強姦,如果不能反抗,那就只能靜靜的去享受它.