linux 軟中斷和tasklet

1. 軟中斷是什麼 ?

 軟中斷是一種延時機制,代碼執行的優先級比進程要高,比硬中斷要低。相比於硬件中斷,軟中段是在開中斷的環境中執行的(長時間關中斷對系統的開銷太大), 代碼是執行在中斷/線程上下文的,是不能睡眠的,雖然每個cpu都有一個對應的ksoftirqd/n線程來執行軟中斷,但是do_softirq這個函數也還會在中斷退出時調用到,因此不能睡眠(中斷上下文不能睡眠的原因是由於調度系統是以進程爲基本單位的,調度時會把當前進程的上下文保存在task_struct這個數據結構中,當進程被調度重新執行時會找到執行的斷點,但是中斷上下文是沒有特定task_struct結構體的,當然現在有所謂的線程話中斷,可以滿足在中斷處理函數執行阻塞操作,但是實時性可能會有問題。還有就是中斷代表當前進程執行的概念,個人感覺有點扯淡,畢竟整個內核空間是由所有進程共享的,不存在代表的概念)

2. 軟中段是怎麼實現的?

一個模塊或者子系統的實現都是數據結構+算法來實現的,算法一般由特定的函數來表徵。數據結構一般會定義一些全局變量來支撐算法的實現。軟中斷牽涉到的數據結構主要是一個全局的向量數組,來映射一個軟中斷向量號和對應的處理函數:

  1. static struct sotfirq_action softirq_vec[NR_SOFTIRQS];  
  2. struct softirq_action {  
  3. void (*action)(struct softirq_action *);  
  4. }  

目前代碼中有10種類型的軟中斷

  1. enum  {  
  2.         HI_SOFTIRQ=0,  
  3.         TIMER_SOFTIRQ,  
  4.         NET_TX_SOFTIRQ,  
  5.         NET_RX_SOFTIRQ,  
  6.         BLOCK_SOFTIRQ,  
  7.         BLOCK_IOPOLL_SOFTIRQ,  
  8.         TASKLET_SOFTIRQ,  
  9.         SCHED_SOFTIRQ,  
  10.         HRTIMER_SOFTIRQ,  
  11.         RCU_SOFTIRQ,  
  12.   
  13.         NR_SOFTIRQS  
  14. }  

這裏只關注和tasklet相關的軟中斷: HI_SOFTIRQ和TASKLET_SOFTIRQ

核心函數:

  1. open_softirq(int nr, void (*action)(struct softirq_action *))  //一個軟中斷和對應的處理函數的綁定,  
  2. {  
  3.         softirq_vec[nr].action = action;  
  4. }  
  1. raise_softirq(unsingned int nr)   //表明一個軟中斷處於pending狀態等待處理, 在do_softirq函數裏會檢查處於pending的軟中斷,然後調用對應的處理函數  
  2. {  
  3.         or_softirq_pending(1UL << nr);    // 主要置位per-cpu類型的irq_stat.__softirq_pending成員的第nr位  
  4.         if (!in_interrupt())   //如果不在中斷上下文,則喚醒內核線程ksoftirqd/n來處理pending的軟中斷,如果在中斷上下文的話,可能是由於軟中斷被禁止執行了  
  5.             wakeup_softirqd();   
  6. }  
  1. do_softirq(void)  
  2. {  
  3.         if(in_interrupt())    // 這裏有兩種情況:do_softirq已經在中斷上下文被調用一次了,例如在執行軟中斷的時候又發生了硬件中斷,在中斷退出的時候又會去執行軟中斷,防止代碼的重入,第二種情況是軟中斷系統被顯示地禁止執行了(全局變量preempt_count的第二個字段softirq counter用來記錄軟中斷被disable的次數)  
  4.              return;  
  5.         pending = local_softirq_pending();  
  6.         if (pending)  
  7.              __do_softirq();  
  8.                  -->__local_bh_disable(); // 進入軟中斷的處理前,會顯示地disable掉軟中斷,防止重入  
  9.                  --> do {  
  10.                            if (pending & 1) {  
  11.                                softirq_vec[nr]->action(h); //調用對應的軟中斷處理函數  
  12.                            }  
  13.                            pending >>=1;  //優先級從0開始  
  14.                          }  while (pending);  
  15. }  


3. tasklet的概念和實現

驅動程序裏最常見的就是在中斷處理函數裏面調度一個tasklet來執行延時的任務,tasklet是在軟中斷的基礎之上實現的,牽涉到HI_SOFTIRQ和TASKLET_SOFTIRQ兩種軟中斷,兩種軟中斷對應的處理函數tasklet_action/tasklet_hi_action會去tasklet_vec和tasklet_hi_vec數組(基於per cpu結構)中取所存在的tasklet,然後執行相應的tasklet處理函數。

  1. static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);   //數組中的元素爲一個鏈表頭, 所有的tasklet是鏈接在一個鏈表上的  
  2. static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);  
  3.   
  4. struct tasket_head {  
  5.         struct tasklet_struct *head;  
  6.         struct tasklet_struct **tail;  
  7. }  
  8.   
  9. struct tasklet_struct {  
  10.         struct tasklet_struct *next;  
  11.         unsigned long state;    //一個tasklet對象所處的狀態,可以爲TASKLET_STATE_SCHED或者TASKLET_STATE_RUN  
  12.         atomic_t count;     //不爲0表示禁止執行tasklet  
  13.         void (*func)(unsigned long);  
  14.         unsigned data;  
  15. }  
  16.   
  17. static inline void tasklet_schedule(struct tasklet_struct *t)  
  18. {  
  19.          if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))  //同一個tasklet只能被調度一次,如果另一個CPU正在執行同一個tasklet,如果剛好清掉TASKLET_STATE_SCHED標誌,這個tasklet可以被掛到這個CPU上的tasklet_vec鏈表上,但是不能被這個CPU被執行,因爲在執行tasklet之前,會檢查當前tasklet的狀態是否爲TASKLET_STATE_RUN, 如果被置位,則不會執行    
  20.                __tasklet_schedule(t);  //將該tasklet鏈接到本地CPU對應的tasklet鏈表上去  
  21.                     -->raise_softirq_irqoff(TASKLET_SOFTIRQ); //通知軟中斷系統,TASKLET_SOFTIRQ類型的軟中斷處於pending狀態  
  22. }  
  23.   
  24. void tasklet_action(struct softirq_action *a)  
  25. {  
  26.           struct tasklet_struct *list = __this_cpu_read(tasklet_vec.head);  
  27.   
  28.           while(list) {  
  29.                 struct tasklet_struct *t = list;  
  30.                 list = list->next;  
  31.                 if (tasklet_trylock(t)) {   //測試TASKLET_STATE_RUN標誌有沒有置位,然後置位該標誌位  
  32.                         if(!atomic_read(&t->count)) {   //該tasklet被disabled了嗎?  
  33.                               if(!test_and_clear_bit(TASKLET_STATE_SCHED, &t->sched))  
  34.                                           BUG();  
  35.                         t->func(t->data);  
  36.                         tasklet_unlock(t);  
  37.                         continue;  
  38.                         }  
  39.                         tasklet_unlock(t);  //復位TASKLET_STATE_RUN標誌位  
  40.                  }  
  41.            }  
  42.   
  43. }  
  44.   
  45.   
  46. static inline void tasklet_disable(struct tasklet_struct *t)  
  47. {  
  48.           atomic_inc(&t->count);    
  49.           tasklet_unlock_wait(t); //等待正在執行的tasklet執行完畢,確保調用該函數後,在調用tasklet_schedule後tasklet是不會被執行的  
  50. }  
  51.   
  52. void tasklet_kill(struct tasklet_struct *t)  //感覺這個函數和tasklet_disable類似,只是等待當前正在執行的tasklet完成,下次調用tasklet_schedule應該還是會執行的,貌似和kill這個單詞不匹配啊,還沒tasklet_disable徹底  
  53. {  
  54.           while(test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {  
  55.                 do {  
  56.                    yield();  
  57.                  } while(test_bit(TASKLET_STATE_SCHED, &t->sched));  
  58.           }  
  59.           tasklet_unlock_wait(t);  
  60.           clear_bit(TASKLET_STATE_SCHED, &t->state);  
  61. }  

從上面的分析可以看出,tasklet的處理函數是不可重入的,換句話說,就是不可能在兩個CPU上跑相同的代碼,因爲一個tasklet只能被調度一次,也就是掛接在一個鏈表裏面,而且在哪個CPU上被調度,就會在哪個CPU被執行,因爲是基於per cpu結構的, 但是軟中斷對應的處理函數是可重入的, 需要處理同步的問題,不過一般驅動裏面還是用tasklet,簡單方便,而且軟中斷是在編譯時決定的,開銷太大。

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