軟中斷和 Bottom Half
為什麼需要 Bottom Half?
背景: 中斷服務程式一般是在關閉中斷的情況下執行的,以避免嵌套而使控制複雜化。但是,如果長時間不打開中斷,又會丢失中斷。為此,desc的status中的标記位 SA_INTERRUPT可以控制在執行終端服務子程式的時候是否需要關閉中斷。實際的情況是,關閉中斷會‘擴大打擊面’,打開中斷會使情況變複雜。
1) 一般來說,一次中斷服務的過程常常可以分為兩部分。開頭的部分必須要在關閉中斷的情況下‘原子地‘,’立即‘得到執行。
2) 後面的部分則沒有嚴格的要求,而且可以合并處理,成為 Bottom Half.
什麼是軟中斷?
為簡化bh的設計,Bottom Half也像 do_IRQ 一樣被嚴格的串形化了。
1) bh函數的執行不允許嵌套。,對同一CPU上的嵌套執行加了鎖。
2) 在SMP中,在同一個時間内隻允許一個CPU執行bh函數。
性能問題: do_IRQ的串形化是針對一個通道的,但是bh沒有通道的概念,它的串行化是全局的。
而之前版本的核心的bh是串行化的,為了系統的解決這個問題,增加了一套“軟中斷softirq”的機制。
“信号”也被稱為軟中斷,那麼這幾個概念的差別:
“硬中斷”:是由外設發起的對CPU的中斷。
“softirq”:是“硬中斷的服務程式”發起的對核心的中斷。
“信号”:是由核心或其他程序發起的對其他程序的中斷。
softirq機制的初始化
void __init softirq_init()
{
int i;
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
1) kernel中有4種類型的軟中斷,bh隻是其中的一種。
enum
{
HI_SOFTIRQ=0,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
TASKLET_SOFTIRQ
};
2) bh_task_vec數組,從注釋中也可以看到這是老kernel中的bh數組。而新的kernel在bh機制上層加了“軟中斷”的機制。
extern struct tasklet_struct bh_task_vec[];
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
softirq, tasklet, bh函數 之間的關系是:
softirq -> tasklet -> bh函數。
tasklet和softirq的差別:tasklet隻能在一個CPU上執行。
tasklet和bh的差別:不同的tasklet可以同時在不同的CPU上執行。
tasklet是在softirq的機制下限制了并發的一種特例。
3) open_softirq
軟中斷号TASKLET_SOFTIRQ 的服務子程式是 tasklet_action,
軟中斷号HI_SOFTIRQ 的服務子程式是 tasklet_hi_action.
tasklet_init(bh_task_vec+i, bh_action, i);
一個bh就是一個做為tasklet執行的。隻不過抽象出了一個叫做tasklet的東東更加系統的解決了bh的問題。
有32個bh就有32個tasklet與之對應。
bh_action就是bh的函數
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->func = func;
t->data = data;
t->state = 0;
atomic_set(&t->count, 0);
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL)
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
unsigned long flags;
int i;
spin_lock_irqsave(&softirq_mask_lock, flags);
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
for (i=0; i<NR_CPUS; i++)
softirq_mask(i) |= (1<<nr);
spin_unlock_irqrestore(&softirq_mask_lock, flags);
}
1) softirq_vec
一個軟中斷号大小的數組,和硬中斷中的 irq_desc作用相同。
static struct softirq_action softirq_vec[32] __cacheline_aligned;
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
2) softirq_mask(i) |= (1<<nr);
extern irq_cpustat_t irq_stat[];
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define softirq_mask(cpu) _IRQ_STAT((cpu), __softirq_mask)
可以看出,softirq_mask就是irq_statp[i].__softirq_mask 。
每個CPU都有自己的中斷狀态的資料結構 irq_cpustat_t:
typedef struct {
unsigned int __softirq_active;
unsigned int __softirq_mask;
unsigned int __local_irq_count;
unsigned int __local_bh_count;
unsigned int __syscall_count;
unsigned int __nmi_count;
} ____cacheline_aligned irq_cpustat_t;
__softirq_active 相當于軟中斷請求寄存器。
__softirq_mask 相當于中斷屏蔽寄存器。
打開軟中斷就是注冊軟中斷的服務子程式action,然後在所有CPU的中斷狀态的 __softirq_mask上置位中斷号nr的bit位。
bh服務程式的初始化
softirq_init 隻是初始化了bh_task_vec和bh_action的關聯, 相當于有了bh的執行機制, 同時也建立了軟中斷的機制, 相當于有了中斷服務隊列,中斷服務程式還沒有挂如隊列.
具體的bh函數是通過init_bh來完成.
sched_init的片段:
void __init sched_init(void)
{
int cpu = smp_processor_id();
int nr;
init_task.processor = cpu;
for(nr = 0; nr < PIDHASH_SZ; nr++)
pidhash[nr] = NULL;
init_timervecs();
init_bh(TIMER_BH, timer_bh);
init_bh(TQUEUE_BH, tqueue_bh);
init_bh(IMMEDIATE_BH, immediate_bh);
atomic_inc(&init_mm.mm_count);
enter_lazy_tlb(&init_mm, current, cpu);
}
1) init_bh(TIMER_BH, timer_bh);
初始化了bh向量TIMER_BH的處理函數是 timer_bh.
2) 系統中預定義的bh向量
enum {
TIMER_BH = 0,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
init_bh(TIMER_BH, timer_bh)
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}
1) bh_base[nr] = routine;
bh的初始化就是設定了bh_base的數組.
static void (*bh_base[32])(void);
bh和softirq服務程式的調用
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_restore(flags);
}
}
static inline void __cpu_raise_softirq(int cpu, int nr)
{
softirq_active(cpu) |= (1<<nr);
}
1) mark_bh
調用一次bh.
2) test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
檢查是否有嵌套.
3) int cpu = smp_processor_id();
目前CPU.
4) t->next = tasklet_hi_vec[cpu].list;
把這個要執行的bh所代表的tasklet_struct挂如目前CPU的tasklet_hi_vec[cpu]中,等待被執行.
5) __cpu_raise_softirq(cpu, HI_SOFTIRQ);
産生一次軟中斷,把Bottom Half對應的軟中斷号 HI_SOFTIRQ置位.
6) 核心有4個時機會檢查是否有軟中斷到來,需要相應.
從系統調用中傳回 ret_from_sys_call()
從異常中傳回 ret_from_exception()
排程程式中 schedule()
處理完硬體中斷之後 do_IRQ()
if (softirq_active(cpu) & softirq_mask(cpu))
do_softirq();
do_softirq軟中斷的執行
asmlinkage void do_softirq()
{
int cpu = smp_processor_id();
__u32 active, mask;
if (in_interrupt())
return;
local_bh_disable();
local_irq_disable();
mask = softirq_mask(cpu);
active = softirq_active(cpu) & mask;
if (active) {
struct softirq_action *h;
restart:
softirq_active(cpu) &= ~active;
local_irq_enable();
h = softirq_vec;
mask &= ~active;
do {
if (active & 1)
h->action(h);
h++;
active >>= 1;
} while (active);
local_irq_disable();
active = softirq_active(cpu);
if ((active &= mask) != 0)
goto retry;
}
local_bh_enable();
return;
retry:
goto restart;
}
1) while(active)
檢查哪些軟中斷寄存器被置位了.調用相應的回調.
這個是HI_SOFTIRQ被置位了,對應的服務程式是tasklet_hi_action
tasklet_hi_action
static void tasklet_hi_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = NULL;
local_irq_enable();
while (list != NULL) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (atomic_read(&t->count) == 0) {
clear_bit(TASKLET_STATE_SCHED, &t->state);
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_enable();
}
}
1) int cpu = smp_processor_id();
目前的CPU.
2) list = tasklet_hi_vec[cpu].list;
取出目前CPU對應的要執行的tasklet連結清單.
3) while (list != NULL)
周遊連結清單.
4) if (核心代碼閱讀(16) _trylock(t))
注意: 因為tasklet_hi_action 是對軟中斷進行服務,要確定軟中斷不能嵌套.
上層的softirq_vec機制保證了同一個tasklet隻能在一個CPU上得到執行,不同的tasklet可以在不同的CPU上執行.
這個需要進一步限制bh類型的tasklet的并發性.
5) t->func(t->data);
調用這個tasklet對應的函數指針.在softirq_init中被設定成了bh_action.
bh_action
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();
if (!spin_trylock(&global_bh_lock))
goto resched;
if (!hardirq_trylock(cpu))
goto resched_unlock;
if (bh_base[nr])
bh_base[nr]();
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}
1) int cpu = smp_processor_id();
2) bh_base[nr]();
可以看到bh_action最終執行到了bh_base數組,這個正是在init_bh中設定的 timer_bh.