Chinaunix首页 | 论坛 | 博客
  • 博客访问: 857762
  • 博文数量: 133
  • 博客积分: 7117
  • 博客等级: 少将
  • 技术积分: 1846
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 21:30
文章分类

全部博文(133)

文章存档

2012年(1)

2011年(4)

2010年(2)

2009年(57)

2008年(69)

分类: LINUX

2008-09-23 17:56:27

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的实现方式是,每个中断源都有自己的
        一个struct irqaction,

        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[].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:我记得generic irq补丁是Thomas Gleixner和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 falling type */
        #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 in progress */

    --没有看到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 for pending IRQ handlers (on other CPUs)
        disable_irq_nosync     : disable an irq without waiting
        disable_irq         : disable an irq and wait for completion
        enable_irq         : enable handling of an irq
        set_irq_wake         : control irq power management wakeup
        free_irq         : free an interrupt
        request_irq         : allocate an interrupt line
        set_irq_chip         : set the irq chip for an irq
        set_irq_type         : set the irq type for an irq
        set_irq_data         : set irq type data for an irq
        set_irq_chip_data     : set irq chip data for an irq

   
    8.5) geneirc irq提供的一些internal functions

        handle_bad_irq         : handle spurious and unhandled irqs
        handle_IRQ_event     : irq action chain handler
        __do_IRQ         : original all in>: initialize a dynamically allocated irq
        dynamic_irq_cleanup     : cleanup a dynamically allocated irq
        set_irq_msi         : set irq type data for an irq
        handle_simple_irq     : Simple and software-decoded IRQs.
        handle_level_irq     : Level type irq handler
        handle_fasteoi_irq     : irq handler for transparent controllers
        handle_edge_irq     : edge type IRQ handler
        handle_percpu_irq     : Per CPU local irq handler


    8.6) irq_chip结构的方法

        startup     : start up the interrupt (defaults to ->enable if NULL)
                  enable中断,使PIC可以handle它
        shutdown     : shut down the interrupt (defaults to ->disable if NULL)
        enable        : enable the interrupt (defaults to chip->unmask if NULL)
        disable     : disable the interrupt (defaults to chip->mask if NULL)
        ack         : start of a new interrupt
                  通知PIC:CPU开始处理这个irq了
        mask         : mask an interrupt source
        mask_ack    : ack and mask an interrupt source
                  mask和ack方法的结合,这样在某些平台上可以得到优化
        unmask        : unmask an interrupt 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 function solely used by UML
                  仅由ULM使用
        typename    : obsoleted by name, kept as migration helper
                  已废弃

[/td][/tr][/table]



我自己还没弄懂, 只是零星的记录了一些看到的东西。 (因此别问我,否则失望)
看高手的文档,可以从无到有的学习,真的佩服这种写文档的能力。 我就只会写只有自己能看懂的文档。   


有两个文档可以参考一下:
1, make htmldocs,看看生成的Documentation/DockBook/genericirq.html
2, [url][/url]
阅读(2442) | 评论(1) | 转发(0) |
0

上一篇:调试总结

下一篇:声明和定义

给主人留下些什么吧!~~

chinaunix网友2010-06-04 15:25:56

一个封建思想的女孩! 我至今不明白清白是什么含义