1. 程式人生 > >linux 軟中斷和tasklet

linux 軟中斷和tasklet

1. 軟中斷是什麼 ?

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

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

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

static struct sotfirq_action softirq_vec[NR_SOFTIRQS];
struct softirq_action {
void (*action)(struct softirq_action *);
}

目前程式碼中有10種類型的軟中斷
enum  {
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        BLOCK_IOPOLL_SOFTIRQ,
        TASKLET_SOFTIRQ,
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,

        NR_SOFTIRQS
}

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

核心函式:

open_softirq(int nr, void (*action)(struct softirq_action *))  //一個軟中斷和對應的處理函式的繫結,
{
        softirq_vec[nr].action = action;
}
raise_softirq(unsingned int nr)   //表明一個軟中斷處於pending狀態等待處理, 在do_softirq函式裡會檢查處於pending的軟中斷,然後呼叫對應的處理函式
{
        or_softirq_pending(1UL << nr);    // 主要置位per-cpu型別的irq_stat.__softirq_pending成員的第nr位
        if (!in_interrupt())   //如果不在中斷上下文,則喚醒核心執行緒ksoftirqd/n來處理pending的軟中斷,如果在中斷上下文的話,可能是由於軟中斷被禁止執行了
            wakeup_softirqd(); 
}

do_softirq(void)
{
        if(in_interrupt())    // 這裡有兩種情況:do_softirq已經在中斷上下文被呼叫一次了,例如在執行軟中斷的時候又發生了硬體中斷,在中斷退出的時候又會去執行軟中斷,防止程式碼的重入,第二種情況是軟中斷系統被顯示地禁止執行了(全域性變數preempt_count的第二個欄位softirq counter用來記錄軟中斷被disable的次數)
             return;
        pending = local_softirq_pending();
        if (pending)
             __do_softirq();
                 -->__local_bh_disable(); // 進入軟中斷的處理前,會顯示地disable掉軟中斷,防止重入
                 --> do {
                           if (pending & 1) {
                               softirq_vec[nr]->action(h); //呼叫對應的軟中斷處理函式
                           }
                           pending >>=1;  //優先順序從0開始
                         }  while (pending);
}


3. tasklet的概念和實現

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

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);   //陣列中的元素為一個連結串列頭, 所有的tasklet是連結在一個連結串列上的
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

struct tasket_head {
        struct tasklet_struct *head;
        struct tasklet_struct **tail;
}

struct tasklet_struct {
        struct tasklet_struct *next;
        unsigned long state;    //一個tasklet物件所處的狀態,可以為TASKLET_STATE_SCHED或者TASKLET_STATE_RUN
        atomic_t count;     //不為0表示禁止執行tasklet
        void (*func)(unsigned long);
        unsigned data;
}

static inline void tasklet_schedule(struct tasklet_struct *t)
{
         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, 如果被置位,則不會執行  
               __tasklet_schedule(t);  //將該tasklet連結到本地CPU對應的tasklet連結串列上去
                    -->raise_softirq_irqoff(TASKLET_SOFTIRQ); //通知軟中斷系統,TASKLET_SOFTIRQ型別的軟中斷處於pending狀態
}

void tasklet_action(struct softirq_action *a)
{
          struct tasklet_struct *list = __this_cpu_read(tasklet_vec.head);

          while(list) {
                struct tasklet_struct *t = list;
                list = list->next;
                if (tasklet_trylock(t)) {   //測試TASKLET_STATE_RUN標誌有沒有置位,然後置位該標誌位
                        if(!atomic_read(&t->count)) {   //該tasklet被disabled了嗎?
                              if(!test_and_clear_bit(TASKLET_STATE_SCHED, &t->sched))
                                          BUG();
                        t->func(t->data);
                        tasklet_unlock(t);
                        continue;
                        }
                        tasklet_unlock(t);  //復位TASKLET_STATE_RUN標誌位
                 }
           }

}


static inline void tasklet_disable(struct tasklet_struct *t)
{
          atomic_inc(&t->count);  
          tasklet_unlock_wait(t); //等待正在執行的tasklet執行完畢,確保呼叫該函式後,在呼叫tasklet_schedule後tasklet是不會被執行的
}

void tasklet_kill(struct tasklet_struct *t)  //感覺這個函式和tasklet_disable類似,只是等待當前正在執行的tasklet完成,下次呼叫tasklet_schedule應該還是會執行的,貌似和kill這個單詞不匹配啊,還沒tasklet_disable徹底
{
          while(test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
                do {
                   yield();
                 } while(test_bit(TASKLET_STATE_SCHED, &t->sched));
          }
          tasklet_unlock_wait(t);
          clear_bit(TASKLET_STATE_SCHED, &t->state);
}

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