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}           @ 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]   //如果进入中断前是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

从上面代码中的注释可以看出,根据进入中断前的工作模式不同,程序下一步将跳转到_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:通过中断号来判断
  movne        r1, sp
  adrne lr, 1b
  bne    asm_do_IRQ               //调用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:中断号
{
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_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()来向内核申请注册中断

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

时间: 2024-08-19 03:59:14

5.分析内核中断运行过程,以及中断3大结构体:irq_desc、irq_chip、irqaction的相关文章

linux内核中的hlist_head、list_head、hlist_node结构体

在linux内核中经常会看到这几个结构体: struct list_head; struct hlist_head; struct hlist_node; 在linux内核源代码中对这三个结构体的定义如下: struct list_head { struct list_head *prev; struct list_head *next; } struct hlist_node { struct hlist_node **prev; struct hlist_node *next; } stru

从内存中分析程序的运行过程

我觉得图形是最可以直观一种解释方法,所以先把程序运行过程的图形解析流程给大家,通过图形来一步一步的理解才是最让人清楚,直观的: 流程图懂了,好多事情也就懂了!

linux内核学习之三 跟踪分析内核的启动过程

一   前期准备工作       1 搭建环境 1.1下载内核源代码并编译内核 创建目录,并进入该目录: 下载源码: 解压缩,并进入该目录:xz -d linux-3.18.6.tar.xz tar -xvf linux-3.18.6 cd  linux-3.18.6 选定x86架构的相关文件编译: 编译: 1.2 制作根文件系统 在工作目录下新建一个文件夹: mkdir rootfs 下载老师提供的资料:git clone https://github.com/mengning/menu.gi

通讯录结构体方法的实现 和VS中存在的一些问题的分析

实现一个通讯录: 通讯录可以用来存储1000个人的信息,每个人的信息包括: 姓名.性别.年龄.电话.住址 功能如下: 1.  添加联系人信息 2.  删除指定联系人信息 3.  查找指定联系人信息 4.  修改指定联系人信息 5.  显示所有联系人信息 6.  清空所有联系人 模块化设计: 头文件 结构体和相应函数的定义,声明 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <

OpenCL内核函数支持double和结构体

在opencl开发中,有时需要保证精度,需要支持double类型,但是double类型在opencl标准里面不是要求强制实现的,有些设备支持,有些不支持,如果你的设备支持的话,就需要在所有出现在double的最前面声明如下: #pragma OPENCL EXTENSION cl_khr_fp64: enable 但是这也有一个问题,就是不能保证程序的可移植性,之前在编写地形因子提取算法时,在某些AMD的显卡就不支持. 另外有时候需要支持结构体的话,就只需要定义和主机端一模一样的结构体,然后在C

Linux内核分析 实验三:跟踪分析Linux内核的启动过程

贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 1.打开shell,输入启动指令,内核启动完成后进入menu程序,支持三个命令help.version和quit. 2.然后使用gdb跟踪调试内核,输入命令qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S 3.按住

20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程

回顾 1.中断上下文的切换——保存现场&恢复现场 本节主要课程内容 Linux内核源代码简介 1.打开内核源代码页面 arch/目录:支持不同CPU的源代码:其中的X86是重点 init/目录:内核启动相关的代码基本都在该目录中(比如main.c等) start_kernel函数就相当于普通C程序的main函数 kernel/目录:Linux内核核心代码在kernel目录中 README 介绍了什么是Linux,Linux能够在哪些硬件上运行,如何安装内核源代码等 构造一个简单的linux系统m

Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】

转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html 研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了解内核启动过程.我在研究内核的内存管理的时候,想知道内核启动后的页表的放置,页表的初始化等信息,这促使我这次仔细地研究内核的启动代码. CPU在bootloader的帮助下将内核载入到了内存中,并开始执行.当然,bootloader必须为zImage做好必要的准备:  1. CPU 寄存器的设置: R0

实验三:跟踪分析Linux内核的启动过程

Ubuntu 16.04下搭建MenuOS的过程: 1.下载内核源代码编译内核 1 # 下载内核源代码编译内核 2 cd ~/LinuxKernel/ 3 wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz 4 xz -d linux-3.18.6.tar.xz 5 tar -xvf linux-3.18.6.tar 6 cd linux-3.18.6 7 make i386_defconfig 8 make