01.linux 中断

来源:互联网 发布:千兆网络交换机的作用 编辑:程序博客网 时间:2024/05/21 09:52

1.中断
1.1 概述:
1)中断:通常为分为同步中断和异步中断。同步中断,只有在一条指令终止执行后才会才是产生(中断可控,这种中断也称作异常);异步中断,是由其他硬件设备依照CPU时钟信号随机产生(中断不可控,这种也称作中断)。
2)同步中断,又称作异常,是CPU执行指令时由CPU控制单元产生。(异常产生的时机分为两种:1.由程序执行出错造成的,内核通过发送一个unix的信号来处理异常;2.由内核必须处理的异常条件产生的,比如缺页异常,内核执行恢复异常的所有步骤。)异步中断,又称作中断,由是由其他硬件设备依照CPU时钟信号随机产生。
3)中断向量表:中断向量指的是用0~256之间的一个数来标识一个中断或异常,这个数就称作这个中断或异常的向量。所有是中断向量组合在一起,成为中断向量表。可以将中断向量按类别进行分类,如IRQ中断向量指定向量范围是32~238

IRQ中断向量(每个IRQ中断向量都对应一个IRQ线,即对应一个IRQ号)
   a. 代码位置:
          kernel\arch\arm\mach-rk30\include\mach\irqs.h 
          
   b. 解析:
     #define NR_GIC_IRQS                     (4 * 32)
     #define NR_GPIO_IRQS                    (4 * 32)
     #define NR_BOARD_IRQS                   64
     #define NR_IRQS                         (NR_GIC_IRQS + NR_GPIO_IRQS + NR_BOARD_IRQS)   =256+64=320
   c. 备注:
IRQ与可编程中断控制器(PIC):每个能够发出中断请求的硬件设备都有一个名为IRQ的输出线。所有现有的IRQ线都与一个可编程中断控制器连接。每个IRQ线都有对应的中断向量,如IRQ0的中断向量为32+0(IRQn 对应的中断向量为32+n)
          3)中断重定向表(IRT):可以编程向量的优先级,目标处理器,选择处理器的方式等
          4)中断描述符表(IDT):记录每类向量的中断或异常处理程序的入口地址。(X86平台,IDT存放在idt_table表中,其中把每种向量类又分为以下5中类别:任务门描述符,中断门描述符,陷阱门描述符,系统中断门描述符,系统门描述符)。trap_init() 初始化IDT。
          5)中断服务例程(ISR):IRQ中断向量在IDT中有对应的中断处理程序。每一个IRQ线可以由N个设备共享,这样每个设备都有自己独有的中断服务例程。当一个IRQ线上有一个中断信号产生,cpu首先调用IDT中的IRQ中断向量的中断处理程序,然后再执行挂载在该IRQ线上的所有中断服务例程(通过do_IRQ()函数执行,并会进行设备匹配)。
          6)中断能够嵌套,导致其不能够阻塞,即不能切换进程。内核的唯一异常就是缺页异常,导致缺页异常的进程能够阻塞,所以中断处理函数从不执行可以导致缺页的操作。

1.2 中断触发流程概述:
1)中断源 -> 中断向量表 -> 中断处理程序(通过中断向量在中断描述符表中找到对应的中断处理程序)->中断服务例程

1.3 关键数据结构
          1)struct irq_desc
               概述:中断向量描述符,标识一个中断向量。所有中断向量的中断向量描述符存放在irq_desc[NR_IRQS]数组中。
               结构:
struct irq_desc {
     struct irqaction     *action;     //指向在中断向量下的中断服务例程链表的第一个元素
     unsigned int          depth;     //nested irq disables,如果IRQ线被激活,则显示0;如果IRQ线被静止不止一次,则显示一个正数。enable_irq()减少该值,disable_irq()增加该值
}
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
     [0 ... NR_IRQS-1] = {
          .handle_irq     = handle_bad_irq,
          .depth          = 1,
          .lock          = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
     }
};

           2)struct irqaction
                概述:中断服务例程描述符,用来标识一个中断向量的中断服务例程。
                结构:
struct irqaction {
     irq_handler_t          handler;     //指向一个I/O设备的中断服务例程     
     unsigned int          irq;             //IRQ线     
     struct irqaction     *next;          //指向中断服务例程描述符链表的下一个元素
}




2.中断机制流程
2.1 中断描述符表初始化
         2.1.1 中断描述符表定义
                 1)代码位置:
                    kernel\arch\arm\kernel\entry-armv.S

                 2)代码
                    __vectors_start:
                    ARM(     swi     SYS_ERROR0     )
                    THUMB(     svc     #0          )
                    THUMB(     nop               )
                         W(b)     vector_und + stubs_offset                                  //未定义指令
                         W(ldr)     pc, .LCvswi + stubs_offset                                //软中断
                         W(b)     vector_pabt + stubs_offset                                 //预取指令终止
                         W(b)     vector_dabt + stubs_offset                                 //数据终止
                         W(b)     vector_addrexcptn + stubs_offset                        //reserved(保留)
                         W(b)     vector_irq + stubs_offset                                    //普通中断请求(IRQ)
                         W(b)     vector_fiq + stubs_offset                                    //快速中断请求(FIQ)

                         .globl     __vectors_end
                    __vectors_end:
                    
         2.1.2 中断描述符表拷贝
              1)概述:将定义的异常向量表拷贝至内存中
              2)初始化过程:
                      a. start_kernel(kernel/init/main.c)==> setup_arch(kernel/arch/arm/kernel/Setup.c)==>early_trap_init(kernel/arch/arm/kernel/Traps.c)
                      b. 拷贝
                          void __init early_trap_init(void)
                         {   
                             ......
                           memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
                           memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
                           memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
                             ......
                          }
                  
2.2 中断服务例程初始化
        2.2.1 IRQ中断服务例程初始化流程:
          1)start_kernel(kernel/init/main.c) => 
          2)init_IRQ(kernel\arch\arm\kernel\irq.c) =>
          3)rk30_init_irq(machine_desc->init_irq() = rk30_init_irq():\kernel\arch\arm\mach-rk30\board-rk3168-tb.c ;  rk30_init_irq() : kernel\arch\arm\mach-rk30\common.c)=>  
          4.1)gic_init(0, IRQ_LOCALTIMER, RK30_GICD_BASE, RK30_GICC_BASE)
                => gic_dist_init(kernel\arch\arm\common\gic.c)   
                => irq_set_chip_and_handler(kernel\include\linux\irq.h) =>irq_set_chip_and_handler_name(kernel\kernel\irq\chip.c) => irq_set_chip

               备注:
                    a)gic中断服务例程初始化
b)在kernel\arch\arm\mach-rk30\include\mach\Irqs.h 定义了IRQ中断向量,在IRQ中断初始化的时候对表中需要使用的中断源分别进行中断控制器加载,加载函数irq_set_chip_and_handler(i, &gic_chip, handle_fasteoi_irq); //gic中断服务例程初始化,IRQ_LOCALTIMER定义在Irqs.h ,表示有29个gic中断源,因此要对这29个中断源分别进行中断服务例程初始化,handle_fasteoi_irq是gic中断服务例程

          4.2)rk30_gpio_init (kernel\drivers\gpio\Gpio-rk30.c) =》 irq_set_chip_and_handler(irq, &rk30_gpio_irq_chip, handle_level_irq);//gpio中断服务例程初始化,handle_level_irq是gpio中断服务例程)
                备注:
                     a)gpio中断服务例程初始化
                     b)gpio中断服务例程初始化完后,每个gpio(每个gpio都是一个IRQ线,每个IRQ线都对应一个中断向量)都分配了一个I/O设备。
      
            
2.3 中断处理流程(irq中断为例)
        2.3.1  irq中断流程
             汇编层:
                  1)irq中断事件发生,CPU停止工作,保存当前部分寄存器的值到内核栈中
                  2)CPU的PC指针(寄存器)跳转到内存中的中断描述符表的起始位置(_vectors_start) ,并指向向量表中的  b vector_irq + stubs_offset 位置,执行此段代码  (b为汇编语言,为跳转的意思),即跳转"vector_irq + stubs_offset"位置
                        备注:
                             a. 所有类型的中断向量都会在中断描述符表中找到中断处理程序的入口,如irq类型的中断则跳转到

                  3)"vector_irq + stubs_offset"的位置在 kernel\arch\arm\kernel\entry-armv.S代码中有定义,即__irq_usr(rk平台是__irq_svc):
                                   .macro     vector_stub, name, mode, correction=0
                                   .align     5

                                   vector_\name:
                                             ......
                                   eor     r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)        //rk平台指定为SVC模式,即管理态
                                   msr     spsr_cxsf, r0                                                             //spsr是程序状态寄存器,通过模式指定程序的状态,

                                  vector_stub     irq, IRQ_MODE, 4                                           //中断调度器,通过程序的状态(spsr)指定中断处理入口

                                   .long     __irq_usr                   @  0  (USR_26 / USR_32)     //程序状态为用户态,则从这里进入
                                   .long     __irq_invalid               @  1  (FIQ_26 / FIQ_32)
                                   .long     __irq_invalid               @  2  (IRQ_26 / IRQ_32)
                                   .long     __irq_svc                    @  3  (SVC_26 / SVC_32)    //程序状态为管理态,则从这里进入
                                     ......

                  4) __irq_usr: =》  irq_handler
                  5)irq_handler  =》arch_irq_handler_default
                         代码:
                            .macro     irq_handler
                              ......
                              arch_irq_handler_default
                              9997:
                             .endm
                  6)arch_irq_handler_default(kernel\arch\arm\include\asm\entry-macro-multi.S) =》asm_do_IRQ 进入到C函数         //执行中断服务例程    
                      
     C语言层:
                 1)asm_do_IRQ (kernel\arch\arm\kernel\irq.c) //进来的时候就已经有了 IRQ号
                 2)asm_do_IRQ  =》generic_handle_irq(kernel\kernel\irq\irqdesc.c)
                        
                 3)generic_handle_irq=》 generic_handle_irq_desc(kernel\include\linux\irqdesc.h)     
                         
                 4)generic_handle_irq_desc =》desc->handle_irq(irq, desc);  //执行中断服务例程 ,如GPIO中断服务例程为handle_level_irq
    chip层:
                 1)desc->handle_irq  =》 handle_level_irq(kernel\kernel\irq\chip.c)  // 不同的中断服务例程,其处理函数不同
                 2)handle_level_irq =》handle_irq_event (kernel\kernel\irq\handle.c)
                         
                3)handle_irq_event =》handle_irq_event_percpu
                4)handle_irq_event_percpu =》action->handler   //中断执行函数
                      备注:
                         a. 此处就是调用在注册中断的时候,自己定义中断函数
                         b. 根据中断号,区分调用指定的中断函数




3. IRQ线动态分配     -  request_irq()  
3.1概述:该函数建立一个新的中断服务例程描述符irqaction ,并与参数值初始化。然后调用setup_irq()函数把它插入指定的IRQ号的中断服务例程链表中。
3.2 代码位置
                 kernel\include\linux\interrupt.h 
                 格式:request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
                 参数:
                        irq,中断号
                        handler,中断处理函数
                        flags,中断标志位
3.3 注册流程:
 request_irq => request_threaded_irq(根据中断号取得irq_desc 和 构造irqaction) =>   __setup_irq(插入指定的IRQ的中断服务例程链表中) => __enable_irq => irq_enable => 
 desc->irq_data.chip->irq_unmask(&desc->irq_data);   => gic_unmask_irq (gic.c 101)  
        备注:
            a.  中断标志位:
              1)IRQF_ONESHOT
    用来标明是在中断线程(底半部)执行完后在打开该中断,该标志非常有用,否则中断有可能一直在顶半执行,而不能处理中断线程。例如对于gpio level中断,如果不设置该位,在顶半执行完成后,会打开中断,此时由于电平没有变化,马上又执行中断,永远没有机会处理线程(底半部)。
http://blog.sina.com.cn/s/blog_752fa65f0100qg7v.html






4.软中断(可延迟函数)
4.1 概述
    1)软中断,是一种将中断服务例程推后执行的机制。
    2)在中断触发流程中,一个中断处理程序(处理一类中断向量)会有多个中断服务例程(每个中断向量都有一个中断服务例程对应),并且这些中断服务例程之间是串行执行的。所以,如果其中一些耗时较长且不紧急中断服务例程在串行执行流的前面,而耗时较短且紧急的中断服务例程在串行执行流的后面,这样就会造成紧急的事件响应过慢。为了解决这个问题,引入软中断的概念,将一部分耗时较长且不紧急中断服务例程推后执行。
    3)软中断的触发有两种形式:
        1.一种是在中断上下文中触发软中断,即在IRQ中断(硬件中断)的中断处理程序结束前触发软中断,此时软中断在中断上下文中。
          asm_do_IRQ()
          -> irq_exit()
            ->invoke_softirq()
              ->__do_softirq(); //软中断服务例程执行
                ->
                    __u32 pending;
                    pending = local_softirq_pending();
                    h = softirq_vec;
                    do {
                        if (pending & 1) {
                            unsigned int vec_nr = h - softirq_vec;
                            h->action(h); //执行软中断服务例程,这时属于中断上下文
                            .....
                            }
                        h++;
                        pending >>= 1;
                    } while (pending);

                    if (pending)
                        wakeup_softirqd(); //当有大量的软中断等待处理时,唤醒软中断守护进程ksoftirqd来执行软中断服务例程,这时属于进程上下文

        2.一种是在进程上下文中触发软中断,在内核中运行着一个软中断守护进程ksoftirqd,用来执行进程上下文中的软中断服务例程。其中进程通过raise_softirq()函数触发软中断。
            raise_softirq(unsigned int nr)
            ->raise_softirq_irqoff(nr);
                ->wakeup_softirqd();

4.2 关键数据结构
    4.2.1 软中断源
        enum
        {
            HI_SOFTIRQ=0,
            TIMER_SOFTIRQ,
            NET_TX_SOFTIRQ,
            NET_RX_SOFTIRQ,
            BLOCK_SOFTIRQ,
            BLOCK_IOPOLL_SOFTIRQ,
            TASKLET_SOFTIRQ,
            SCHED_SOFTIRQ,
            HRTIMER_SOFTIRQ,
            RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

            NR_SOFTIRQS
        };

    4.2.2 struct softirq_action
        概述:管理一个软中断源对应的软中断处理函数
        格式:
            struct softirq_action
            {
                void    (*action)(struct softirq_action *);
            };
            //softirq_vec数组记录着已经注册的软中断源及对应的软中断处理函数
            static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

    4.2.3 irq_cpustat_t
        概述:每个CPU都有一个irq_cpustat_t结构变量,用于记录待处理的软中断(pending)
        格式:
            typedef struct {
                unsigned int __softirq_pending; //__softirq_pending字段中的每一个bit,对应着某一个软中断,某个bit被置位,说明有相应的软中断等待处理。
            } ____cacheline_aligned irq_cpustat_t;
            //irq_stat数组记录着每个CPU的待处理的软中断(pending)
            irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

    4.2.4 ksoftirqd守护进程(内核线程)
        概述:
            1)软中断守护进程,在系统初始化的时候,会为每个CPU创建一个ksoftirqd守护进程,并创建了一个per_cpu变量用于保存每个守护进程的task_struct结构指针
            2)每个ksoftirqd守护进程都运行ksoftirqd()函数。
        格式:DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
            shell@rk3188:/ # ps |grep ksoftirqd
            ps |grep ksoftirqd
            root      3     2     0      0     c047a3b4 00000000 S ksoftirqd/0
            root      9     2     0      0     c047a3b4 00000000 S ksoftirqd/1
            root      12    2     0      0     c047a3b4 00000000 S ksoftirqd/2
            root      15    2     0      0     c047a3b4 00000000 S ksoftirqd/3   

4.3 软中断实例 - tasklet中断
    4.3.1 概述:
        1)tasklet中断,是属于软中断中的一种类别   

    4.3.2 关键数据结构
        1)struct tasklet_struct
            概述:tasklet描述符,描述一个tasklet
            格式:
                struct tasklet_struct
                {
                    struct tasklet_struct *next;
                    unsigned long state; //tasklet的状态,状态值有TASKLET_STATE_SCHED,TASKLET_STATE_RUN
                    //锁计数器,用于tasklet对tasklet_disable和tasklet_enable的计数,count为0时表示允许tasklet执行,否则不允许执行,每次tasklet_disable时,该值加1,tasklet_enable时该值减1。
                    atomic_t count;
                    void (*func)(unsigned long); //tasklet的执行函数(软中断服务例程)
                    unsigned long data; //tasklet执行函数的输入数据
                };   
                enum
                {
                    TASKLET_STATE_SCHED,    /* Tasklet is scheduled for execution */
                    TASKLET_STATE_RUN    /* Tasklet is running (SMP only) */
                };

    4.3.3 执行流程
        1)注册tasklet中断源
            void open_softirq(int nr, void (*action)(struct softirq_action *))
            {
                softirq_vec[nr].action = action;
            }   
            static void tasklet_action(struct softirq_action *a)
            {......
            }
            static void tasklet_hi_action(struct softirq_action *a)
            {......
            }
            static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); //为每个CPU创建一个tasklet_vec数组,用于储存属于TASKLET_SOFTIRQ中断源的tasklet
            static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);//为每个CPU创建一个tasklet_hi_vec数组,用于储存属于HI_SOFTIRQ中断源的tasklet
            void __init softirq_init(void)
            {
                int cpu;
                for_each_possible_cpu(cpu) {
                    per_cpu(tasklet_vec, cpu).tail =&per_cpu(tasklet_vec, cpu).head;
                    per_cpu(tasklet_hi_vec, cpu).tail =&per_cpu(tasklet_hi_vec, cpu).head;
                }

                open_softirq(TASKLET_SOFTIRQ, tasklet_action); //注册TASKLET_SOFTIRQ中断,其软中断处理函数是tasklet_action()
                open_softirq(HI_SOFTIRQ, tasklet_hi_action);  //注册HI_SOFTIRQ中断,其软中断处理函数是tasklet_hi_action()
            }   

        2)初始化tasklet
            方法1:
                void tasklet_init(struct tasklet_struct *t,
                          void (*func)(unsigned long), unsigned long data)
                {
                    t->next = NULL;
                    t->state = 0;
                    atomic_set(&t->count, 0);
                    t->func = func;
                    t->data = data;
                }

                struct tasklet_struct keyboard_tasklet;
                tasklet_init(&keyboard_tasklet,kbd_bh, 0);

            方法2:   
                #define DECLARE_TASKLET_DISABLED(name, func, data) \
                struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
                #define DECLARE_TASKLET(name, func, data) \
                struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

                struct tasklet_struct keyboard_tasklet;
                static void kbd_bh(unsigned long dummy)
                {......
                }
                DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);//初始化keyboard_tasklet,并设置软中断服务例程为kbd_bh(),count置1
                或
                DECLARE_TASKLET(keyboard_tasklet, kbd_bh, 0);//count置0   

        3)使能,禁止tasklet   
            tasklet_disable(&keyboard_tasklet); //通过给count字段加1来禁止一个tasklet,如果tasklet正在运行中,则等待运行完毕才返回(通过TASKLET_STATE_RUN标志)。
            tasklet_disable_nosync(&keyboard_tasklet);  //tasklet_disable的异步版本,它不会等待tasklet运行完毕。
            tasklet_enable(&keyboard_tasklet); //使能tasklet,只是简单地给count字段减1。

        4)调度   
            //如果TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等待执行的tasklet链表上,接着发出TASKLET_SOFTIRQ软件中断请求。
            tasklet_schedule(&keyboard_tasklet);
            //效果同上,区别是它发出的是HI_SOFTIRQ软件中断请求。
            tasklet_hi_schedule(&keyboard_tasklet);

            tasklet_schedule(&keyboard_tasklet);
            ->__tasklet_schedule(t);
                ->raise_softirq_irqoff(TASKLET_SOFTIRQ);

        5)销毁
            //如果tasklet处于TASKLET_STATE_SCHED状态,或者tasklet正在执行,则会等待tasklet执行完毕,然后清除TASKLET_STATE_SCHED状态。
           tasklet_kill(&keyboard_tasklet);





5.软中断(swi)