天天看點

下半部和推後執行的工作--tasklet

版權聲明:您好,轉載請留下本人部落格的位址,謝謝 https://blog.csdn.net/hongbochen1223/article/details/48175097

(一):tasklet

tasklet是利用軟中斷實作的一種下半部機制,他和程序沒有任何關系,他在本質上和軟中斷是相似的,行為表現也很相近.但是他的接口很簡單,鎖保護也要求較低.

1:tasklet的實作

tasklet有兩類軟中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ.這兩者之間唯一的實際差別在于,HI_SOFTIRQ類型的軟中斷先于TASKLET_SOFTIRQ類型的軟中斷執行.

1):tasklet結構體

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

/* Tasklets --- multithreaded analogue of BHs.
   BHS的多線程模拟
   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.
   他們和軟中斷不同的主要特征為:tasklet隻能同時運作在一個cpu上.
   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.
   他們和BHS不同的主要特征為:不同的tasklet可能同時運作在不同的CPU上
   Properties:
   特性:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
     如果tasklet_schedule()函數被調用,tasklet一定會運作在
     一些CPU上,至少一次
   * If the tasklet is already scheduled, but its excecution is still not
     started, it will be executed only once.
     如果tasklet已經被排程了,但是他的運作依然沒有開始,他将僅僅被執行一次.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
     如果該tasklet已經運作在另外一個CPU上了(或這是對tasklet本身執行排程了),
     他将會在後面被排程.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
     和鎖相關
 */
struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};
           

結構體中func成員是tasklet的處理程式.data是他唯一的參數.state成員隻能在0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之間取值.TASKLET_STATE_SCHED 表明tasklet已被排程,正準備投入運作,TASKLET_STATE_RUN表明該tasklet正在運作.TASKLET_STATE_RUN隻有在多處理器上才會作為一種優化來使用,單處理器系統任何時候都清楚單個tasklet是不是正在運作.

count成員是tasklet的引用計數器.如果他不為0,則tasklet被禁止,不允許執行;隻有當他為0的時候,tasklet才被激活,并且設定為挂起狀态,該tasklet才能夠執行.

2):排程tasklet

已排程的tasklet(等同于被觸發的軟中斷)存放在兩個單處理器資料結構:tasklet_vec(普通tasklet)和task_hi_vec(高優先級的tasklet).這兩個資料結構都是由tasklet_struct構成的連結清單.連結清單中每一個tasklet_struct代表一個不同的tasklet.

tasklet由tasklet_schedule()和tasklet_hi_schedule()函數進行排程.他們接受一個指向tasklet_struct結構的指針作為參數.兩個函數非常相似(差別在于一個使用TASKLET_SOFTIRQ 而另外一個使用HI_SOFTIRQ). 現在我們看一下tasklet_schedule()函數.

static inline void tasklet_schedule(struct tasklet_struct *t)
{
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
        __tasklet_schedule(t);
}           

1:首先檢查tasklet的狀态是否為TASKLET_STATE_SCHED.如果是,說明tasklet已經被排程過了,函數立即傳回

2:調用__tasklet_schedule()

void __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;
    local_irq_save(flags);
    t->next = NULL;
    *__get_cpu_var(tasklet_vec).tail = t;
    __get_cpu_var(tasklet_vec).tail = &(t->next);
    raise_softirq_irqoff(TASKLET_SOFTIRQ);
    local_irq_restore(flags);
}           

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

4:把需要排程的tasklet加到每個處理器的tasklet_vec連結清單或task_hi_vec連結清單的表頭上去.

5:喚起TASKLET_SOFTIRQ或HI_SOFTIRQ軟中斷,這樣下一次調用do_softirq()的時候就會執行該tasklet.

6:恢複中斷到原狀态并傳回

下面我們來看一下相應的軟中斷處理程式,tasklet_action()和tasklet_hi_action().這兩個函數的處理過程基本上是相同的,我們來看其中一個:

static void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;
    local_irq_disable();
    list = __get_cpu_var(tasklet_vec).head;
    __get_cpu_var(tasklet_vec).head = NULL;
    __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
    local_irq_enable();
    while (list) {
        struct tasklet_struct *t = list;
        list = list->next;
        if (tasklet_trylock(t)) {
            if (!atomic_read(&t->count)) {
                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
                    BUG();
                t->func(t->data);
                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }
        local_irq_disable();
        t->next = NULL;
        *__get_cpu_var(tasklet_vec).tail = t;
        __get_cpu_var(tasklet_vec).tail = &(t->next);
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_enable();
    }
}           

1:禁止中斷,并未目前處理器檢索tasklet_vec或者是tasklet_hi_vec.

2:将目前處理器上的該連結清單設定為NULL,達到清空效果.

3:允許響應中斷.沒有必要再恢複他們到原來狀态,因為這段代碼本身就是作為軟中斷處理程式調用的,是以中斷是應該被允許的

4:循環周遊獲得連表上的每一個待處理的tasklet

5:如果是多處理器系統,通過檢查TASKLET_STATE_RUN來判斷這個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.

2:使用tasklet

大多數情況下,為了控制一個尋常的硬體裝置,tasklet機制都是實作自己的下半部的最佳選擇.tasklet可以動态建立,使用友善,執行起來也還算快.

1):聲明自己的tasklet

你既可以靜态的建立tasklet,也可以動态的建立他.選擇哪種方式取決于你到底是有一個對tasklet的直接引用還是間接引用.如果你準備靜态的建立一個tasklet(也就是有一個他的直接引用),使用下面linux/interrupt.h中定義的兩個宏的一個:

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
           

這兩個宏都能根據給定的名稱靜态建立一個tasklet_struct結構.當該tasklet被排程以後,給定的函數fun會被執行,他的參數由data給出.這兩個宏之間的差別在于引用計數器的初始值不同.前面一個宏把建立的tasklet的引用計數器設定為0,該tasklet處于激活狀态.另一個把引用計數器設定為1,是以該tasklet處于禁止狀态,下面是一個例子:

DECLARE_TASKLET(my_tasklet,my_tasklet_hadnler,dev);
//等價于
struct tasklet_struct my_tasklet = { NULL, 0 , ATOMIC_INIT(0), my_tasklet_handler,dev};
           

這樣就建立了一個名為my_tasklet.處理程式為tasklet_handler并且是已被激活的tasklet.當處理程式被調用的時候,dev就被傳遞給他.

還可以通過将一個間接引用(一個指針)賦給一個動态建立的tasklet_struct結構的方式來初始化一個tasklet_init():

tasklet_init(t,tasklet_handler,dev); /* 動态而不是靜态建立 */

2):編寫你自己的tasklet處理程式

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

void tasklet_handler(unsigned long data);           

因為是靠軟中斷實作,是以tasklet不能睡眠.這意味着你不能在tasklet中使用信号量或者其他什麼阻塞式的函數.由于tasklet運作的時候允許響應中斷,是以你必須做好預防工作(如屏蔽中斷然後擷取一個鎖),如果你的tasklet和中斷處理程式之間共享了某些資料的話.兩個相同的tasklet絕不會同時執行,這點和軟中斷不同-盡管兩個不同的tasklet可以在兩個處理器上同時執行.如果你的tasklet和其他的tasklet或者是 軟中斷共享了資料,你必須進行适當的鎖保護.

3):排程你自己的tasklet

通過調用task_schedule()函數并傳遞給他相應的task_struct的指針,該tasklet就會被排程以便執行:

tasklet_schdule(&my_tasklet);  /* 把my_tasklet标記為挂起 */           

在tasklet被排程以後,隻要有機會他就會盡可能早的運作.在他還沒有得到運作機會之前,如果有一個相同的tasklet又被排程了,那麼他仍然隻會運作一次.而如果這時他已經開始運作了,比如說在另外一個處理器上,那麼這個新的tasklet會被重新排程并再次運作.作為一種優化措施,一個tasklet總在排程他的處理器上執行--這是更好的利用處理器的高速緩存.

你可以調用tasklet_disable()函數來禁止某個指定的tasklet.如果該tasklet目前正在執行,這個函數會等到他執行完畢後再傳回.也可以使用tasklet_disable_nosync()函數,他也用來禁止指定的tasklet,不過他無須在傳回前等待tasklet執行完畢.調用tasklet_enable()函數可以激活一個tasklet,如果希望激活DECLARE_TASKLET_DISABLE()建立的tasklet,你也得調用這個函數.如:

tasklet_disable(&my_tasklet);  /* tasklet現在被禁止 */
/* 我們現在毫無疑問的知道tasklet不能運作 */
tasklet_enable(&my_tasklet); /* tasklet現在激活 */
           

你也可以使用tasklet_kill()函數從挂起的隊列中去掉一個tasklet.這個函數的參數是一個指向某個tasklet的tasklet_struct的長指針.在處理一個經常排程他自身的tasklet的時候,從挂起的隊列中移去已排程的tasklet會很有用,這個函數首先等待該tasklet執行完畢,然後再将他移除.當然,沒有什麼可以阻止其他地方的代碼重新排程該tasklet,由于該函數可能會引起休眠,是以禁止在中斷上下文中使用它.

4:ksoftirqd

當有大量軟中斷出現的時候,立即處理軟中斷或者是不立即處理軟中斷都會導緻一些問題,那麼就需要有一種折中的方法,那就是,當大量軟中斷出現的時候,核心會喚醒一組核心線程來處理這些負載,這些線程在最低的優先級上運作,這樣能夠避免他們跟重要的任務搶奪資源.是以這個方案能夠保證在軟中斷負擔很重的時候,使用者程式不會因為得不到處理時間而處于饑餓狀态,相應的,也能夠保證”過量”的軟中斷終究會得到處理.

每個處理器都有一個這樣的線程,所有線程的名字都叫做ksoftirqd/n,差別在于n.他對應的是處理器的編号.在一個雙CPU的機器上就有兩個這樣的線程,分别叫做ksoftirqd/0和ksoftirqd/1;為了保證隻要有空閑的處理器,他們就會軟中斷,所有給每個處理器都配置設定一個這樣的線程.一旦該線程被初始化,他就會執行類似下面的死循環.

for (;;) { 
if (!softirq_pending(cpu)) 
schedule(); 
set_current_state(TASK_RUNNING); 
while (softirq_pending(cpu)) { 
do_softirq(); 
if (need_resched()) 
schedule(); 
} 
set_current_state(TASK_INTERRUPTIBLE); 
}            

繼續閱讀