《Linux核心設計與實現》讀書筆記(6)— 下半部和推後執行的工作(2)

來源:互聯網
上載者:User

下半部和推後執行的工作

4.tasklet

    tasklet是通過非強制中斷實現的,它由兩類非強制中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。兩者唯一區別在於前者優先於後者執行。

    tasklet由tasklet_struct結構體表示,每個結構體代表一個tasklet,在<linux/interrupt.h>中定義:

struct tasklet_struct {    struct tasklet_struct *next;    /* 鏈表中的下一個tasklet */        unsigned long state;              /* tasklet的狀態 */        atomic_t count;                      /* 引用計數器 */        void (*func)(unsigned long);  /* tasklet處理函數 */        unsigned long data;               /* 給tasklet處理函數的參數 */}

    結構體中的func成員是tasklet的處理常式(像非強制中斷中的action一樣),data是它唯一的參數。state成員只能在0、TASKLET_STATE_SCHED和TASKLET_STATE_RUN之間取值。TASKLET_STATE_SCHED表明tasklet已被調度,正準備投入運行。TASKLET_STATE_RUN表明該tasklet正在運行。count成員是tasklet的引用計數器,如果它不為0,則tasklet被禁止,不允許執行;只有當它為0時,tasklet才被啟用,並且在被設定為掛起狀態時,該tasklet才能夠執行。

 

    已調度的tasklet存放在兩個單一處理器資料結構:tasklet_vec(普通tasklet)和tasklet_hi_vec(高優先順序的tasklet)中。分別由tasklet_schedule()和tasklet_hi_schedule()進行調度。tasklet_schedule()的細節:

    1)檢查tasklet的狀態是否為TASKLET_STATE_SCHED。如果是,說明tasklet已經被調度過了(有可能是一個tasklet已經被調度過但還沒有來得及執行,而該tasklet又被喚起了一次)函數立即返回。

    2)儲存中斷狀態,然後禁止本地中斷。在我們執行tasklet代碼時,這麼做能夠保證當tasklet_schedule()處理這些tasklet時,處理器上的資料不會弄亂。

    3)把需要調度的tasklet加到每個處理器一個的tasklet_vec或tasklet_hi_vec鏈表的表頭上去。

    4)喚起TASKLET_SOFTIRQ或HI_SOFTIRQ非強制中斷,這樣在下一次調用do_softirq()時就會執行該tasklet。

    5)恢複中斷到原狀態並返回。

 

    tasklet_action()和tasklet_hi_action()是tasklet處理的核心。它們的細節:

    1)禁止中斷。(沒有必要首先儲存其狀態,因為這裡的代碼總是作為非強制中斷被調用,而且中斷總是被啟用的)並為當前處理器檢索tasklet_vec或tasklet_hi_vec鏈表。

    2)將當前處理器上的該鏈表設定為NULL,達到清空的效果。

    3)允許響應中斷。沒有必要再恢複它們回原狀態,因為這段程式本身就是作為非強制中斷處理常式被調用的,所以中斷是應該被允許的。

    4)迴圈遍曆獲得鏈表上的每一個待處理的tasklet。

    5)如果是多處理器系統,通過檢查TASKLET_STATE_RUN狀態標誌來判斷這個tasklet是否正在其他處理器上運行,如果它正在運行,那麼現在就不要執行,跳到下一個待處理的tasklet去(同一時間裡,相同類型的tasklet只能有一個執行)。

    6)如果當前這個tasklet沒有執行,將其狀態標誌設定為TASKLET_STATE_RUN,這樣別的處理器就不會再去執行它了。

    7)檢查count值是否為0,確保tasklet沒有被禁止。如果tasklet被禁止了,則跳到下一個掛起的tasklet去。

    8)這個tasklet沒有在其他地方執行,並且被設定成執行狀態,這樣它在其他部分就不會被執行,並且引用計數為0,現在可以執行tasklet的處理常式了。

    9)tasklet運行完畢,清除tasklet的state域的TASKLET_STATE_RUN狀態標誌。

    10)重複執行下一個tasklet,直至沒有剩餘的等待處理的tasklet。

 

5.使用tasklet

    可以靜態或動態地建立tasklet。如果你準備靜態地建立一個tasklet,使用下面<linux/interrupt.h>中定義的兩個宏中的一個。

    DECLARE_TASKLET(name, func, data);

    DECLARE_TASKLET_DISABLED(name, func, data);

    這兩個宏都能根據給定的名稱靜態地建立一個tasklet_struct結構。當該tasklet被調度以後,給定的函數func會被執行,它的參數由data給出。這兩個宏之間的區別在於引用計數器的初始值設定不同。前面一個宏把建立tasklet的引用計數器設定為0,該tasklet處理啟用狀態。另一個把引用計數器設定為1,所以該tasklet處于禁止狀態。

    動態建立則調用tasklet_init函數:

    tasklet_init(t, tasklet_handler, dev);

 

    tasklet處理常式必須符合規定的函數類型

    void tasklet_handler(unsigned long data)

    因為是靠非強制中斷實現,所以tasklet不能睡眠。tasklet運行時允許響應中斷,但兩個相同的tasklet決不會同時執行。

 

    通過調用tasklet_schedule()函數並傳遞給它相應的tasklet_struct指標,該tasklet就會被調度以便執行。

    tasklet_schedule(&my_tasklet);

    在tasklet被調度以後,只要有機會它就會儘可能早地運行。如果有一個相同的tasklet又被調度了,那麼它仍然只會運行一次。而如果這時它已經開始運行了,比如說在另外一個處理器上,那麼這個新的tasklet會被重新調度並再次運行。作為一種最佳化措施,一個tasklet總在調度它的處理器上執行——這是希望能更好地利用處理器的快取。

    你可以調用tasklet_disable()函數禁止某個指定的tasklet。如果該tasklet當前正在執行,這個函數會等到它執行完畢再返回。你也可以調用tasklet_disable_nosync()函數,它也可用來禁止指定的tasklet,不過它無須在返回前等待tasklet執行完畢。這往往不太安全。調用tasklet_enable()函數可以啟用一個tasklet。調用tasklet_kill()函數從掛起的隊列中去掉一個tasklet。

 

6.ksoftirqd

    每個處理器都有一組輔助處理非強制中斷(和tasklet)的核心線程,當核心中出現大量非強制中斷的時候,這些核心線程就會輔助它們。這些線程在最低的優先順序上運行(nice值是19),避免和其他重複的任務搶奪資源。所有線程的名字都叫做ksoftirad/n,區別在於n,它對應的是處理器的編號。

 

7.工作隊列

    工作隊列(work queue)是另一種將工作推後執行的形式。它由一個核心線程去執行——這個下半部分總是會在進程上下文執行。最重要的就是工作隊列允許重新調度甚至是睡眠。

    如果推後執行的任務需要睡眠,那麼就選擇工作隊列,反之,選擇非強制中斷或tasklet。實際上,工作隊列通常可以用核心線程替換,但是由於核心開發人員們非常反對建立新的核心線程,所以推薦使用工作隊列。

    工作隊列子系統是一個用於建立核心線程的介面,通過它建立的進程負責執行由核心其他部分排到隊列裡的任務。它建立的這些核心線程被稱作工作者線程(worker thread)。工作隊列可以讓你的驅動程式建立一個專門的工作者線程來處理需要推後的工作。不過,工作隊列子系統提供了一個預設的工作者線程來處理這些工作。

    預設的工作者線程叫做events/n,這裡n是處理器的編號;每個處理器對應一個線程。

    要實際建立一些需要推後完成的工作,可能通過 DECLARE_WORK 在編譯時間靜態地建立:DECLARE_WORK(name, void(*func)(void *), void *data),這樣就會靜態地建立一個名為 name,處理函數為 func,參數為 data 的 workstruct 結構體。同時,也可以在運行時通過指標建立一個工作:INIT_WORK(struct work_struct *work, void(*func)(void *), void *data);

    工作隊列處理函數的原型是:void work_handler(void *data),這個函數會由一個工作者線程執行,因此,函數會運行在進程上下文中。預設情況下,允許響應中斷並且不持有任何鎖。如果需要,函數可以睡眠。需要注意的是,儘管操作處理函數運行在進程上下文中,但它不能訪問使用者空間,因為核心線程在使用者空間沒有相關的記憶體映射。通常在系統調用發生時,核心會代表使用者空間的進程運行,此時它才能訪問使用者空間,也只有此時它才會映射使用者空間的記憶體。

    使用 schedule_work(&work) 和 schedule_delayed_work(&work, delay) 調度工作,flush_scheduled_work(void) 保證操作已經執行完畢,int cancel_delayed_work(struct work_struct *work) 取消順延強制的工作。struct workqueue_struct *create_workqueue(const char *name) 在每個處理器上建立一個工作者線程,調用 int queue_work(struct workqueue_struct *wq, struct work_struct *work); 和 int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay); 進行調度,重新整理指定的工作隊列則調用 flush_workqueue(struct workqueue_struct *wq);

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.