Linux那些事兒之我是Hub(7)蝴蝶效應--INIT_DELAYED_WORK詳解

   朋友,你相信,一隻蝴蝶在北京拍拍翅膀,將使得紐約幾個月後出現比狂風還厲害的龍捲風嗎?看過那部經典的影片蝴蝶效應的朋友們一定會說,這不就是蝴蝶效應嗎.沒錯.蝴蝶效應其實是混沌學理論中的一個概念.它是指對初始條件敏感性的一種依賴現象.蝴蝶效應的原因在於蝴蝶翅膀的運動,導致其身邊的空氣系統發生變化,並引起微弱氣流的產生,而微弱氣流的產生又會引起它四周空氣或其它系統產生相應的變化,由此引起連鎖反應,最終導致其它系統的極大變化.

   自從197912月麻省理工的洛侖茲大俠在美國科學促進會上作了關於蝴蝶效應的報告之後,從此蝴蝶效應很快風靡全球,其迷人的美學色彩和深刻的科學內涵令許多人着迷,激動,同時發人深省.蝴蝶效應被引入了各個領域,比如軍事,比如政治,比如經濟,再後來也被引入到了企業管理,甚至我們的人生歷程裏也存在.當然Linux中也不會放過如此有哲學魅力的理論.從本質上來說,蝴蝶效應給人一種對未來行爲不可預測的危機感.Linux內核代碼中這種感覺更是強烈,幾乎到了無處不在的程度.很多函數,特別是那種做初始化的函數,你根本就不知道它在幹什麼,只有當你在未來某個時刻,看到了另一個函數,你纔會回過頭來看,原來當初是這個函數設置了初始條件.假如你改變了初始條件,那麼後來你某個地方的某個函數的某個行爲就會發生改變.但問題是,你並不知道這個行爲將在午夜12點發生還是在下午3點半發生.

   是不是覺得很玄?像思念一樣玄?那好,我們來看點具體的,比如935,INIT_DELAYED_WORK().這是一張新面孔.同志們大概注意到了,hub這個故事裏,我們的講解風格略有變化,對於那些舊的東西,對於那些在usb-storage裏面講過很多次的東西,我們不會再多提,但是對於新鮮的東西,我們會花大把的筆墨去描摹.這樣做的原因很簡單,男人嘛,有幾個不是喜新厭舊呢,要不然也不會結婚前覺得適合自己的女人很少,結婚後覺得適合自己的女人很多.

   所以本節我們就用大把的筆墨來講述老百姓自己的故事.就講這一行,935.INIT_DELAYED_WORK()是一個宏,我們給它傳遞了兩個參數.&hub->ledsled_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_WORKINIT_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/0events/7,07啊這些都是處理器的編號,每個處理器對應其中的一個線程.要是您的計算機只有一個處理器,那麼您只能看到一個這樣的線程,events/0,您要是雙處理器那您就會看到多出一個events/1的線程.哥們兒這裏Dell PowerEdge 2950的機器,8個處理器,所以就是events/0events/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->ledsled_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()的時候,我們是沒法預測未來會發生什麼的.所以我們只能拭目以待.又想起了那句老話,大學生活就像被強姦,如果不能反抗,那就只能靜靜的去享受它.

原文:http://blog.csdn.net/fudan_abc/article/details/1751565,博主相當強大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章