您的位置:首页 > 运维架构 > Linux

Linux内核修炼之软中断分析

2012-05-19 22:27 246 查看
====本文系本站原创,欢迎转载! 转载请注明出处:http://blog.csdn.net/yyplc====
基于内核:linux-2.6.30.4 arm平台分析.
中断分成硬中断和软中断。软中断是通过软件的方式模拟硬中断,以使内核可以延期或异步执行任务的目的。

软中断的核心元素包括:

  1、 软中断状态寄存器soft interrupt state(irq_stat)

  2、 软中断向量表(softirq_vec)

  3、 软中断守护daemon进程

  软中断的工作工程模拟了实际的中断处理过程,当某一软中断事件发生后,首先需要设置对应的中断标记位,触发中断事务,然后唤醒守护线程去检测中断状态寄存器,如果通过查询发现有软中断事务发生,那么通过查询软中断向量表调用相应的软中断服务程序action()。这就是软中断的过程,与硬件中断唯一不同的地方是从中断标记到中断服务程序的映射过程。在CPU的硬件中断发生之后,CPU需要将硬件中断请求通过向量表映射成具体的服务程序,这个过程是硬件自动完成的,但是软中断不是,其需要守护线程去实现这一过程,这也就是软件模拟的中断,故称之为软中断。linux软中断是与平台无关的,实现代码在kernel/Softirq.c中.

相应的数据结构如下:

中断未悬寄存器

typedef struct {
           unsigned int __softirq_pending;
           unsigned int local_timer_irqs;
} ____cacheline_aligned irq_cpustat_t;
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)


Linux-2.6.30.4中采用一个共用体来记录所支持的软中断以及软中断个数:

enum
{
          HI_SOFTIRQ=0,
          TIMER_SOFTIRQ,
          NET_TX_SOFTIRQ,  
          NET_RX_SOFTIRQ,
          BLOCK_SOFTIRQ,
          TASKLET_SOFTIRQ,
          SCHED_SOFTIRQ,
          HRTIMER_SOFTIRQ,
          RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
          NR_SOFTIRQS   
};


软中断的action,也就是发生相应软中断时的服务函数

struct softirq_action
{
      void (*action)(struct softirq_action *);
};


数组softirq_vec[ ]描述了所有软中断的向量表

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;


软中断的注册:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}


如在start_kernel-->softirq_init()中注册TASKLET_SOFTIRQ软中断

open_softirq(TASKLET_SOFTIRQ, tasklet_action);


软中断的触发:

void raise_softirq(unsigned int nr)
{
    unsigned long flags;
    local_irq_save(flags);        //并关闭中断
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);    //开启中断
}
raise_softirq其中关键是调用raise_soft_irqoff,所以__raise_soft_irqoff才是触发软中断的函数:

inline void raise_softirq_irqoff(unsigned int nr)
{
     __raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
    if (!in_interrupt())   //如果不是在中断中,则唤醒ksoftirq()守护进程
       wakeup_softirqd(); 
}


其中__raise_soft_irqoff定义如下,可以看出这是一种软件模拟,因为未旋寄存器是软件实现置位的!

#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)


如触发软中断TASKLET_SOFTIRQ时使用

void __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;
    local_irq_save(flags);
    t->next = NULL;
    *__get_cpu_var(tasklet_vec).tail = t;
    __get_cpu_var(tasklet_vec).tail = &(t->next);
    raise_softirq_irqoff(TASKLET_SOFTIRQ);
    local_irq_restore(flags);
}


有注册和触发之后,那么软中断就需要执行了。执行操作由do_softirq()和__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()
    __do_softirq();   //
    local_irq_restore(flags);  //开启中断
}


asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;

    pending = local_softirq_pending();
    account_system_vtime(current);
    //关闭本地CPU下半部。__local_bh_disable该函数是增加current_thread_info()->preempt_count的软中断部分的计数//使得进程    //不可调度//那么软中断的执行序列一般就不会被打断,可以线性执行,也就保证了一个软中断的实例
    __local_bh_disable((unsigned long)__builtin_return_address(0));
    lockdep_softirq_enter();

    cpu = smp_processor_id();     //获取当前cpu的
restart:
/* Reset the pending bitmask before enabling irqs */
     set_softirq_pending(0);

     local_irq_enable();
     h = softirq_vec;      //从软中断向量表中获取各个软中断的服务函数
     do {
         if (pending & 1) {
         int prev_count = preempt_count();
         trace_softirq_entry(h, softirq_vec);
         h->action(h);  //这里就是执行相应软中断的服务函数了
         trace_softirq_exit(h, softirq_vec);
         if (unlikely(prev_count != preempt_count())) {
            printk(KERN_ERR "huh, entered softirq %td %s %p"
            "with preempt_count %08x,"
            " exited with %08x?\n", h - softirq_vec,
            softirq_to_name[h - softirq_vec],
            h->action, prev_count, preempt_count());
            preempt_count() = prev_count;
         }
         rcu_bh_qsctr_inc(cpu);
      }
      h++;
      pending >>= 1;
   } while (pending);

   local_irq_disable();
   pending = local_softirq_pending();    
   if (pending && --max_restart)  //max_restart定义了一次__do_softirq中可以执行的最大软中断的服务函数
       goto restart;
   if (pending)   //如果超过最大max_restart次数,则唤醒软中断守护ksoftirqd进程
     wakeup_softirqd();
   lockdep_softirq_exit();
   account_system_vtime(current);
   _local_bh_enable();
}

唤醒ksoftirqd守护进程:
void wakeup_softirqd(void)
{
    /* Interrupts are disabled: no need to stop preemption */
    struct task_struct *tsk = __get_cpu_var(ksoftirqd);    //获取当前cpu的ksoftirqd守护进程
    if (tsk && tsk->state != TASK_RUNNING)
      wake_up_process(tsk);
}


软中断的守护进程,没个CPU对应一个ksoftirqd

static int ksoftirqd(void * __bind_cpu)
{
    set_current_state(TASK_INTERRUPTIBLE);

    while (!kthread_should_stop()) {
       preempt_disable();
      if (!local_softirq_pending()) {   //如果无软中断未悬,则调用schedule()进行调度
      preempt_enable_no_resched();
      schedule();
      preempt_disable();
    }
    __set_current_state(TASK_RUNNING);       //设置当前进程为运行状态
    while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
  If already offline, we'll be on wrong CPU:
  don't process */
       if (cpu_is_offline((long)__bind_cpu))   
          goto wait_to_die;
       do_softirq();   //
       preempt_enable_no_resched();
       cond_resched();
       preempt_disable();
       rcu_qsctr_inc((long)__bind_cpu);
    }
    preempt_enable();
    set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;
wait_to_die:
    preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;
}
在__do_softirq()时候,又把硬中断打开了,当前的软中断有可能又被硬中断抢占,所以在硬中断退出的时候irq_exit()

asm_do_IRQ()是硬中断的总入口

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    irq_enter();  

/*
* Some hardware gives randomly wrong interrupts.  Rather
* than crashing, do something sensible.
*/
    if (irq >= NR_IRQS)
       handle_bad_irq(irq, &bad_irq_desc);
    else
       generic_handle_irq(irq);
    /* AT91 specific workaround */
     irq_finish(irq);
     irq_exit();
     set_irq_regs(old_regs);
}


irq_exit()时也会进行do_softirq()

void irq_exit(void)
{
    account_system_vtime(current);
    trace_hardirq_exit();
    sub_preempt_count(IRQ_EXIT_OFFSET);
    if (!in_interrupt() && local_softirq_pending())
      invoke_softirq();     //invoke_softirq() --> do_softirq()

    #ifdef CONFIG_NO_HZ
    /* Make sure that timer wheel updates are propagated */
    rcu_irq_exit();
    if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
      tick_nohz_stop_sched_tick(0);
    #endif
    preempt_enable_no_resched();
}






                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: