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 条评论
登录 后参与评论

相关文章

来自专栏Java Edge

Java内存模型与volatile关键字Java内存模型(JMM)指令重排序对于Long和double型变量的特殊规则内存屏障有序性(Ordering)先行发生原则

35510
来自专栏spring源码深度学习

java基础thread——java5之后的多线程(浅尝辄止)

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个...

681
来自专栏云霄雨霁

设计线程安全的类

1444
来自专栏我的博客

Zend FrameWork之Zend_Db_Table笔记

根据Zend_Db_Table操作数据(也就是在models建立一个对应表的模型) 准备条件: course数据表中有cid课程号,自增,主键,cname课...

3223
来自专栏互联网杂技

丁点而内存知识

在C和C++语言开发中,指针、内存一直是学习的重点。因为C语言作为一种偏底层的中低级语言,提供了大量的内存直接操作的方法,这一方面使程序的灵活度最大化,同时也为...

3144
来自专栏逢魔安全实验室

UAF Writeup - pwnable.kr

0x00 UAF — pwnable.kr是一个韩国的CTF练习的网站,有很多经典的CTF题目供爱好者练习。 UAF(Use After Free)释放后重用...

3356
来自专栏Java Edge

JMM1、基础与概念2、重排序6、锁7、java concurrent包的通用化的实现模式7、final8、双重检查和延迟优化

2839
来自专栏芋道源码1024

【死磕Java并发】—–Java内存模型之happens-before

在上篇博客(【死磕Java并发】—–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性...

2845
来自专栏程序员互动联盟

【编程基础】C语言内存使用的常见问题

所讨论的“内存”主要指(静态)数据区、堆区和栈区空间。数据区内存在程序编译时分配,该内存的生存期为程序的整个运行期间,如全局变量和static关键字所声明的静态...

3275
来自专栏chenssy

【死磕Java并发】—–Java内存模型之happens-before

在上篇博客(【死磕Java并发】—–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性...

3279

扫码关注云+社区