天天看點

核心代碼閱讀(17) - softirq和bottom half

軟中斷和 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.      

繼續閱讀