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,簡單方便,而且軟中斷是在編譯時決定的,開銷太大。