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_\name:                        //定义不同的宏,比如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的实现如下(arch\arm\kernel\entry-armv.S):

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

4.2.irq_handler的实现过程,arch\arm\kernel\entry-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)\n", 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_irqset_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()来向内核申请注册中断

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JMCui

使用 Lombok 优雅编码

    Lombok 是一个 java 库,能以简单的注解形式来简化 java 代码,提高开发人员的开发效率。

642
来自专栏林德熙的博客

win10 uwp listView 绑定前一项 WPF 绑定前一项UWP 绑定前一项

大神问,如何在 ListView 绑定前一项,于是我下面告诉大家如何在 ListView 绑定前一项

431
来自专栏MasiMaro 的技术博文

windows 线程

在windows中进程只是一个容器,用于装载系统资源,它并不执行代码,它是系统资源分配的最小单元,而在进程中执行代码的是线程,线程是轻量级的进程,是代码执行的最...

512
来自专栏DOTNET

.Net多线程编程—同步机制

1.简介 新的轻量级同步原语:Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLoc...

2925
来自专栏javathings

@Async 注解的使用

在 Spring 中,@Async 标注的方法,在执行的时候,是异步运行的,它运行在独立的线程中,程序不会被该方法所阻塞。

1132
来自专栏java达人

Java 8 Stream 教程 (三)

作者:Benjamin 译者:java达人 来源:http://winterbe.com/posts/2014/07/31/java8-stream-tuto...

1926
来自专栏一“技”之长

iOS多线程编程之二——NSOperation与NSOperationQueue

NSOperation是基于Object-C封装的一套管理与执行线程操作的类。这个类是一个抽象类,通常情况下,我们会使用NSInvocationOperatio...

562
来自专栏向治洪

Volley解析之表单提交篇

要实现表单的提交,就要知道表单提交的数据格式是怎么样,这里我从某知名网站抓了一条数据,先来分析别人提交表单的数据格式。  数据包: Connection: ...

2025
来自专栏Java与Android技术栈

基于Kotlin的委托机制实现一个对Extra、SharedPreferences操作的库

本文介绍的库,github地址:https://github.com/fengzhizi715/SAF-Object-Delegate

973
来自专栏编程之旅

iOS开发——GCD在Swift中的变脸

Xcode8正式发布后,Swift3也随即发布,为了跟上苹果这艘大船的脚步,赶紧逼着自己看文档哦。在看文档的过程中,发现GCD的变化跟OC相比简直都要不认识了,...

1232

扫码关注云+社区