超强的Linux中断分析

1) IPI中断的初始化。

 

  intr_init_hook调用apic_intr_init(), 后者再调用──如果CONFIG_SMP──smp_intr_init(),

   这个函数设置IPI中断的处理, 然后, apic_intr_init()为另外两个IPI:SPURIOUS_APIC_VECTOR和

  ERROR_APIC_VECTOR设置ISR。

 

 

2) irq_desc[NR_IRQS]

 

   struct irq_desc,亦即irq_desc_t,描述了一个irq的属性, 如irqaction、depth、

  pending_mask等。

  include/linux/irq.h:

           

         struct irq_desc {

           irq_flow_handler_t handle_irq;

           struct irq_chip        *chip;

           struct msi_desc        *msi_desc;

           void          *handler_data;

           void          *chip_data;

           struct irqaction *action; /* IRQ action list */

           unsigned int        status;        /* IRQ status */

 

           unsigned int        depth;        /* nested irq disables */

           unsigned int        wake_depth; /*nested wake enables */

           unsigned int        irq_count; /*For detecting broken IRQs */

           unsigned int       irqs_unhandled;

           spinlock_t        lock;

         #ifdef CONFIG_SMP

           cpumask_t        affinity;

           unsigned int        cpu;

         #endif

         #if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)

           cpumask_t        pending_mask;

         #endif

         #ifdef CONFIG_PROC_FS

           struct proc_dir_entry *dir;

         #endif

           const char        *name;

         } ____cacheline_internodealigned_in_smp;

 

 

 

 

  Linux有一个全局变量, 包含了了所有的IRQ:(kernel/irq/handle.c)

 

         struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned = {

        [0 ... NR_IRQS-1] = {

           .status = IRQ_DISABLED,

           .chip = &no_irq_chip,

           .handle_irq = handle_bad_irq,

            .depth = 1,

           .lock = SPIN_LOCK_UNLOCKED,

         #ifdef CONFIG_SMP

           .affinity = CPU_MASK_ALL

           #endif

        }

         }

 

 

3) irq_chip(即在genericirq之前的hw_interrupt_type)

 

  >以下这段是genericirq之前的:

  >Linux支持N种可编程中断控制器PIC, 所以有一个struct hw_interrupt_type,对于i8259A

  >来说,这个结构是i8259A_irq_type, 对于IOAPIC来说, 根据设置为电平触发或边沿触发的方式,

  >分别有ioapic_level_type和ioapic_edge_type两个不同的结构。

 

   在引入genericirq补丁之后,定义了几个irq_chip结构:

        

        针对i386和x86-64各有一个定义的:

        ioapic_chip,

        i8259A_chip,

        lapic_chip,

        msi_chip,

        ht_irq_chip,

        vmi_chip,

      针对visw体系的:

        cobalt_irq_chip,

        piix4_master_irq_type,

        piix4_virtual_irq_type

      针对voyager体系的:

        vic_chip

      其它目的的:

        no_irq_chip,

        dummy_irq_chip

 

 

4) irq_stat[NR_CPUS]

 

  Linux定义了一个全局的数组,用来描述每个CPU上的irq处理状态:(arch/i386/kernel/irq.c)

 

         DEFINE_PER_CPU(irq_cpustat_t, irq_stat)__cacheline_internodealigned_in_smp;

         EXPORT_PER_CPU_SYMBOL(irq_stat);

 

  irq_stat_t的定义。

     typedef struct {

            unsigned int __softirq_pending;

            unsigned long idle_timestamp;

            unsigned int __nmi_count; /* arch dependent */

            unsigned int apic_timer_irqs; /* arch dependent */

     } ____cacheline_aligned irq_cpustat_t;

 

 

5). 中断共享

     

      我们知道,多个中断源可以共享一个irq线。 Linux的实现方式是,每个中断源都有自己的

      一个structirqaction,

 

      irqaction结构的定义:

 

      struct irqaction {

        irq_handler_t handler;

         unsigned long flags;

        cpumask_t mask;

        const char *name;

        void *dev_id;

        struct irqaction *next;

        int irq;

        struct proc_dir_entry *dir;

      };

 

      同一个irq可能有多个irqaction,组成一个链表。 struct irq_desc中有个域:

        

         struct irqaction *action; /* IRQ action list */

 

      这个链表就包含了所有共享该irq号的中断源(及其对应的handler等信息)。 当device driver

      进行request_irq()时,会为它生成一个irqaction,设置相应的值,然后挂载

      irq_desc[<irq>].action队列中(是添加在链表的最后面)。

 

      request_irq(irq, handler, irqflags, devname, dev_id) > setup_irq(irq,irqaction)

 

      flags有3个:

        IRQF_SHARED       : 共享中断号

      IRQF_DISABLED       : 就是旧时代的SA_INTERRUPT,设置了该标志,则执行ISR时关本地中断

      IRQF_SAMPLE_RANDOM     : 告诉内核,本中断源可以用作随机数生成器的熵池

 

      只有满足以下条件,irq才可以在多个中断源之间共享:

        

      a). 每个中断源都愿意共享irq: request_irq时指定了IRQF_SHARED

      b). 试图共享一个irq的中断源,具有相同的触发机制(都是level trigger,或者都是edge

        trigger),并且具有相同的polarity(都是低电平有效,或者都是高电平有效)

  

      下面是set_irq()函数中判断old和new两个中断源是否可以share同一个irq号的代码:

      /*

     * Can't share interrupts unless both agree to and are

     * the same type (level, edge, polarity). So both flag

     * fields must have IRQF_SHARED set and the bits which

     * set the trigger type must match.

     */

      if (!((old->flags & new->flags) & IRQF_SHARED) ||

        ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {

        old_name = old->name;

        goto mismatch;

      }

 

 

 

 

6). 中断处理(do_IRQ, __do_IRQ,generic_handle_irq, etc) - Part I: __do_IRQ

     

       __do_IRQ()是genericirq引入之前的通用中断处理函数(除了IPI中断,其它所有中断/异常

      都经过它),它由do_IRQ调用,并调用handle_IRQ_event(而handle_IRQ_event会调用各个

      driver的ISR)。

 

      在引入genericirq之后,__do_IRQ()函数已基本不用了。 64位的X86系统上还可能使用

      它(通过do_IRQ > generic_handle_irq),32位的x86已经完全不用它了。

 

      然而我们还是看一下__do_IRQ函数,因为道理是一样的:

 

      __do_IRQ():

/*{{{*/

      //首先给irq_desc[irq].lock加锁,以免别的CPU访问该desc结构

 

      spin_lock(&desc->lock);

 

      //发送ACK给中断控制器

 

      if (desc->chip->ack)

        desc->chip->ack(irq);

      /*

     * REPLAY is when Linux resends an IRQ that was dropped earlier

     * WAITING is used by probe to mark irqs that are being tested

     */

     /*清除IRQ_REPLAY和IRQ_WAITING标志*/

      status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);

 

      /*设置IRQ_PENDING标志。 这个flag的意思是,已经ACK但尚未处理*/

      status |= IRQ_PENDING; /* we _want_ to handle it */

 

      /*

     * If the IRQ is disabled for whatever reason, we cannot

     * use the action we have.

     */

      /*如果IRQ被disable了,但是我们收到了中断,说明这是个spurious interrupt,

     * 有些有BUG的主板等硬件会干这种事

     */

      action = NULL;

      /* 只要IRQ_DISABLED或者IRQ_INPROGRESS被设置,我们就不handle该irq。

     *

     * 对于IRQ_INPROGRESS被设置的情况,说明此irq号的另一个实例正运行在

     * 另一个CPU上,我们就不处理了,而是让 _那个_ CPU在运行完它的ISR时再检查

     * 一下IRQ_PENDING标志,那时候它会再去处理我们这里逃避的事情的

     */

      if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) { /*正常情况下2这都不被设置,

                                    *那我们就设置desc->status

                                    */

        action = desc->action;

        status &= ~IRQ_PENDING; /* we commit to handling,清除pending标志 */

        status |= IRQ_INPROGRESS; /* we are handling it ,设置inprogress标志*/

      }

      desc->status = status;

 

      /*

     * If there is no IRQ handler or it was disabled, exit early.

     * Since we set PENDING, if another processor is handling

     * a different instance of this same irq, the other processor

     * will take care of it.

     */

      if (unlikely(!action))

        goto out;

 

      /*

     * Edge triggered interrupts need to remember

     * pending events.

     * This applies to any hw interrupts that allow a second

     * instance of the same irq to arrive while we are in do_IRQ

     * or in the handler. But the code here>for (;;) {

        irqreturn_t action_ret;

 

         //真正的IRQ处理是handle_IRQ_event,我们先unlock

 

        spin_unlock(&desc->lock);

        action_ret = handle_IRQ_event(irq, action);

        spin_lock(&desc->lock);

        //再lock,因为后面还要unlock

 

        

        /*

        * 在我们调用handle_IRQ_event时,如果同一个irq又在另一个CPU上

        * 来了一次,那个CPU会检测到IRQ_INPROGRESS标志,只设置了IRQ_PENDING

        * 标志便退出了。 这时我们就会检测到该标志,从而再处理第2次到来的irq

        *

        * 注意!IRQ_PENDING只是个逻辑标志,而不是一个counter!所以,这种方式

        * 只能处理同一irq的两个实例!如果发生了更多实例,第3个,第4个……就丢失了

        */

 

        //如果没有第2个需要处理,退出

 

        if (likely(!(desc->status & IRQ_PENDING)))

           break;

 

        //还有第2个需要处理,那么就清除IRQ_PENDING标志,表示我们已经答应要处理它了

 

        desc->status &= ~IRQ_PENDING;

      }

     

/*}}}*/

 

 

7).中断处理(do_IRQ, __do_IRQ,generic_handle_irq, etc) - Part II: handle_IRQ_event

 

      handle_IRQ_event()依次调用irq_desc[irq]->action链表上的每一个action。

      它会先打开中断(如果request_irq时没有设置IRQF_DISABLED标志),然后一个个执行irqaction,

      再禁用本地中断。

 

      handle_IRQ_event:

 

      irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)

      {

        irqreturn_t ret, retval = IRQ_NONE;

        unsigned int status = 0;

 

        handle_dynamic_tick(action);

 

        //如果指定了IRQF_DISABLED,就在关中断的情形下执行ISR

 

        //否则的话,在开中断的情形下执行ISR

 

        if (!(action->flags & IRQF_DISABLED))

           local_irq_enable_in_hardirq();

 

        //该循环遍历irq_desc[irq]->action链表,一个个调用其handler域

 

        do {

           ret = action->handler(irq, action->dev_id);

           if (ret == IRQ_HANDLED)

                   status |= action->flags;

           retval |= ret;

           action = action->next;

        } while (action);

 

        if (status & IRQF_SAMPLE_RANDOM)

           add_interrupt_randomness(irq);

        local_irq_disable();

 

        return retval;

      }

 

      Linux有两种情况可能导致丢中断,都是在SMP下才会发生的:

 

      a). CPU1在处理irq N,结果又来了一个irq N在CPU2上执行,这时候该CPU2只设置

        irq_desc[irq].status的IRQ_PENDING标志,以便CPU1去检查从而再执行一遍。

      当如果CPU3又收到一次,也设置IRQ_PENDING标志,这时CPU2设置的信息会丢失。

 

      补救办法:无

 

      b). CPU1在处理器某IRQ之前,先发送ACK给PIC,结果这时候CPU2通过PIC禁用了该irq,

        从而导致irq_desc[irq].status的IRQ_DISABLED标志被设置。 然后CPU1在正要处理

      irq时发现对应的IRQ_DISABLED标志置位,于是退出。 这样就丢了一次中断。

 

      补救办法: 在下一次enable_irq()被调用时,检查是否存在的这样的丢失。若然,

           调用check_irq_resend()重新generate一次中断。

 

           注意,在__do_IRQ()的一开始会清楚irq_desc[irq].status的IRQ_REPLAY

           标志,这时为了防止对一次irq丢失「补救」多次。

 

 

 

8). 中断处理(do_IRQ, __do_IRQ, generic_handle_irq, etc) - Part III: Generic IRQ补丁

     

      FIXME:我记得genericirq补丁是ThomasGleixner和Ingo Molnar在大约2.6.17时引入的,

      当时支持i386、x86-64和arm三个体系结构。

 

      generic irq层的引入,是为了剥离irq flow和irq chip过于紧密的耦合。 为driver程序员提供

      通用的API来request/enable/disable/free中断,这样程序员不用知道任何底层的中断控制器细节。

 

 

8.1) 它为driver程序员提供的highlevel的API:

     

      request_irq()

      free_irq()

      disable_irq()

      enable_irq()

      disable_irq_nosync()     (SMP>)

      synchronize_irq()       (SMP>)

      set_irq_type()

      set_irq_wake()

      set_irq_data()

      set_irq_chip()

      set_irq_chip_data()

 

8.2) 它为irq flow提供了一组预定义了的方法:

     

      handle_level_irq()     => 针对level type的irq handler

      handle_edge_irq()     => 针对edge type的irq handler

      handle_simple_irq()     => 针对Simple and software-decoded IRQS

      //FIXME: 我猜测percpu irq不是IPI,而是某种x86没有的东西

 

       handle_percpu_irq()     => 针对per-cpu local IRQs

      handle_fasteoi_irq()     => 针对transparent controllers, 目前IO-APIC主要用它和edge

                      //FIXME: 什么叫透明的中断控制器?老子咋看不懂涅?

 

 

Irq的flow type, generic irq有以下数种:

 

      #define IRQ_TYPE_NONE       0x00000000     /* Default, unspecified type */

      #define IRQ_TYPE_EDGE_RISING    0x00000001     /* Edge rising type*/

      #define IRQ_TYPE_EDGE_FALLING    0x00000002     /* Edge fallingtype */

      #define IRQ_TYPE_EDGE_BOTH    (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)

      #define IRQ_TYPE_LEVEL_HIGH    0x00000004     /* Level high type*/

      #define IRQ_TYPE_LEVEL_LOW    0x00000008     /* Level low type*/

      #define IRQ_TYPE_SENSE_MASK    0x0000000f     /* Mask of the above*/

      #define IRQ_TYPE_PROBE      0x00000010     /* Probing inprogress */

 

--没有看到simple类型和per-cpu类型,我估计这2者都是其他architectures上的。 这里把EDGE触发的irq又分成了

上升沿、下降沿和both, level触发的又分成了低电平有效和high active。

 

 

这5个函数取代了原来的__do_IRQ,由do_IRQ直接调用:

     

      desc->handle_irq(irq, desc);

 

而这个irq_desc[irq].handle_irq又是在哪里设置的呢? 不同的irq chip有不同的设置,现在让

我们看一下ioapic_chip上的irqs的设置:

 

 

      static void ioapic_register_intr(int irq, unsigned long trigger)

      {

        /* 如果不是edge触发的,就设置为handle_fasteoi_irq *//

        if (trigger) {

           irq_desc[irq].status |= IRQ_LEVEL;

           set_irq_chip_and_handler_name(irq, &ioapic_chip,

                             handle_fasteoi_irq, "fasteoi");

        } else {

        /* 如果是edge触发的,就设置为handle_edge_irq */

           irq_desc[irq].status &=~IRQ_LEVEL;

           set_irq_chip_and_handler_name(irq, &ioapic_chip,

                              handle_edge_irq,"edge");

        }

      }

 

 

原来MSI中断也是用handle_edge_irq处理的,见代码:

 

pci_enable_msi() > msi_capability_init()\

                        =>arch_setup_msi_irqs() > arch_setup_msi_irq():

pci_enable_msim() >msim_capability_init() /

 

     

      set_irq_msi(irq, desc);

      write_msi_msg(irq, &msg);

 

      set_irq_chip_and_handler_name(irq, &msi_chip, handle_edge_irq,"edge");

 

 

8.4) genericirq提供的一些public functions

     

      synchronize_irq     : wait forpending IRQ handlers (on other CPUs)

      disable_irq_nosync     : disablean irq without waiting

      disable_irq       : disable an irqand wait for completion

      enable_irq       : enable handlingof an irq

      set_irq_wake       : control irqpower management wakeup

      free_irq       : free an interrupt

      request_irq       : allocate aninterrupt line

      set_irq_chip       : set the irqchip for an irq

      set_irq_type       : set the irqtype for an irq

      set_irq_data       : set irq typedata for an irq

      set_irq_chip_data     : set irqchip data for an irq

 

 

8.5) geneirc irq提供的一些internal functions

 

      handle_bad_irq       : handlespurious and unhandled irqs

      handle_IRQ_event     : irq actionchain handler

      __do_IRQ       : original allin>: initialize a dynamically allocated irq

      dynamic_irq_cleanup     : cleanupa dynamically allocated irq

      set_irq_msi       : set irq typedata for an irq

      handle_simple_irq     : Simple andsoftware-decoded IRQs.

      handle_level_irq     : Level typeirq handler

      handle_fasteoi_irq     : irqhandler for transparent controllers

      handle_edge_irq     : edge typeIRQ handler

      handle_percpu_irq     : Per CPUlocal irq handler

 

 

8.6) irq_chip结构的方法

 

      startup     : start up theinterrupt (defaults to ->enable if NULL)

               enable中断,使PIC可以handle它

      shutdown     : shut down theinterrupt (defaults to ->disable if NULL)

      enable        : enable theinterrupt (defaults to chip->unmask if NULL)

      disable     : disable theinterrupt (defaults to chip->mask if NULL)

      ack       : start of a new interrupt

               通知PIC:CPU开始处理这个irq了

      mask       : mask an interruptsource

      mask_ack : ack and mask an interrupt source

               mask和ack方法的结合,这样在某些平台上可以得到优化

      unmask        : unmask aninterrupt source

      eoi        : end of interrupt -chip level

      end        : end of interrupt -flow level

               通知PIC:中断处理完毕

      set_affinity : set the CPU affinity>: resend an IRQ to the CPU

               重新创建和递送一个irq

      set_type : set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ

               设置irq的flow type:level, edge, simple, per-cpu

      set_wake : enable/disable power-management wake-on of an IRQ

               是否支持由该irq来wake睡眠中的系统

      release        : release functionsolely used by UML

               仅由ULM使用

      typename : obsoleted by name, kept as migration helper

               已废弃

 

 

 

 

 

我自己还没弄懂, 只是零星的记录了一些看到的东西。 (因此别问我,否则失望)

看高手的文档,可以从无到有的学习,真的佩服这种写文档的能力。 我就只会写只有自己能看懂的文档。

你可能感兴趣的:(超强的Linux中断分析)