Linux 软中断机制分析

来源:
导读 大家好,我是本期栏目编辑小友,现在为大家讲解Linux 软中断机制分析问题。 最近中断分析比较忙,没有时间总结一些内核相关的东西。总结一

大家好,我是本期栏目编辑小友,现在为大家讲解Linux 软中断机制分析问题。

最近中断分析比较忙,没有时间总结一些内核相关的东西。总结一下这次的软中断,也就是sofTIrq。之后,我们将总结一些小任务和工作队列机制。

1.为什么软中断?

编写驱动程序时,产生中断后,内核可能需要在中断处理功能中做大量工作。但是,中断处理函数的处理会关闭中断。也就是说,当响应中断时,系统不能再次响应其他外部中断。因此,外部中断可能会丢失。因此,linux内核设计了一种架构,将中断功能要处理的任务分为两部分,一部分在中断处理功能中执行,然后系统关闭中断。另一部分在软件中断中执行。此时,中断开启,系统可以响应外部中断。

关于软件中断理论的各种书籍都有介绍,但没有描述。要真正理解软件中断的作用,必须从代码的角度进行分析。当我们工作时,我们强调专业。当一个人对某个领域一无所知时,我们称他为小白,我不是苹果电脑。小白的脑子里充满了各种各样的问题。慢慢地,当这些疑惑被解释清楚后,小白就变白了。这个时候我们对这个领域的基本框架有了一个了解,但是专业和专业之间还是有一定的差距。经过更多的时间,该领域可以逐渐整合,达到专业水平。

2.什么时候触发处理软件中断?

说了这么多废话,言归正传。当你第一次知道软件中断的时候,你脑子里一定有很多问题。首先,软件中断在哪里触发处理?这个问题的答案是:硬件中断处理后。以下函数在处理硬件中断后推导出中断处理函数,并在irq_exit中触发软件中断的处理。

ASM link void _ _ Exception ASM _ do _ IRQ(无符号int irq,struct pt _ regs * regs){ struct pt _ regs * old _ regs=set _ IRQ _ regs(regs);IRQ _ enter();/* *某些硬件会随机给出错误的中断。与其崩溃,不如做些明智的事情。*/if(IRQ=NR _ IRQS)handle _ bad _ IRQ(IRQ,bad _ IRQ _ desc);else generic _ handle _ IRQ(IRQ);/* AT91特定变通方法*/IRQ _ finish(IRQ);IRQ _ exit();set _ IRQ _ regs(old _ regs);} 12345678910111213141516171819 ASM link void _ excepTIon ASM _ do _ IRQ(无符号int irq,struct pt _ regs * regs){ struct pt _ regs * old _ regs=set _ IRQ _ regs(regs);IRQ _ enter();/* *某些硬件会随机给出错误的中断。与其崩溃,不如做些明智的事情。*/if(IRQ=NR _ IRQS)handle _ bad _ IRQ(IRQ,bad _ IRQ _ desc);elsegeneric _ handle _ IRQ(IRQ);/* AT91特定变通方法*/IRQ _ finish(IRQ);IRQ _ exit();set _ IRQ _ regs(old _ regs);}这里需要注意的是,invoke_sofTIrq只有满足两个条件才能被调用,一个是是否处于硬件中断处理或软件中断处理过程中,另一个是必须有软件中断处于挂起状态。第二,很容易理解,如果有软件中断,会处理,如果没有,不会处理。第一个很难理解。

/* *退出中断上下文。如果需要和可能,处理soft IRQ : */void IRQ _ exit(void){ account _ system _ vt time(当前);trace _ hard rq _ exit();sub _ prempt _ count(IRQ _ EXIT _ OFFSET);if(!in _ interrupt()local _ soft IRQ _ pending())invoke _ soft IRQ();# ifdef _ CONFIG _ NO _ HZ/*确保传播了计时器轮更新*/rcu _ IRQ _ exit();if(idle _ CPU(SMP _ processor _ id())a

mp;& !in_interrupt() && !need_resched()) tick_nohz_stop_sched_tick(0);#endif preempt_enable_no_resched();}1234567891011121314151617/*                                       * Exit an interrupt context. Process softirqs if needed and possible:                                       */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();#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();}

在linux系统的进程数据结构里,有这么一个数据结构

#define preempt_count()(current_thread_info()->preempt_count),

利用preempt_count可以表示是否处于中断处理或者软件中断处理过程中。

       #define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS)#define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS)#define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS)#define PREEMPT_OFFSET (1UL#define SOFTIRQ_OFFSET (1UL#define HARDIRQ_OFFSET (1ULsub_preempt_count(IRQ_EXIT_OFFSET);#define in_interrupt()(irq_count())#define irq_count()(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))1234567891011#define PREEMPT_MASK    (__IRQ_MASK(PREEMPT_BITS)#define SOFTIRQ_MASK    (__IRQ_MASK(SOFTIRQ_BITS)#define HARDIRQ_MASK    (__IRQ_MASK(HARDIRQ_BITS)#define PREEMPT_OFFSET    (1UL#define SOFTIRQ_OFFSET    (1UL#define HARDIRQ_OFFSET    (1UL sub_preempt_count(IRQ_EXIT_OFFSET); #define in_interrupt()(irq_count()) #define irq_count()(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))

preempt_count的8~23位记录中断处理和软件中断处理过程的计数。如果有计数,表示系统在硬件中断或者软件中断处理过程中。系统这么设计是为了避免软件中断在中断嵌套中被调用,并且达到在单个CPU上软件中断不能被重入的目的。对于ARM架构的CPU不存在中断嵌套中调用软件中断的问题,因为ARM架构的CPU在处理硬件中断的过程中是关闭掉中断的。只有在进入了软中断处理过程中之后才会开启硬件中断,如果在软件中断处理过程中有硬件中断嵌套,也不会再次调用软中断,because硬件中断是软件中断处理过程中再次进入的,此时preempt_count已经记录了软件中断!对于其它架构的CPU,有可能在触发调用软件中断前,也就是还在处理硬件中断的时候,就已经开启了硬件中断,可能会发生中断嵌套,在中断嵌套中是不允许调用软件中断处理的。Why?我的理解是,在发生中断嵌套的时候,表明这个时候是系统突发繁忙的时候,内核第一要务就是赶紧把中断中的事情处理完成,退出中断嵌套。避免多次嵌套,哪里有时间处理软件中断,所以把软件中断推迟到了所有中断处理完成的时候才能触发软件中断。

3. 软件中断的处理过程

之前我已经说到,软中断的一个很大的目的就是避免中断处理中,处理的操作过多而丢失中断。同时中断还需要考虑到一件事情就是中断处理过程过长就会影响系统响应时间。如果一个中断处理一秒钟,那你一定能感受到串口卡住的现象。从另外一方面说呢,我们又必须考虑中断处理的操作一定的优先度,毕竟是硬件触发的事务,关系到网络、块设备的效率问题。Linux内核就中断方面就必须考虑平衡这三个方面的问题。而下面我要分析的__do_softirq函数就恰似在这三者之间打太极,游刃有余,面面俱到!

       /* * We restart softirq processing MAX_SOFTIRQ_RESTART times, * and we fall back to softirqd after that. * * This number has been established via experimentation. * The two things to balance is latency against fairness - * we want to handle softirqs as soon as possible, but they * should not be able to lock up the box. */#define MAX_SOFTIRQ_RESTART 10asmlinkage 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);__local_bh_disable((unsigned long)__builtin_return_address(0)); trace_softirq_enter(); cpu = smp_processor_id();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(); h->action(h); if (unlikely(prev_count != preempt_count())) { printk(KERN_ERR "huh, entered softirq %td %p" "with preempt_count %08x," " exited with %08x?n", 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) goto restart; if (pending) wakeup_softirqd(); trace_softirq_exit(); account_system_vtime(current); _local_bh_enable();}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465/*                                       * We restart softirq processing MAX_SOFTIRQ_RESTART times,                                       * and we fall back to softirqd after that.                                       *                                       * This number has been established via experimentation.                                       * The two things to balance is latency against fairness -                                       * we want to handle softirqs as soon as possible, but they                                       * should not be able to lock up the box.                                       */#define MAX_SOFTIRQ_RESTART 10asmlinkage 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);__local_bh_disable((unsigned long)__builtin_return_address(0));    trace_softirq_enter();        cpu = smp_processor_id();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();                        h->action(h);                        if (unlikely(prev_count != preempt_count()))            {                printk(KERN_ERR "huh, entered softirq %td %p"                       "with preempt_count %08x,"                       " exited with %08x?n", 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)        goto restart;        if (pending)        wakeup_softirqd();        trace_softirq_exit();        account_system_vtime(current);    _local_bh_enable();}

__do_softirq函数处理软件中断过程如下图流程分析

4. 首先调用local_softirq_pending函数取得目前有哪些位存在软件中断

5. 调用__local_bh_disable关闭软中断,其实就是设置正在处理软件中断标记,在同一个CPU上使得不能重入__do_softirq函数

6. 重新设置软中断标记为0,set_softirq_pending重新设置软中断标记为0,这样在之后重新开启中断之后硬件中断中又可以设置软件中断位。

7. 开启硬件中断

8. 之后在一个循环中,遍历pending标志的每一位,如果这一位设置就会调用软件中断的处理函数。在这个过程中硬件中断是开启的,随时可以打断软件中断。这样保证硬件中断不会丢失。

9. 之后关闭硬件中断,查看是否又有软件中断处于pending状态,如果是,并且在本次调用__do_softirq函数过程中没有累计重复进入软件中断处理的次数超过10次,就可以重新调用软件中断处理。如果超过了10次,就调用wakeup_softirqd();唤醒内核的一个进程来处理软件中断。设立10次的限制,也是为了避免影响系统响应时间。

4. 处理软中断内核线程

之前我说到不能让CPU长时间来处理中断事务,这样会影响系统的响应时间,严重影响用户和系统之间的交互式体验。所以在之前的__do_softirq中最多将循环执行10次,那么当执行了10次仍然有软中断在pending状态,这个时候应该怎么处理呢?系统将唤醒一个软件中断处理的内核进程,在内核进程中处理pending中的软件中断。这里要注意,之前我们分析的触发软件中断的位置其实是中断上下文中,而在软中断的内核线程中实际已经是进程的上下文。

这里说的软中断上下文指的就是系统为每个CPU建立的ksoftirqd进程。

看完这个函数,我不得不佩服这个函数设计的精巧!而我更多的从中体会到其中蕴藏的一种做人的道理。那就是做人要霸道一点,太谦和太恭维不行,但是又不能横行霸道,原则的问题要公平讲理,一定的时候顾及别人的利益,好处不能一个人独吞。这就跟下面ksoftirqd处理过程一样,该狠的时候禁止抢占,其它进程别想调度到哦,但是自己占用CPU时间过长的话,也自觉的问一问是不是该释放CPU给其它进程了。

下面我们就来分析一下这个处理过程怎么就体现了上面的这种说法呢?软中断的内核进程中主要有两个大循环,外层的循环处理有软件中断就处理,没有软件中断就休眠。内层的循环处理软件中断,并每循环一次都试探一次是否过长时间占据了CPU,需要调度释放CPU给其它进程。具体的操作在注释中做了解释。

       static int ksoftirqd(void *__bind_cpu){ set_current_state(TASK_INTERRUPTIBLE);while (!kthread_should_stop()) { /*不管三七二十一首先禁止抢占,我掌握CPU,并全凭我自己掌握调度*/ preempt_disable(); if (!local_softirq_pending()) { preempt_enable_no_resched(); /*如果没有软中断在pending,那就让出CPU来吧*/ schedule(); /*我被唤醒了,首先掌握CPU,不让自己被抢占,自己决定自己的是否要调度*/ 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;}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647static int ksoftirqd(void *__bind_cpu){    set_current_state(TASK_INTERRUPTIBLE);while (!kthread_should_stop())    {        /*不管三七二十一首先禁止抢占,我掌握CPU,并全凭我自己掌握调度*/        preempt_disable();        if (!local_softirq_pending())        {            preempt_enable_no_resched();            /*如果没有软中断在pending,那就让出CPU来吧*/            schedule();            /*我被唤醒了,首先掌握CPU,不让自己被抢占,自己决定自己的是否要调度*/            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;}

 

标签:

版权声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢您的支持与理解。