linux中断 下半部

Linux中断体系 下半部

一.Softirq

二.Tasklet

三.Tasklet_action

 

一.softirq

为了提高中断的响应速度,很多操作系统都把中断分成两个部分,上半部分和下半部分(bottom half.上半部分通常是响应中断,并把中断所得到的数据保存到下半部,耗时的操作一般都会留到下半部去处理。

softirq_init()中会注册两个常用类型的软中断,看具体代码:

void __init softirq_init(void)

{

      open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);

      open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

}

cat /proc/softirq时能看到此两种软中断

 

//参数含义:nr:软中断类型 action:软中断处理函数    data:软中断处理函数参数

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

{

      softirq_vec[nr].data = data;

      softirq_vec[nr].action = action;

}

static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;

struct softirq_action

{

      void  (*action)(struct softirq_action *);

      void  *data;

};

在上面的代码中,我们可以看到:open_softirq().其实就是对softirq_vec数组的nr项赋值.softirq_vec是一个32元素的数组,实际上linux内核只使用了六项. 如下示:

enum

{

      HI_SOFTIRQ=0,

      TIMER_SOFTIRQ,

      NET_TX_SOFTIRQ,

      NET_RX_SOFTIRQ,

      SCSI_SOFTIRQ,

      TASKLET_SOFTIRQ

}

另外.如果使软中断能被CPU调度,还得要让它激活才可以.激活所使用的函数为__raise_softirq_irqoff()

代码如下:

#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)

这个宏使local_softirq_pendingnr位置1

好了,经过open_softirq()à local_softirq_pending(),我们来看下软中断怎么被CPU调度.

继续上面中断处理的代码.在处理完硬件中断后,会调用irq_exit().这就是软中断的入口点了,我们来看下

#define irq_exit()                                      /

do {                                              /

           preempt_count() -= IRQ_EXIT_OFFSET;               /

           //注意了,软中断不可以在硬件中断上下文或者是在软中断环境中使用哦

           //softirq_pending()的判断,注意我们上面分析过的_raise_softirqoff().它判断当前cpu有没有激活软中断

           if (!in_interrupt() && softirq_pending(smp_processor_id())) /

                 do_softirq();                          /

           preempt_enable_no_resched();                      /

} while (0)

跟踪进do_softirq()

asmlinkage void do_softirq(void)

{

      __u32 pending;

      unsigned long flags;

      //在硬件中断环境中,退出

      if (in_interrupt())

           return;

      //禁止本地中断,不要让其受中断的影响

      local_irq_save(flags);

      pending = local_softirq_pending();

      //是否有软中断要处理?

      if (pending)

           __do_softirq();

      //恢复CPU中断

      local_irq_restore(flags);

}

转入__do_softirq()

asmlinkage void __do_softirq(void)

{

      struct softirq_action *h;

      __u32 pending;

      int max_restart = MAX_SOFTIRQ_RESTART;

      int cpu;

      pending = local_softirq_pending();

      //禁止软中断,不允许软中断嵌套

      local_bh_disable();

      cpu = smp_processor_id();

restart:

      /* Reset the pending bitmask before enabling irqs */

      //把挂上去的软中断清除掉,因为我们在这里会全部处理完

      local_softirq_pending() = 0;

      //CPU中断

      local_irq_enable();

      //softirq_vec:32元素数组

      h = softirq_vec;

      //依次处理挂上去的软中断

      do {

           if (pending & 1) {

                 //调用软中断函数

                 h->action(h);

                 rcu_bh_qsctr_inc(cpu);

           }

           h++;

           pending >>= 1;

      } while (pending);

      //CPU 中断

      local_irq_disable();

      pending = local_softirq_pending();

      //在规定次数内,如果有新的软中断了,可以继续在这里处理完

      if (pending && --max_restart)

           goto restart;

      //依然有没有处理完的软中断,为了提高系统响应效率,唤醒softirqd进行处理

      if (pending)

           wakeup_softirqd();

      //恢复软中断

      __local_bh_enable();

}

从上面的处理流程可以看到,软中断处理就是调用open_ softirq()action参数.这个函数对应的参数是软中断本身(h->action(h)),采用这样的形式,可以在改变softirq_action结构的时候,不会重写软中断处理函数

在进入了软中断的时候,使用了in_interrupt()来防止软中断嵌套,和抢占硬中断环境。然后软中断以开中断的形式运行,软中断的处理随时都会被硬件中断抢占,由于在软中断运行之前调用了local_bh_disable(),所以in_interrupt()为真,不会执行软中断.

来看下in_interrupt() local_bh_disable() __local_bh_enable()的具体代码:

#define in_interrupt()      (irq_count())

#define irq_count()   (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))

#define local_bh_disable() /

           do { preempt_count() += SOFTIRQ_OFFSET; barrier(); } while (0)

#define __local_bh_enable() /

           do { barrier(); preempt_count() -= SOFTIRQ_OFFSET; } while (0)

相当于local_bh_disable设置了preempt_countSOFTIRQ_OFFSETIn_interrupt判断就会返回一个真值

相应的__local_bh_enable()清除了SOFTIRQ_OFFSET标志

还有几个常用的判断,列举如下:

in_softirq():判断是否在一个软中断环境

hardirq_count():判断是否在一个硬中断环境

local_bh_enable()__local_bh_enable()作用是不相同的:前者不仅会清除SOFTIRQ_OFFSET,还会调用do_softirq(),进行软中断的处理

上述几个判断的代码都很简单,可自行对照分析

二.Tasklet

注册了两类软中断之后,在什么时候被触发呢?tasklet_schedule

       static inline void tasklet_schedule(struct tasklet_struct *t)

{

      //如果tasklet没有置调度标置,也就是说该tasklet没有被调度

      if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))

           __tasklet_schedule(t);

}

void fastcall __tasklet_schedule(struct tasklet_struct *t)

{

      unsigned long flags;

      //tasklet加到__get_cpu_var(tasklet_vec).list链表头

      local_irq_save(flags);

      t->next = __get_cpu_var(tasklet_vec).list;

      __get_cpu_var(tasklet_vec).list = t;

      //激活相应的软中断

      raise_softirq_irqoff(TASKLET_SOFTIRQ);

      local_irq_restore(flags);

}

这个函数比较简单,不详细分析了

void tasklet_kill(struct tasklet_struct *t)

{

      //不允许在中断环境中进行此操作

      if (in_interrupt())

           printk("Attempt to kill tasklet from interrupt/n");

      //一直等待tasklet被调度完

      while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {

           do

                 yield();

           while (test_bit(TASKLET_STATE_SCHED, &t->state));

      }

      //一直等待tasklet被运行完

      tasklet_unlock_wait(t);

      //清除调度标志

      clear_bit(TASKLET_STATE_SCHED, &t->state);

}

该函数会一直等待该tasklet调度并运行完,可能会睡眠,所以不能在中断环境中使用它

三.Tasklet_action

       软中断处理函数其实是tasklet_actiontasklet_hi_action.

static void tasklet_action(struct softirq_action *a)

{

      struct tasklet_struct *list;

      //禁止本地中断

      local_irq_disable();

      //per_cpu变量

      list = __get_cpu_var(tasklet_vec).list;

      //链表置空

      __get_cpu_var(tasklet_vec).list = NULL;

      //恢复本地中断

      local_irq_enable();

      //接下来要遍历链表了

      while (list) {

           struct tasklet_struct *t = list;

           list = list->next;

           //为了避免竞争,下列操作都是在加锁情况下进行的

           if (tasklet_trylock(t)) {

                 //t->count为零才会调用task_struct里的函数

                 if (!atomic_read(&t->count)) {

                      //t->count 1。但又没有置调度标志。系统BUG

                      if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))

                            BUG();

                      //调用tasklet函数

t->func(t->data);

                      tasklet_unlock(t);

                      continue;

                 }

                 tasklet_unlock(t);

           }

           //注意 :所有运行过的tasklet全被continue过去了,只有没有运行的tasklet才会重新加入到链表里面

           //禁本地中断

           local_irq_disable();

           //t放入队列头,准备下一次接收调度

           t->next = __get_cpu_var(tasklet_vec).list;

           __get_cpu_var(tasklet_vec).list = t;

           //置软中断调用标志。下次运行到do_softirq的时候,可以继续被调用

           __raise_softirq_irqoff(TASKLET_SOFTIRQ);

           //启用本地中断

           local_irq_enable();

      }

}

高优先级tasklet的处理其实与上面分析的函数是一样的,只是per_cpu变量不同而已。

另外,有几个问题值得考虑:

1)       cpu怎么计算软中断优先级的

do_softirq()à__do_softirq()有:

{

      pending = local_softirq_pending();

      ......

      do {

      if (pending & 1) {

            h->action(h);

            rcu_bh_qsctr_inc(cpu);

      }

      h++;

      pending >>= 1;

} while (pending);

......

}

从上面看到,从softirq_vec[]中取项是由pending右移位计算的。

另外,在激活软中断的操作中:

#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)

可以看到 nr越小的就会越早被do_softirq遍历到

2)       在什么条件下才会运行tasklet 链表上的任务

我们在上面的代码里看到只有在t->count为零,且设置了TASKLET_STATE_SCHED标志才会被遍历到链表上对应的函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值