linux 的內核任務隊列

admin @ 2014-03-25 , reply:0

        許多驅動程序需要將任務延遲到以後處理,但又不想藉助中斷。Linux 為此提供了三種方法:任務隊列、tasklet(從內核 2.3.43 開始)和內核定時器。任務隊列和 tasklet 的使用很靈活,可以或長或短地延遲任務到以後處理,在編寫中斷處理程序時非常有用,我們還將在第9章“Tasklet和底半部處理”一節中繼續討論。內核定時器則用來調度任務在未來某個指定時間執行,將在本章的“內核定時器”一節中討論。

       使用任務隊列或tasklet的一個典型情形是,硬體不產生中斷,但仍希望提供阻塞型的讀取。此時需要對設備進行輪詢,同時要小心地不使 CPU 負擔過多無謂的操作。將讀進程以固定的時間間隔喚醒(例如,使用 current->timeout 變數)並不是個很好的方法,因為每次輪詢需要兩次上下文切換(一次是切換到讀進程中運行輪詢代碼,另一次是返回執行實際工作的某個進程),而且通常來講,恰當的輪詢機制應該在進程上下文之外實現。

     類似的情形還有象不時地給簡單的硬體設備提供輸入。例如,有一個直接連接到並口的步進馬達,要求該馬達能一步步地移動,但馬達每次只能移動一步。在這種情況下,由控制進程通知設備驅動程序進行移動,但實際上,移動是在 write 返回后,才在周期性的時間間隔內一步一步進行的。

     快速完成這類不定操作的恰當方法是註冊任務在未來執行。內核提供了對“任務隊列”的支持,任務可以累積,而在運行隊列時被“消耗”。我們可以聲明自己的任務隊列,並且在任意時刻觸發它,或者也可以將任務註冊到預定義的任務隊列中去,由內核來運行(觸發)它。

任務隊列的本質
任務隊列其實是一個任務鏈表,每個任務用一個函數指針和一個參數表示。任務運行時,它接受一個void * 類型的參數,返回值類型為 void,而指針參數可用來將一個數據結構傳入函數,或者可以被忽略。隊列本身是一個結構(即任務)鏈表,並由聲明和操縱它們的內核模塊所擁有。模塊要全權負責這些數據結構的分配和釋放,為此一般使用靜態的數據結構。
隊列元素由下面這個結構來描述,這段代碼是直接從頭文件 <linux/tqueue.h> 拷貝下來的:

struct tq_struct {
struct tq_struct *next; /* linked list of active bh's */
int sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};

第一個註釋中的 bh 指的是底半部(bottom-half)。底半部是“中斷處理程序的一半部”,我們將在第9章的“tasklet和底半部”一節中介紹中斷時詳細討論。現在,我們只要知道底半部是驅動程序實現的一種機制就可以了,它用於處理非同步任務,這些任務通常比較大,不適於在處理硬體中斷時完成。本章並不要求你理解底半部處理,但必要時也會偶爾提及。

譯註:在2.4版本的內核中,tq_struct的第一個成員變數已經有所不同,改為
struct list_head list; /* linked list of active bh's */
這是因為通用的雙向鏈表 list_head在內核中大量採用,在很多情況下替代了數據結構中自行維護的鏈表。相應的task_queue的定義也改為
typedef struct list_head task_queue;

       上面的數據結構中最重要的成員是routine和data。為了將隨後執行的任務排隊,必須先設置好結構的這些成員,並把 next 和 sync 兩個欄位清零。結構中的 sync 標誌位由內核使用,以避免同一任務被插入多次,因為這會破壞 next 指針。一旦任務被排隊,該數據結構就被認為由內核“擁有”了,不能再被修改,直到任務開始運行。

       與任務隊列有關的其他數據結構還有 task_queue,目前它實現為指向 tq_struct 結構的指針,之所以將這個指針(struct tq_struct* )定義成另一個數據結構(struct task_queue)是為了擴展的需要, 在需要的時候,task_queue結構中可以增加別的內容。
在使用之前,必須將 task_queue 指針初始化為 NULL。
下面匯總了所有可以在任務隊列和 tq_struct 結構上執行的操作。
DECLARE_TASK_QUEUE(name);
這個宏用給定的名稱 name 聲明了一個任務隊列,並把它初始化為空。
int queue_task(struct tq_struct *task, task_queue *list);
正如該函數的名字,它用於將任務排進隊列中。如果隊列中已有該任務,返回 0,否則返回非 0。
void run_task_queue(task_queue *list);
run_task_queue函數用於運行累積在隊列上的任務。除非你要聲明和維護自己的任務隊列,否則不必調用本函數。

        如前所述,一個任務隊列,實際上是一個函數鏈表。當調用 run_task_queue 運行某個隊列時,列表中的每一項都會被執行。在編寫和任務隊列有關的函數時,必須牢記內核是在什麼時候調用run_task_queue的,而且當內核調用 run_task_queue 時,實際的上下文將限制能夠進行的操作。也不應對隊列中任務的運行順序做任何假定,它們每個都是獨立完成自己的任務的。
那麼任務隊列在什麼時候運行呢?如果使用的是下面一節介紹的預定義的任務隊列,則答案是“在內核輪到它那裡時”。不同的隊列在不同的時間運行,只要內核沒有其他更緊要的任務,它們總是會運行的。
更重要的是,當對任務進行排隊的進程運行時,任務隊列幾乎肯定是不會運行的,相反,它們是非同步執行的。到現在為止,示例驅動常式中所有的事情都是在這個執行系統調用的進程上下文中完成的。但當任務隊列運行時,這個進程可能正在睡眠,或正在另一個處理器上運行,甚至可能已經完全退出了。
 
       這種非同步執行類似於硬體中斷髮生時的情景(我們會在第9章詳細討論)。實際上,任務隊列常常是作為“軟體中斷”的結果而運行的。在中斷模式(或中斷期間)下,代碼的運行會受到許多限制。我們現在介紹這些限制,這些限制還會在本書後面多次出現。我們也會多次重複,中斷模式下的這些規則必須遵守,否則系統會有大麻煩。
 
       許多動作需要在進程上下文中才能執行。如果處於進程上下文之外(比如在中斷模式下),則必須遵守如下規則:

  • 不允許訪問用戶空間。因為沒有進程上下文,也就沒有辦法訪問與任何一個特定進程相關聯的用戶空間。
  • current指針在中斷模式下是無效的,不能使用。
  • 不能執行睡眠或調度。中斷模式代碼不可以調用schedule或者sleep_on;也不能調用任何可能引起睡眠的函數。例如,調用kmalloc(...,GFP_KERNEL)就不符合本規則。信號量也不能用,因為可能引起睡眠。

       內核代碼可以通過調用函數in_interrupt( ) 來判斷自己是否正運行於中斷模式,該函數無需參數,如果處理器在中斷期間運行就返回非0值。

       當前的任務隊列實現還有一個特性,隊列中的一個任務可以將自己重新插回到它原先所在的隊列。舉個例子,定時器隊列中的任務可以在運行時將自己插回到定時器隊列中去,從而在下一個定時器滴答又再次被運行。這是通過調用 queue_task 把自己放回隊列來實現的。由於在處理任務隊列之前,是先用NULL指針替換隊列的頭指針,也就是將任務隊列初始化了,另外,在執行隊列中的任務之前,首先將任務從隊列中移出來,這樣在任務將本身插入任務隊列的時候,它其實是將指針指向新的任務隊列。結果就是,隨著舊隊列的執行,新的隊列逐漸生成。

        儘管一遍遍地重新調度同一個任務看起來似乎沒什麼意義,但有時這也有些用處。例如,步進馬達每次移動一步直到目的地,它的驅動程序就可以通過讓任務在定時器隊列上不斷地重新調度自己來實現。其他的例子還有 jiq 模塊,該模塊中的列印函數通過重新調度自己來產生輸出??結果是利用定時器隊列產生多次迭代。 

預定義的任務隊列
        延遲任務執行的最簡單方法是使用由內核維護的任務隊列。這種隊列有好幾種,但驅動程序只能使用下面列出的其中三種。任務隊列的定義在頭文件 <linux/queue.h> 中,驅動程序代碼需要包含該頭文件。

調度器隊列
調度器隊列在預定義任務隊列中比較獨特,它運行在進程上下文中,這意味著該隊列中的任務可以更多的事情。在Linux 2.4,該隊列由一個專門的內核線程 keventd 管理,通過函數 schedule_task 訪問。在較老的內核版本,沒有用keventd,所以該隊列(tq_scheduler)是直接操作的。
tq_timer
該隊列由定時器處理程序(定時器嘀噠)運行。因為該處理程序(見函數do_timer)是在中斷期間運行的,因此該隊列中的所有任務也是在中斷期間運行的。
tq_immediate
立即隊列是在系統調用返回時或調度器運行時得到處理,以便儘可能快地運行該隊列。該隊列在中斷期間得到處理。
還有其它的預定義隊列,但驅動程序開發中通常不會涉及到它們。



[admin via 研發互助社區 ] linux 的內核任務隊列已經有864次圍觀

http://cocdig.com/docs/show-post-42589.html