Chinaunix首页 | 论坛 | 博客
  • 博客访问: 181904
  • 博文数量: 57
  • 博客积分: 2215
  • 博客等级: 大尉
  • 技术积分: 635
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-09 15:47
个人简介

非淡泊无以明志,非宁静无以致远

文章分类

全部博文(57)

文章存档

2013年(12)

2011年(15)

2010年(30)

我的朋友

分类:

2011-03-04 10:07:45

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_count的SOFTIRQ_OFFSET。In_interrupt判断就会返回一个真值

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

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

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

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

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

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

  四:几种常用的软中断分析

  经过上面的分析,看到了linux的软中断处理模式,我们具体分析一下2.6kernel中常用的几种软中断

  1:tasklet分析

  Tasklet也是俗称的小任务机制,它使用比较方法,另外,还分为了高优先级tasklet与一般tasklet。还记得我们刚开始分析过的softirq_init()这个函数吗

void __init softirq_init(void)
{
   //普通优先级
   open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
   //高优先级
   open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
它们的软中断处理函数其实是tasklet_action与tasklet_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标志才会被遍历到链表上对应的函数

  那在我们自己的代码里该如何使用tasklet呢?举个例子:

#include
#include
#include
#include
static void tasklet_test_handle(unsigned long arg)
{
   printk("in tasklet testn");
}
//声明一个tasklet
DECLARE_TASKLET(tasklet_test,tasklet_test_handle,0);
MODULE_LICENSE("GPL xgr178@163.com");
int kernel_test_init()
{
   printk("test_initn");
   //调度这个tasklet
   tasklet_schedule(&tasklet_test);
}
int kernel_test_exit()
{
   printk("test_exitn");
   //禁用这个tasklet
   tasklet_kill(&tasklet_test);
   return 0;
}
module_init(kernel_test_init);
module_exit(kernel_test_exit);

示例模块里涉及到tasklet通用的三个API.分别是DECLARE_TASKLET(), tasklet_schedule(),tasklet_kill()

  跟踪一下内核代码:

#define DECLARE_TASKLET(name, func, data)
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
struct tasklet_struct
{
   struct tasklet_struct *next;
   unsigned long state;
   atomic_t count;
   void (*func)(unsigned long);
   unsigned long data;
};

  实际上,DECLARE_TASKLET就是定义了一个tasklet_struct的变量.相应的tasklet调用函数为func().函数参数为data

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 interruptn");
   //一直等待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调度并运行完,可能会睡眠,所以不能在中断环境中使用它

2:网络协议栈里专用软中断

  在前面分析网络协议协的时候分析过,网卡有两种模式,一种是中断,即数据到来时给CPU上传中断,等到CPU处理中断.第二种是轮询,即在接收到第一个数据包之后,关闭中断,CPU每隔一定时间就去网卡DMA缓冲区取数据.其实,所谓的轮询就是软中断.接下来就来研究一下网络协议栈的软中断

static int __init net_dev_init(void)
{
   ……
   open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
   open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
   ……
}
在这里注册了两个软中断,一个用于接收一个用于发送,函数大体差不多,我们以接收为例.从前面的分析可以知道,软中断的处理函数时就是它调用open_softirq的action参数.在这里即是net_rx_action.代码如下:

static void net_rx_action(struct softirq_action *h)
{
   //per_cpu链表.所有网卡的轮询处理函数都通过napi_struct结构存放在这链表里面
   struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
   unsigned long start_time = jiffies;
   int budget = netdev_budget;
   void *have;
   //关中断
   local_irq_disable();
   //遍历链表
   while (!list_empty(list)) {
      struct napi_struct *n;
      int work, weight;
      if (unlikely(budget <= 0 || jiffies != start_time))
         goto softnet_break;
      local_irq_enable();
      // 取链表里的相应数据
      n = list_entry(list->next, struct napi_struct, poll_list);
      have = netpoll_poll_lock(n);
      weight = n->weight;
      work = 0;
      //如果允许调度,则运行接口的poll函数
      if (test_bit(NAPI_STATE_SCHED, &n->state))
         work = n->poll(n, weight);
      WARN_ON_ONCE(work > weight);
      budget -= work;
      //关中断
      local_irq_disable();
      if (unlikely(work == weight)) {
         //如果被禁用了,就从链表中删除
         if (unlikely(napi_disable_pending(n)))
           __napi_complete(n);
         Else
           //否则加入链表尾,等待下一次调度
           list_move_tail(&n->poll_list, list);
      }
      netpoll_poll_unlock(have);
   }
out:
   //启用中断
   local_irq_enable();
//选择编译部份,忽略
#ifdef CONFIG_NET_DMA
   /* There may not be any more sk_buffs coming right now, so push
   * any pending DMA copies to hardware
   */
   if (!cpus_empty(net_dma.channel_mask)) {
      int chan_idx;
      for_each_cpu_mask(chan_idx, net_dma.channel_mask) {
         struct dma_chan *chan = net_dma.channels[chan_idx];
         if (chan)
           dma_async_memcpy_issue_pending(chan);
      }
   }
#endif
   return;
softnet_break:
   __get_cpu_var(netdev_rx_stat).time_squeeze++;
   __raise_softirq_irqoff(NET_RX_SOFTIRQ);
   goto out;
}
一般在接口驱动中,会调用__napi_schedule.将其添加进遍历链表.代码如下示:

void fastcall __napi_schedule(struct napi_struct *n)
{
   unsigned long flags;
   local_irq_save(flags);
   //加至链表末尾
   list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
   //激活软中断
   __raise_softirq_irqoff(NET_RX_SOFTIRQ);
   local_irq_restore(flags);
}

  关于网卡选择哪一种模式最为合适,我们在前面已经讲述过,这里不再赘述.

  五:小结

  本节主要分析了中断程序的处理过程与软中断的实现.虽然软中断实现有很多种类,究其模型都是一样的,就是把中断的一些费时操作在响应完中断之后再进行.另外,中断与软中断处理中有很多临界区,需要关闭CPU中断和打开CPU中断.其中的奥妙还需要慢慢的体会

阅读(1388) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~