内核代码阅读(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.
上一篇:基于istio的流量镜像构建真实流量的staging环境


下一篇:响应式布局的常用解决方案对比(媒体查询、百分比、rem和vw/vh)