Linux內核:中斷、軟中斷、tasklet

http://blog.csdn.net/jansonzhe/article/details/48786207


在之前我所寫的Linux驅動程序中,會經常使用到中斷機制,像CC1100高頻驅動、倒車雷達驅動等等。但所用到的中斷機制都基本上是用到中斷的頂半部,即:編寫中斷處理函數,通過request_irq函數申請中斷,這樣當中斷來臨的時候,就會自動執行中斷處理程序裏面的內容。之所以沒有使用到中斷的底半部,是因爲我們這些驅動程序中,中斷處理函數一般都能被很快執行完,同時也不會存在有任何休眠的動作,因此使用中斷的頂半部對於我們這些驅動程序來說,反而相對簡單一些。因此這也就得出,並不是任何中斷程序都一定會使用到中斷的底半部。


中斷頂半部

對於中斷的頂半部,我想大部分的關於Linux驅動的書上都會有詳細的講解,並且這一塊理解和實踐起來都比較容易,但這裏我需要講解的是關於共享中斷的這一部分,因爲這一塊可能對於一些初學者會有一點難度。

共享中斷是指多個設備共享一根中斷線(中斷線在這裏可以理解爲中斷號,也就是說多個設備共享一箇中斷號),爲什麼會有這種情況發生,因爲在Linux內核中,中斷線的數目是有限的,如果每一個設備都使用一根中斷線的話,中斷線肯定是不夠的,所以聰明的Linux內核設計師們就提出了共享中斷這一理念。這裏理念的主要目的就是可以在一根中斷線上搭載多箇中斷設備。那麼好了,現在問題也來了,既然都在同一個中斷線上,如果中斷來了的話,要如何判斷該中斷來自於哪一個設備呢?其實對於Linux內核來說,要判斷其來自哪一個設備,其需要做兩步工作。當一箇中斷來臨時,Linux內核會遍歷該中斷線上所有註冊了的中斷處理程序,在該中斷處理程序中,就會迅速判斷到底是來自於哪一個硬件設備。而在中斷程序中如何來判斷呢?這就需要相應產生中斷的硬件設備來支持了。例如可能中斷處理程序會檢查一下該處理程序對應的硬件設備的某一寄存器的狀態來判斷是否該設備發生了中斷,如果是該設備發出的中斷,就執行接下來的處理函數。如果不是,就立即返回(應該返回IRQ_RETVAL(IRQ_NONE))。

首先我們來看一下在申請共享中斷的過程與一般申請中斷有哪些不同。

我們知道申請註冊中斷的函數是:

  1. request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,  
  2.         const charchar *name, voidvoid *dev)  
  3. {  
  4.     return request_threaded_irq(irq, handler, NULL, flags, name, dev);  
  5. }  
  1. 如果我們要申請共享中斷函數的話,flag標誌位必須還要指定一個IRQF_SHARED(即flag再“ | ”上一個IRQF_SHARED),注意,該中斷線的每一箇中斷設備在申請中斷的時候都必須要加上該標誌位。
  2. 對於每一個註冊的中斷處理程序來說,最後一個參數dev必須是唯一的(這是共享中斷所明確要求的)。爲了確保dev參數值是唯一的。可以將dev參數的值設爲指向申請中斷函數的設備結構體指針即可(我們的CC1101和倒車雷達驅動都是這麼幹的)。而且由於中斷處理函數可能會用到設備結構體的數據,因此這是一個一箭雙鵰的方法。對於共享中斷處理程序,dev的參數值不能爲NULL。

中斷上下文

在這裏順便提一下中斷上下文,當我們執行一箇中斷處理函數時,內核就會處於中斷上下文(Interrupt Context)中。與進程上下文不同,中斷上下文與進程並沒有什麼關係。與current宏也沒有任何關係,儘管此時若使用的current標誌位的話,其任然是指向被中斷的進程。由於中斷上下文不依賴與進程,因此中斷上下文不能休眠,不能在中斷上下文中調用某些可能引起休眠的函數。

由於中斷上下文可以打斷其他正在執行的代碼,因此,中斷上下文在執行時間上由嚴格的時間限制。中斷上下文中的代碼需要儘可能簡潔,儘量不要使用循環或者是耗時比較長的函數來處理中斷任務。這是由於中斷上下文已經打斷了其他正在執行的代碼,甚至可能是其他的中斷處理程序,因此中斷處理程序應該快速地執行完,否則可能會使其他被打斷的程序長時間等待而造成系統性能下降甚至崩潰。當然,在中斷上下文中處理複雜耗時的任務也在所難免,但最好將這部分任務放在中斷的底半部(主要因爲中斷的底半部,可以被其他甚至是同類型的中斷打斷,並且中斷底半部函數是異步執行。)。這樣既可以很快地執行完中斷處理程序(儘快回覆被中斷的代碼),又可以在中斷程序中完成很複雜的任務。後面的軟中斷或者是tasklet都屬於中斷的上下文中。

在Linux2.6內核中,中斷處理程序擁有自己的棧,每一個處理器一個,大小爲一頁(4KB),儘管中斷棧並不算大,但平均可用棧空間要比Linux內核的其他程序大得多。因爲中斷程序把這一頁據爲己有。在我們編寫中斷處理程序時,並不需要關心如何設置中斷棧或內核棧的大小,總之,儘量節約中斷棧的空間就行了。


中斷的底半部

下面我們來開始講解中斷底半部,如果用一個詞來形容底半部的功能,就是“延遲執行”,爲什麼要這樣說呢,後面分析過後就會深刻理解這一點了。在中斷的上半部,即中斷處理程序結束前,當前的中斷線在所有的處理器上都會被屏蔽,如果在申請中斷線時使用了IRQF_DISABLED,那麼情況會更加糟糕,在中斷處理程序執行時會禁止所有的本地中斷。因此儘可能地縮短中斷被屏蔽的時間對系統的響應能力和性能都至關重要。因此,要將耗時較長的任務放到底半部延遲執行。因爲底半部並不禁止其他中斷上半部的執行(哪怕是自己的中斷處理函數)。對於中斷底半部的實現方式一共有三種;

  1. 採用軟中斷的方式
  2. 採用tasklet微線程
  3. 採用工作隊列
下面我們分別介紹這三種實現中斷底半部的方式


軟中斷
軟中斷使內核可以執行延遲任務,由於其運作方式與之前的中斷類似,並且完全由軟件實現,因此稱之爲軟中斷。在講解軟中斷之前,我們先來看一下軟中斷的一個模型圖:


我們可以看到,所有的中斷線上的中斷都需要經過do_IRQ函數,執行該函數,需要一個irq的參數,也就是中斷號。然後do_IRQ函數便會輪詢得執行該中斷線上的所以中斷處理程序,該實現方式主要是在do_IRQ函數中調用handle_IRQ_event函數實現。handle_IRQ_event會執行irq所在的中斷線上的所有的中斷處理函數。好了,現在如果我們執行完了handle_IRQ_event函數後,我們會在do_IRQ中調用irq_exit函數,該函數纔是負責調用和處理待決的軟中斷。首先我們來看一下這個函數的源代碼:
  1. void irq_exit(void)  
  2. {  
  3.     account_system_vtime(current);  
  4.     trace_hardirq_exit();  
  5.     sub_preempt_count(IRQ_EXIT_OFFSET);  
  6.     if (!in_interrupt() && local_softirq_pending())  
  7.  //判斷是否有軟中斷被請求,主要是看是否有執行raise_softirq函數,  
  8.         invoke_softirq(); //用於喚醒軟中斷,即會激活do_softirq函數  
  9.   
  10.     rcu_irq_exit();  
  11. #ifdef CONFIG_NO_HZ  
  12.     /* Make sure that timer wheel updates are propagated */  
  13.     if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())  
  14.         tick_nohz_stop_sched_tick(0);  
  15. #endif  
  16.     preempt_enable_no_resched();  
  17. }  
一旦我們激活軟中斷,調用do_softirq函數,就說明程序已經進入軟中斷環境了。與do_IRQ所處的中斷的頂半部不同,處於軟中斷環境中,是可以被其他中斷程序打斷的,甚至是處於同一中斷線的中斷,也因爲此,所以軟中斷可以執行一些稍微時間長一點的任務,也不會遲滯系統對中斷的反應時間。
下面我將詳細介紹有關do_softirq函數的實現原理和過程,因爲只有瞭解該過程,才能充分理解軟中斷的工作原理。
軟中斷由一個softirq_action結構體表示,在該結構體中只定義了一個函數指針
  1. struct softirq_action  
  2. {  
  3.     void    (*action)(struct softirq_action *); //函數指針名爲action,其中參數類型爲一個  
  4. };  
其實這個函數指針就是指向該軟中斷的處理函數。

當我們需要使用軟中斷時,首先要想Linux內核註冊軟中斷,註冊的內容包括軟中斷的類型和軟中斷處理函數指針。所以這也就知道了爲什麼說軟中斷由一個softirq_action結構體表示。因爲軟中斷最爲核心的地方就是軟中斷處理函數。
在驅動程序中,如果我們要想註冊軟中斷,我們需要使用open_softirq函數
  1. void open_softirq(int nr, void (*action)(struct softirq_action *))  
  2. {  
  3.     softirq_vec[nr].action = action; //指定軟中斷處理函數指針。  
  4. }  
我們可以發現,這裏出現了一個softirq_vec數組,該數組類型當然是softirq_action類型的了,數組的下標表示了不同的軟中斷類型。下標越小,軟中斷的優先級越高。下面是不同的下標表示不同的類型的軟中斷,其用一個枚舉來表示。

  1. enum  
  2. {  
  3.     HI_SOFTIRQ=0//優先級最高的軟中斷,用於tasklet  
  4.     TIMER_SOFTIRQ,  
  5.     NET_TX_SOFTIRQ, //發送網絡數據的軟中斷  
  6.     NET_RX_SOFTIRQ,  
  7.     BLOCK_SOFTIRQ,  
  8.     BLOCK_IOPOLL_SOFTIRQ,  
  9.     TASKLET_SOFTIRQ, //tasklet軟中斷  
  10.     SCHED_SOFTIRQ,  
  11.     HRTIMER_SOFTIRQ,  
  12.     RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */  
  13.   
  14.     NR_SOFTIRQS   //該枚舉值就是當前Linux內核允許註冊的最大軟中斷數  
  15. };  
這裏需要注意的是softirq_vec對於整個Linux內核是全局的,所以任何一個軟中斷的處理程序都是全局唯一的,即softirq_vec[nr].action = action中一旦指定了其軟中斷處理程序的方法,則這個類型的軟中斷的處理程序就確定了。
當我們編寫了含有softirq_action參數的軟中斷處理程序,並且通過open_softirq函數註冊完之後(open_softirq函數的功能其實很簡單,就是根據nr指定的軟中斷類型定位當前驅動的softirq_action數組的相應元素,然後將action指定的軟中斷處理函數的指針賦給softirq_vec[nr].action,注意這裏的softirq_vec是全局的。),我們就可以使用軟中斷了,一般怎麼使用軟中斷呢?
前面我們知道,如果在還沒有開啓Ksoftirq線程的情況下,我們會在中斷頂半部的do_IRQ中調用irq_exit函數,而該函數會喚醒軟中斷,即會執行do_softirq函數,所以歸根結底,要想執行軟中斷,必須得要執行do_IRQ函數,但是是不是隻要我們在調用do_IRQ函數之前,註冊了軟中斷函數,我們的軟中斷函數就會被執行了呢?答案當然是否定的。因爲如果這樣的話,那每一次中斷結束時,所有的軟中斷處理程序都得執行了,不管這個軟中斷處理程序是否跟這個中斷線有關。所以呀,要想do_softirq函數只執行我們想要的軟中斷處理程序的話,應該需要用raise_softirq函數將我們想要執行的軟中斷處理程序掛起。
  1. void raise_softirq(unsigned int nr)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     local_irq_save(flags); //保存中斷狀態,禁止中斷  
  6.     raise_softirq_irqoff(nr); //掛起相應的中斷類型  
  7.     local_irq_restore(flags); //恢復中斷。  
  8. }  
所以其實raise_softirq函數真正調用的是raise_softirq_irqoff(nr)函數,同時從這裏我們也知道了,一個軟中斷類型,只能對應一個軟中斷處理程序,而若要使用tasklet類型的軟中斷的話,就必須要執行raise_softirq_irqoff(TASKLET_SOFTIRQ)函數。
相應類型的軟中斷被掛起之後,下面我們就來看看Linux是如何執行它的吧,通過上面我給出的那個軟中斷的模型圖,我們可以知道執行軟中斷的函數是do_softirq函數,這個函數是執行一切軟中斷處理程序的根本,也就是說任何一個軟中斷處理程序都應該經過do_softirq函數調用的,好了我們現在來看看do_softirq的廬山真面目吧
  1. asmlinkage void do_softirq(void)  
  2. {  
  3.     __u32 pending;  
  4.     unsigned long flags;  
  5.   
  6.     if (in_interrupt())  
  7.         return;  
  8.   
  9.     local_irq_save(flags);  
  10.   
  11.     pending = local_softirq_pending(); //再獲取pending標誌位,是否有軟中斷處理程序被raise  
  12.   
  13.     if (pending)  
  14.         __do_softirq(); //如果有,則執行_do_softirq函數  
  15.   
  16.     local_irq_restore(flags);  
  17. }  
所以do_softirq函數真正起作用的是_do_softirq函數。
  1. asmlinkage void __do_softirq(void)  
  2. {  
  3.     struct softirq_action *h;  
  4.     __u32 pending;  
  5.     int max_restart = MAX_SOFTIRQ_RESTART;  
  6.     int cpu;  
  7.   
  8.     pending = local_softirq_pending();  
  9.     account_system_vtime(current);  
  10.   
  11.     __local_bh_disable((unsigned long)__builtin_return_address(0),  
  12.                 SOFTIRQ_OFFSET);  
  13.     lockdep_softirq_enter();  
  14.   
  15.     cpu = smp_processor_id();  
  16. restart:  
  17.     /* Reset the pending bitmask before enabling irqs */  
  18.     set_softirq_pending(0);  
  19.   
  20.     local_irq_enable();  
  21.   
  22.     h = softirq_vec;  
  23.   
  24.     do {  
  25.         if (pending & 1) {    //將pending不同的爲與1相"&",來確定哪種類型的軟中斷被掛起了  
  26.             unsigned int vec_nr = h - softirq_vec; //獲取softirq_vec數組的下標值,該下標值也就確定了軟中斷屬於什麼類型了  
  27.             int prev_count = preempt_count();  
  28.   
  29.             kstat_incr_softirqs_this_cpu(vec_nr);  
  30.   
  31.             trace_softirq_entry(vec_nr);  
  32.             h->action(h);  //在這裏執行軟中斷的處理函數action  
  33.             trace_softirq_exit(vec_nr);  //執行完該軟中斷處理程序之後,就應該將掛起的標誌位重新置0,將相應的_softirq_pending  
  34.             if (unlikely(prev_count != preempt_count())) {  
  35.                 printk(KERN_ERR "huh, entered softirq %u %s %p"  
  36.                        "with preempt_count %08x,"  
  37.                        " exited with %08x?\n", vec_nr,  
  38.                        softirq_to_name[vec_nr], h->action,  
  39.                        prev_count, preempt_count());  
  40.                 preempt_count() = prev_count;  
  41.             }  
  42.   
  43.             rcu_bh_qs(cpu);  
  44.         }  
  45.         h++;  
  46.         pending >>= 1;  
  47.     } while (pending);  
  48.   
  49.     local_irq_disable();  
  50.   
  51.     pending = local_softirq_pending();  
  52.     if (pending && --max_restart)  
  53.         goto restart;  
  54.   
  55.     if (pending)  //重新獲取的pending,如果其不爲0,說明又有新的軟中斷處理程序被掛起,如果待處理的軟中斷程序過多,就應該開啓Ksoftirq線程。從而達到延時目的  
  56.         wakeup_softirqd(); //開啓Ksoftirq線程(軟中斷處理線程),即將Ksoftirq線程加入至可運行隊列  
  57.   
  58.     lockdep_softirq_exit();  
  59.   
  60.     account_system_vtime(current);  
  61.     __local_bh_enable(SOFTIRQ_OFFSET);  
  62. }  
關於Ksoftirq線程後面會詳細講解。到此軟中斷的工作原理就全部講解完了,理解了軟中斷的機制,再去理解tasklet就容易很多了。


tasklet微線程

軟中斷是將操作推遲到將來某一個時刻執行的最有效的方法。由於該延遲機制處理複雜,多個處理器可以同時並且獨立得處理(即do_softirq函數可以被多個CPU同時執行),並且一個軟中斷的處理程序可以在多個CPU上同時執行,因此處理程序必須要被設計爲完全可重入和線程安全的。此外臨界區必須用自旋鎖保護。由於軟中斷因爲這些原因就顯得太過於麻煩,因此引入tasklet機制,就變得很有必要了。tasklet是基於軟中斷實現的,在我們上面講軟中斷的時候知道,tasklet確切的說應該是軟中斷的一個類型,所以根據軟中斷的性質,一個軟中斷類型對應一個軟中斷處理程序action。同理,也可以推出tasklet也會對應於一個唯一的action。可能講到這裏會有讀者覺得,既然一個tasklet類型的軟中斷只對應一個軟中斷處理程序,那麼我可能在一個驅動程序中使用多個tasklet怎麼辦?或者是有多個驅動程序裏面都要使用tasklet又怎麼辦?要回答這個問題,我們就要了解tasklet另一個重要的性質。那就是,每一個CPU都會有自己獨立的tasklet隊列,雖然一個tasklet類型的軟中斷只對應一個action處理程序,但是我們可以在該處理程序中輪詢執行一個tasklet隊列,隊列裏面的每一個tasklet_struct都會對應一個tasklet處理函數,這樣當我們的驅動程序中需要使用到tasklet的時候,只要往這個tasklet隊列加入我們自定義的tasklet_struct對象就可以了。同時,由於每一個CPU都會有一個tasklet隊列,並且每一個CPU只會執行自己tasklet隊列裏面的tasklet_struct對象,因此tasklet並不需要自旋鎖的保護(當然這隻能是對同一個tasklet而言,如果多個不同的tasklet需要使用同一資源的話,仍需要自旋鎖的保護,後面瞭解了tasklet機制之後就會明白這一點),因此這樣就降低了對tasklet處理函數的要求。
對於一個tasklet對象是通過一個tasklet_struct結構體來描述的,該結構體定義在include\linux\interrupt.h文件中
  1. struct tasklet_struct  
  2. {  
  3.     struct tasklet_struct *next; //鏈接下一個tasklet_struct對象,以構成一個tasklet隊列  
  4.     unsigned long state;  //該tasklet的運行狀態標誌位  
  5.     atomic_t count;     //該tasklet被引用的次數標誌位,當count爲0時,表示已激活可用  
  6.     void (*func)(unsigned long); //該tasklet的處理函數指針,也是tasklet的核心所在  
  7.     unsigned long data;   //給上面的處理函數傳的參數。  
  8. };  
下面就給出一個tasklet的隊列示意圖
理解這個tasklet隊列非常有用,這樣我們就可以充分理解tasklet的工作機制了。從上面這個隊列,我們可以看到這個隊列的頭是一個名叫tasklet_vec的tasklet_head結構體,我們來看看tasklet_head結構體體
  1. struct tasklet_head  
  2. {  
  3.     struct tasklet_struct *head;  
  4.     struct tasklet_struct **tail;  
  5. };  
可以看到,裏面有兩個元素,第一個是一個tasklet_struct結構體指針,在tasklet_vec中,這個用來指向與其最近的一個tasklet_struct結構體。另外一個是一個tasklet_struct類型的指針的指針,幹嘛用的呢,從上圖我們可以看到,它指向的是tasklet隊列最後一個tasklet_struct的next指針的地址。這樣一個tasklet_head就可以維護一個tasklet隊列了。這裏需要注意的是,當這個tasklet列表沒有一個tasklet_struct元素時,它的指向是這樣的:
通過這兩個示意圖,我們也知道了如何往該隊列增加tasklet_struct對象了,那就是將最後一個tasklet_sturct的next指針指向新加的tasklet_struct對象,同時將列表頭的tail指針的指針指向新加的tasklet_struct結構體的next指針的地址。下面我們看看具體添加的代碼:
  1. tasklet_struct * t;  
  2. * _get_cpu_var(tasklet_vec).tail = t;  
  3. _get_cpu_var(tasklet_vec).tail = &(t->next);  
初學者在第一次看到段代碼時,一般都看不太懂,即便是已經看懂了我上面話的tasklet隊列示意圖。其實這段代碼的目的非常簡單,就是將新的tasklet_struct指針t加入tasklet隊列,需要注意的是_get_cpu_var(變量名)的作用是獲取獨屬於該CPU的tasklet_vec變量。要看懂這兩行代碼需要有較強的C語言功底,尤其是要對指針的指針理解深刻。
首先我們來看第一行代碼:
*_get_cpu_var(tasklet_vec).tail = t;
將t所指向的地址賦給指針的指針tail,我們知道 *tail 表示的是其指向指針所指向的地址(在這裏有*tail == head 或者是 *tail == tasklet_struct ->next),而對於tail我們知道,其有兩種情況,第一種是指向最後一個tasklet_struct對象的next指針的地址,因此如果我們執行了*_get_cpu_var(tasklet_vec).tail = t;就表示了最後一個tasklet_struct對象的next的指向也發生變化了,即next指向了新的tasklet_struct;如果是第二種情況的話,即tail指向的是head指針的地址,那麼也可以知道執行*_get_cpu_var(tasklet_vec).tail = t;之後,head便會指向新的tasklet_struct結構體t。
第二行代碼:
_get_cpu_var(tasklet_vec).tail = &(t->next);
這行代碼很簡單,就是將tail指針指向新的tasklet_struct的next地址。

瞭解了上面的添加tasklet_struct對象之後,下面我們就可以分析tasklet的工作調用機制了。我們知道每一個軟中斷類型都會對應一個action(軟中斷處理程序),所以tasklet類型的軟中斷同樣也有其唯一對應的action(一般其他類型軟中斷的action都是由用戶自己編寫,但是tasklet不一樣,Linux設計師已經幫我們實現了。所以也是因爲這樣,tasklet被廣泛應用於驅動程序中。)
  1. static void tasklet_action(struct softirq_action *a)  
  2. {  
  3.     struct tasklet_struct *list;  
  4.   
  5.     local_irq_disable();  //禁止本地中斷  
  6.     list = __this_cpu_read(tasklet_vec.head); //獲取本地中斷的tasklet_vec.head指針的指向  
  7.     __this_cpu_write(tasklet_vec.headNULL);  //將tasklet_vec.head賦值爲null  
  8.     __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);  //將tasklet_vec.tail賦值爲head的地址  
  9.     local_irq_enable();  
  10.   
  11.     while (list) {  
  12.         struct tasklet_struct *t = list;  
  13.   
  14.         list = list->next;  
  15.   
  16.         if (tasklet_trylock(t)) { //主要是判斷該tasklet是否處於run狀態,如果處於run狀態的話,就從新將其放入tasklet_vec隊列中  
  17.             if (!atomic_read(&t->count)) {  
  18.                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))  
  19.                     BUG();  
  20.                 t->func(t->data); //執行tasklet的處理函數  
  21.                 tasklet_unlock(t);  
  22.                 continue;  
  23.             }  
  24.             tasklet_unlock(t);  
  25.         }  
  26.   
  27.         local_irq_disable();  
  28.         t->next = NULL;  
  29.         *__this_cpu_read(tasklet_vec.tail) = t;  //如果tasklet正在被其他CPU運行,那麼就將該tasklet重新裝入隊列現在再來看這兩行代碼就應該熟悉了吧  
  30.         __this_cpu_write(tasklet_vec.tail, &(t->next));  
  31.         __raise_softirq_irqoff(TASKLET_SOFTIRQ); //將tasklet掛起,等待下一次調用do_softirq函數的時候,這些加入tasklet隊列的tasklet_struct對象就會被執行。  
  32.         local_irq_enable();  
  33.     }  
  34. }  

接下來我們再來看tasklet一個非常重要的函數,就是tasklet_schedule,這個函數通常用於中斷處理程序中,用於將tasklet_struct加入所在CPU的tasklet隊列,同時將tasklet軟中斷掛起。因爲我們知道,在中斷的上半部中的irq_exit函數中,會激活do_softirq函數,所以在中斷處理程序中使用tasklet_schedule函數就顯得特別必要。下面我們來看一下tasklet_schedule函數的源碼:
  1. static inline void tasklet_schedule(struct tasklet_struct *t)  
  2. {  
  3.     if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))  
  4.         __tasklet_schedule(t); //調用_tasklet_schedule函數  
  5. }  
這裏要說明一下test_and_set_bit(TASKLET_STATE_SCHED, &t->state)函數,這個函數的目的是首先判斷t->state是否是TASKLET_STATE_SCHED,如果是就返回TASKLET_STATE_SCHED,如果不是則將t->state設置爲TASKLET_STATE_SCHED,同時返回t->state原來的值。所以這裏要執行if後面的代碼,就必須要t->state原來的值爲0,即該tasklet是一個全新的tasklet,沒有被用過。我們再來看看_tasklet_schedule函數:
  1. void __tasklet_schedule(struct tasklet_struct *t)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     local_irq_save(flags); //禁止本地中斷,因爲tasklet_vec是本地CPU的公共資源,在一個程序正在使用時,肯定不能被其他程序同時使用,這樣被導致安全問題。  
  6.     t->next = NULL;  
  7.     *__this_cpu_read(tasklet_vec.tail) = t;  
  8.     __this_cpu_write(tasklet_vec.tail, &(t->next));  //這兩行代碼很熟悉吧  
  9.     raise_softirq_irqoff(TASKLET_SOFTIRQ);  //後面當然也很熟悉  
  10.     local_irq_restore(flags); //恢復本地中斷  
  11. }  

在我們驅動程序中若要使用tasklet,首先我們還必須要創建一個tasklet_struct對象,通常創建tasklet_struct對象一共有兩種方式;
一種是靜態的創建方式,採用define這種預編譯的方式,這裏可以使用一個庫預編譯命令:
  1. DECLARE_TASKLET(name, func, data)    //count = 0;處於激活狀態  
  2. DECLARE_TASKLET_DISABLED(name, func, data) //count = 1;處於未激活狀態  
第二種是動態創建方式:
  1. static struct tasklet_struct my_tasklet;  
  2. tasklet_init(&my_tasklet, tasklet_handler, 0); //count = 0,處於激活狀態。  
其中tasklet_init函數也是系統函數,可以直接使用的
  1. void tasklet_init(struct tasklet_struct *t,  
  2.           void (*func)(unsigned long), unsigned long data)  
  3. {  
  4.     t->next = NULL;  
  5.     t->state = 0;  
  6.     atomic_set(&t->count, 0);  
  7.     t->func = func;  
  8.     t->data = data;  


發佈了0 篇原創文章 · 獲贊 18 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章