• 5.分析内核中断运行过程,以及中断3大结构体:irq_desc、irq_chip、irqaction(详解)


    本节目标:

       分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc、irq_chip、irqaction

    在裸板程序中(参考stmdb和ldmia详解):

    1.按键按下,

    2.cpu发生中断,

    3.强制跳到异常向量入口执行(0x18中断地址处)

    3.1使用stmdb将寄存器值保存在栈顶(保护现场)

    stmdb sp!, { r0-r12,lr }

    3.2执行中断服务函数

    3.3 使用ldmia将栈顶处数据读出到寄存器中,并使pc=lr(恢复现场)

    ldmia  sp!, { r0-r12,pc }^
    
    //^表示将spsr的值复制到cpsr,因为异常返回后需要恢复异常发生前的工作状态

    在linux中:

    需要先设置异常向量地址(参考linux应用手册P412):

    在ARM裸板中异常向量基地址是0x00000000,如下图:

     

    而linux内核中异常向量基地址是0xffff0000(虚拟地址),

    位于代码arch/cam/kernel/traps.c,代码如下:

    void __init trap_init(void)
    {         
    /* CONFIG_VECTORS_BASE :内核配置项,在.config文件中,设置的是0Xffff0000*/
    /* vectors =0xffff0000*/
    unsigned long vectors = CONFIG_VECTORS_BASE;
    ... ...
     
    /*将异常向量地址复制到0xffff0000处*/ 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); ... ... }

    上面代码中主要是将__vectors_end - __vectors_start之间的代码复制到vectors (0xffff0000)处,

    __vectors_start为什么是异常向量基地址?

    通过搜索,找到它在arch/arm/kernel/entry_armv.S中定义:

    __vectors_start:
             swi    SYS_ERROR0                      //复位异常,复位时会执行
             b       vector_und + stubs_offset              //undefine未定义指令异常
             ldr     pc, .LCvswi + stubs_offset             //swi软件中断异常 
             b       vector_pabt + stubs_offset             //指令预取中止abort
             b       vector_dabt + stubs_offset             //数据访问中止abort
             b       vector_addrexcptn + stubs_offset       //没有用到
             b       vector_irq + stubs_offset             //irq异常
             b       vector_fiq + stubs_offset            //fig异常

    其中stubs_offset是链接地址的偏移地址, vector_und、vector_pabt等表示要跳转去执行的代码

    1.以vector_irq中断为例, vector_irq是个宏,它在哪里定义呢?

    它还是在arch/arm/kernel/entry_armv.S中定义,如下所示:

    vector_stub  irq, IRQ_MODE, 4//irq:名字  IRQ_MODE:0X12    4:偏移量

    上面的vector_stub  根据参数irq, IRQ_MODE, 4来定义” vector_ irq”这个宏(其它宏也是这样定义的)

    2.vector_stub又是怎么实现出来的定义不同的宏呢?

    我们找到vector_stub这个定义:

    .macro    vector_stub, name, mode, correction=0  //定义vector_stub有3个参数
    .align      5
    vector_
    ame:                        //定义不同的宏,比如vector_ irq
             .if correction                //判断correction参数是否为0
             sub    lr, lr, #correction         //计算返回地址
             .endif
    @ @ Save r0, lr_
    <exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr //读出spsr str lr, [sp, #8] @ save spsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ 进入管理模式 mrs r0, cpsr //读出cpsr eor r0, r0, #(mode ^ SVC_MODE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f //lr等于进入模式之前的spsr,&0X0F就等于模式位 mov r0, sp ldr lr, [pc, lr, lsl #2] movs pc, lr @ branch to handler in SVC mode

    3.因此我们将上面__vectors_start里的b  vector_irq + stubs_offset 中断展开如下:

    .macro    vector_stub, name, mode, correction=0  //定义vector_stub有3个参数
    .align      5
      vector_stub  irq, IRQ_MODE, 4   //这三个参数值代入 vector_stub中
    
    vector_ irq:                   //定义 vector_ irq
      /*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */
             sub    lr, lr, #4             
            
             @
             @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
             @ (parent CPSR)
             @保存r0和lr和spsr
             stmia sp, {r0, lr}               //存入sp栈里  
             mrs   lr, spsr                       //读出spsr
             str     lr, [sp, #8]           @ save spsr
    
     
    
             @
             @ Prepare for SVC32 mode.  IRQs remain disabled.
             @ 进入管理模式
             mrs   r0, cpsr                    //读出cpsr
             eor    r0, r0, #(mode ^ SVC_MODE)  
             msr   spsr_cxsf, r0
    
             @
             @ the branch table must immediately follow this code
             @
             and    lr, lr, #0x0f    //lr等于进入模式之前的spsr,&0X0F就等于模式位
             mov  r0, sp
             ldr     lr, [pc, lr, lsl #2]   //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc
    
             movs pc, lr                    //跳转到下面某处,且目标寄存器是pc,指令S结尾,最后会恢复cpsr.
         
             .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)
             .long __irq_invalid                          @  4
             .long __irq_invalid                          @  5
             .long __irq_invalid                          @  6
             .long __irq_invalid                          @  7
             .long __irq_invalid                          @  8
             .long __irq_invalid                          @  9
             .long __irq_invalid                          @  a
             .long __irq_invalid                          @  b
             .long __irq_invalid                          @  c
             .long __irq_invalid                          @  d
             .long __irq_invalid                          @  e
             .long __irq_invalid                          @  f

    从上面代码中的注释可以看出:

    • 1).将发生异常前的各个寄存器值保存在SP栈里,若是中断异常,则PC=PC-4,也就是CPU下个要运行的位置处
    • 2).然后根据进入中断前的工作模式不同,程序下一步将跳转到_irq_usr 、或__irq_svc等位置。

    4.我们先选择__irq_usr作为下一步跟踪的目标:

    4.1其中__irq_usr的实现如下(archarmkernelentry-armv.S):

    __irq_usr:
      usr_entry                     //保存数据到栈里
      get_thread_info tsk 
      irq_handler                     //调用irq_handler
      b ret_to_user

    4.2.irq_handler的实现过程,archarmkernelentry-armv.S

    .macro  irq_handler
      get_irqnr_preamble r5, lr                
      get_irqnr_and_base r0, r6, r5, lr         // get_irqnr_and_base:获取中断号,r0=中断号
      movne        r1, sp                 //r1等于sp  (发生中断之前的各个寄存器的基地址)            
      adrne lr, 1b
      bne    asm_do_IRQ                   //调用asm_do_IRQ, irq=r0   regs=r1

    irq_handler最终调用asm_do_IRQ

    4.3 asm_do_IRQ实现过程,arch/arm/kernel/irq.c

    该函数和裸板中断处理一样的,完成3件事情:

    1).分辨是哪个中断;

    2).通过desc_handle_irq(irq, desc)调用对应的中断处理函数;

    3).清中断

     asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)  //irq:中断号   *regs:发生中断前的各个寄存器基地址
    {
    struct pt_regs *old_regs = set_irq_regs(regs);
    /*根据irq中断号,找到哪个中断, *desc =irq_desc[irq]*/
    struct irq_desc *desc = irq_desc + irq; // irq_desc是个数组(位于kernel/irq/handle.c)
     
    
    if (irq >= NR_IRQS) 
    desc = &bad_irq_desc;
     
    
    irq_enter();    
    desc_handle_irq(irq, desc);     // desc_handle_irq根据中断号和desc,调用函数指针,进入中断处理,
    
     
    irq_finish(irq);
    irq_exit();
    set_irq_regs(old_regs);
    
    }

    上面主要是执行desc_handle_irq函数进入中断处理

    其中desc_handle_irq代码如下:

    desc->handle_irq(irq, desc);//相当于执行irq_desc[irq]-> handle_irq(irq, irq_desc[irq]);

    它会执行handle_irq成员函数,这个成员handle_irq又是在哪里被赋值的?

    搜索handle_irq,找到它位于kernel/irq/chip.c,__set_irq_handler函数下:

    void  __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)
    {
      ... ...
      desc = irq_desc + irq;      //在irq_desc结构体数组中找到对应的中断
      ... ...
      desc->handle_irq = handle;  //使handle_irq成员指向handle参数函数
    }

    继续搜索__set_irq_handler函数,它被set_irq_handler函数调用:

    static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
    {
             __set_irq_handler(irq, handle, 0, NULL);
    }

    继续搜索set_irq_handler函数,如下图

    发现它在s3c24xx_init_irq(void)函数中被多次使用,显然在中断初始化时,多次进入__set_irq_handler函数,并在irq_desc数组中构造了很多项 handle_irq函数

    我们来看看irq_desc中断描述结构体到底有什么内容:

    struct irq_desc {
             irq_flow_handler_t       handle_irq;  //指向中断函数, 中断产生后,就会执行这个handle_irq
             struct irq_chip   *chip; //指向irq_chip结构体,用于底层的硬件访问,下面会介绍
             struct msi_desc             *msi_desc; 
             void                     *handler_data;  
             void                     *chip_data;
             struct irqaction     *action;      /* IRQ action list */   //action链表,用于中断处理函数
             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;          
         ... ...
             const char            *name;              //产生中断的硬件名字
    } ;

    其中的成员*chip的结构体,用于底层的硬件访问, irq_chip类型如下:

    struct irq_chip {
             const char   *name;
             unsigned int    (*startup)(unsigned int irq);       //启动中断 
             void            (*shutdown)(unsigned int irq);      //关闭中断
             void            (*enable)(unsigned int irq);         //使能中断
             void            (*disable)(unsigned int irq);        //禁止中断
             void            (*ack)(unsigned int irq);       //响应中断,就是清除当前中断使得可以再接收下个中断
             void            (*mask)(unsigned int irq);     //屏蔽中断源 
             void            (*mask_ack)(unsigned int irq);  //屏蔽和响应中断
             void            (*unmask)(unsigned int irq);   //开启中断源
             ... ...
         int              (*set_type)(unsigned int irq, unsigned int flow_type);  //将对应的引脚设置为中断类型的引脚
         ... ...
    #ifdef CONFIG_IRQ_RELEASE_METHOD
             void            (*release)(unsigned int irq, void *dev_id);       //释放中断服务函数
    #endif
    
    };

    其中的成员struct irqaction  *action,主要是用来存用户注册的中断处理函数,

    一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断.

    所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。

    所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断

     irqaction结构定义如下:

    struct irqaction {
             irq_handler_t handler;      //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数
             unsigned long flags;         //中断标志,注册时设置,比如上升沿中断,下降沿中断等
             cpumask_t mask;           //中断掩码
             const char *name;          //中断名称,产生中断的硬件的名字
             void *dev_id;              //设备id
             struct irqaction *next;        //指向下一个成员
             int irq;                    //中断号,
             struct proc_dir_entry *dir;    //指向IRQn相关的/proc/irq/
    
    };

    上面3个结构体的关系如下图所示:

     

    我们来看看s3c24xx_init_irq()函数是怎么初始化中断的,以外部中断0为例(位于s3c24xx_init_irq函数):

    s3c24xx_init_irq()函数中部分代码如下:

    /*其中IRQ_EINT0=16, 所以irqno=16 */
    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) 
    { irqdbf(
    "registering irq %d (ext int) ", irqno);
    /*在set_irq_chip函数中会执行: desc = irq_desc + irq; desc->chip = chip;*/ set_irq_chip(irqno, &s3c_irq_eint0t4); //所以(irq_desc+16)->chip= &s3c_irq_eint0t4 /* set_irq_handler 会调用__set_irq_handler 函数*/ set_irq_handler(irqno, handle_edge_irq); //所以(irq_desc+16)-> handle_irq = handle_edge_irq

    set_irq_flags(irqno, IRQF_VALID); }

    初始化了外部中断0后,当外部中断0触发,就会进入我们之前分析的asm_do_IRQ函数中,调用(irq_desc+16)-> handle_irq也就是handle_edge_irq函数。

    我们来分析下handle_edge_irq函数是如何执行中断服务的:

    void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
    {
             const unsigned int cpu = smp_processor_id();
             spin_lock(&desc->lock);
             desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
    
    
       /*判断这个中断是否正在运行(INPROGRESS)或者禁止(DISABLED)*/
         if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || !desc->action)) 
        { desc
    ->status |= (IRQ_PENDING | IRQ_MASKED); mask_ack_irq(desc, irq); //屏蔽中断 goto out_unlock; } kstat_cpu(cpu).irqs[irq]++; //计数中断次数
    /* Start handling the irq */ desc->chip->ack(irq); //开始处理这个中断 /* Mark the IRQ currently in progress.*/ desc->status |= IRQ_INPROGRESS; //标记当前中断正在运行 do { struct irqaction *action = desc->action; irqreturn_t action_ret; if (unlikely(!action)) { //判断链表是否为空 desc->chip->mask(irq); goto out_unlock; } if (unlikely((desc->status & (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) == (IRQ_PENDING | IRQ_MASKED))) { desc->chip->unmask(irq); desc->status &= ~IRQ_MASKED; } desc->status &= ~IRQ_PENDING; spin_unlock(&desc->lock); action_ret = handle_IRQ_event(irq, action); //真正的处理过程 if (!noirqdebug) note_interrupt(irq, desc, action_ret); spin_lock(&desc->lock); } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); desc->status &= ~IRQ_INPROGRESS; out_unlock: spin_unlock(&desc->lock); }

    上面handle_edge_irq()函数主要执行了:

    1.  desc->chip->ack(irq);    //开始处理这个中断

    在s3c24xx_init_irq()函数中chip成员指向了s3c_irq_eint0t4(),

    所以desc->chip->ack(irq)就是执行handle_edge_irq(irq)函数,handle_edge_irq函数如下:

    s3c_irq_ack(unsigned int irqno)
    {
             unsigned long bitval = 1UL << (irqno - IRQ_EINT0); 
             __raw_writel(bitval, S3C2410_SRCPND);    //向SRCPND寄存器写入bitval ,清SRCPND中断
             __raw_writel(bitval, S3C2410_INTPND);   //向INTPND寄存器位写入bitval ,清INTPND中断
    }

    所以desc->chip->ack(irq); 主要执行清中断之类的

    2.handle_IRQ_event(irq, action);   //真正的处理过程

    handle_IRQ_event()代码如下:

    handle_IRQ_event(unsigned int irq, struct irqaction *action)
    {
             irqreturn_t ret, retval = IRQ_NONE;
             unsigned int status = 0;
             handle_dynamic_tick(action);
             if (!(action->flags & IRQF_DISABLED))
                       local_irq_enable_in_hardirq();
             do {
                       ret = action->handler(irq, action->dev_id);      //执行action->handler
                       if (ret == IRQ_HANDLED)
                                status |= action->flags;
                       retval |= ret;
                       action = action->next;    //指向下个action成员
             } while (action);          //取出action所有成员
    
    
             if (status & IRQF_SAMPLE_RANDOM)
                       add_interrupt_randomness(irq);
             local_irq_disable();
             return retval;
    
    }

    所以handle_IRQ_event()函数主要是取出action链表中的成员,然后执行irq_desc->action->handler(irq, action->dev_id);

    action链表是irq_desc中断描述符结构体的 成员

    本节常用函数总结:

    trap_init(): 初始化异常向量的虚拟基地址,一般为0XFFFF0000

    s3c24xx_init_irq():初始化各个中断

    set_irq_chip(irqno, &s3c_irq_eint0t4):设置irq_desc[irqno]->chip等于第二个参数

    set_irq_handler(irqno, handle_edge_irq); 设置irq_desc[irqno]->handle_irq等于第二个参数

    asm_do_IRQ():中断产生后,会进入这个函数,最终执行 desc->handle_irq(irq, desc);

    handle_edge_irq(irq, desc):执行中断函数,主要是执行以下两步骤:

    (1) desc->chip->ack(irq):相应中断,也就是清中断,使能再次接受中断

    (2) handle_IRQ_event(irq, action):执行中断的服务函数,desc->action->handler

     

    中断运行总结:

    当产生一个中断异常

    1.进入异常向量vector,比如中断异常:  vector_irq + stubs_offset

    2.比如中断异常之前是用户模式(正常工作),则进入 __irq_usr,然后最终进入asm_do_IRQ函数,

    3.然后执行irq_desc [irq]->handle_irq(irq, irq_desc [irq]);        

    通过刚才的分析,外部中断0(irq_desc[16])的handle_irq成员等于handle_edge_irq函数,

    所以就是执行handle_edge_irq(irq, irq_desc [irq]);

    4.以外部中断0为例,在handle_edge_irq函数中主要执行两步:

      ->4.1 desc->chip->ack    //使用chip成员中的ack函数来清中断

      ->4.2  执行action链表 irq_desc->action->handler

    这4步都是系统给做好的(中断的框架),当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断

    中断运行分析完毕后,接下来开始分析如何通过函数来注册卸载中断

     

     

     

     

     

     

     

     

     

     

                                  

     

  • 相关阅读:
    面向对象程序设计第五次作业
    C++第一次作业
    面向对象程序设计第四次作业
    面向对象程序设计第三次作业
    C++自学笔记(3)
    C++自学随笔(2)
    软件测试2019:第七次作业—— 用户体验测试
    软件测试2019:第八次作业—— 缺陷管理(含缺陷管理工具的配置实验)
    软件测试2019:第六次作业—— Web功能测试(含Selenium IDE实验)
    软件测试2019:第五次作业—— 安全测试(含安全测试工具实验)
  • 原文地址:https://www.cnblogs.com/lifexy/p/7506504.html
Copyright © 2020-2023  润新知