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, //irq:名字 IRQ_MODE:0X12 4:偏移量
上面的vector_stub 根据参数irq, IRQ_MODE, 4来定义” vector_ irq”这个宏(其它宏也是这样定义的)
2.vector_stub又是怎么实现出来的定义不同的宏呢?
我们找到vector_stub这个定义:
.macro vector_stub, name, mode, correction= //定义vector_stub有3个参数
.align
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, #] @ 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 #]
movs pc, lr @ branch to handler in SVC mode
3.因此我们将上面__vectors_start里的b vector_irq + stubs_offset 中断展开如下:
.macro vector_stub, name, mode, correction= //定义vector_stub有3个参数
.align
vector_stub irq, IRQ_MODE, //这三个参数值代入 vector_stub中 vector_ irq: //定义 vector_ irq
/*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */
sub lr, lr, # @
@ 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, #] @ 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 #] //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc movs pc, lr //跳转到下面某处,且目标寄存器是pc,指令S结尾,最后会恢复cpsr. .long __irq_usr @ (USR_26 / USR_32)
.long __irq_invalid @ (FIQ_26 / FIQ_32)
.long __irq_invalid @ (IRQ_26 / IRQ_32)
.long __irq_svc @ (SVC_26 / SVC_32)
.long __irq_invalid @
.long __irq_invalid @
.long __irq_invalid @
.long __irq_invalid @
.long __irq_invalid @
.long __irq_invalid @
.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, , 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 = ;
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()来向内核申请注册中断
中断运行分析完毕后,接下来开始分析如何通过函数来注册卸载中断
5.分析内核中断运行过程,以及中断3大结构体:irq_desc、irq_chip、irqaction的更多相关文章
- Linux第三周——跟踪分析内核的启动过程
跟踪分析内核的启动过程实验 张潇月<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 这周主要学习的是对内核 ...
- nand烧写分析/内核在启动过程中式如何将这个文件映射成/目录及各子目录的?
我用的是ramdisk.image.gz,烧写在flash的0x10140000处 我不太明白内核在启动过程中式如何将这个文件映射成/目录及各子目录的? 如果ramdisk.image.gz在flas ...
- linux内核学习之三 跟踪分析内核的启动过程
一 前期准备工作 1 搭建环境 1.1下载内核源代码并编译内核 创建目录,并进入该目录: 下载源码: 解压缩,并进入该目录:xz -d linux-3.18.6.tar.xz tar ...
- Java程序员必了解的JVM原理以及虚拟机的运行过程
JVM概念 虚拟机:指以软件的方式模拟具有完整硬件,VM概念 虚拟机:指以软件的方式模拟具有完整硬件系统功能.运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现.常用的虚拟机有VMWare ...
- 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写
一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...
- 通过gdb调试分析Linux内核的启动过程
作者:吴乐 山东师范大学 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验流程 1.打开环境 执 ...
- 跟踪分析Linux内核的启动过程--实验报告 分析 及知识重点
跟踪分析Linux内核的启动过程 攥写人:杨光 学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.stud ...
- Linux内核分析 实验三:跟踪分析Linux内核的启动过程
贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...
- Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】
转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html 研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了解内 ...
随机推荐
- NodeJS学习目录
前面的话 几年前,对于学习NodeJS可能还有所迟疑,怕分散了前端学习的精力.但到了现在,如果不学习nodeJS,前端的学习却可能无法再有所进展.技术的进步就是这么残酷.对新技术观望的时候,该技术已经 ...
- 最大流当前弧优化Dinic模板
最大流模板: 普通最大流 无向图限制:将无向图的边拆成2条方向相反的边 无源汇点有最小流限制的最大流:理解为水管流量形成循环,每根水管有流量限制,并且流入量等于流出量 有源汇点的最小流限制的最大流 顶 ...
- ue4 C++ 导入图片
void SDrawHouseTypeTools::OnButtonClickLoadImage() { // 当前如果是在硬装模块,则可进行导入户型操作 FHardEdM ...
- NYOJ--1236--挑战密室(第八届河南省程序设计大赛)
挑战密室 时间限制:1000 ms | 内存限制:65535 KB 难度:4 描述 R组织的特工Dr. Kong 为了寻找丢失的超体元素,不幸陷入WTO密室.Dr. Kong必须尽快找到解锁密 ...
- 关于IE,Chrome,Firefox浏览器的字符串拼接问题
昨天项目测试的时候,IE8.IE11测试勾选checkbox然后执行保存的时候,竟然执行的结果与预期相反,吓屎我了,最终排查之下,原来是拼接checkbox的值的时候出现的问题.本人对js了解知之甚少 ...
- C++算法接口使用参考
C++算法接口参考 算法参考:[algorithm] 编译:g++ -std=c++11 xxx_algorithm.cpp 运行:./a.out 1.保持原序列运算 all_of template ...
- 【leetcode】61. Rotate List
Given a list, rotate the list to the right by k places, where k is non-negative. For example:Given 1 ...
- 解析Linux中的VFS文件系统之文件系统的来源与简介(一)
最近挂载了N多的文件系统,大致了不同文件系统的相应特性及挂载方式,却还是对Linux的文件系统没有从源码方面去了解.不求甚解确实不好不好. 于是借鉴一些大牛的博客及自己的理解,总结了博客系列: 一.V ...
- 第5天:js-系统时间对象
今天听了一小节,js中如何获取系统时间.最后做了一个轮播图效果,是在别人的代码上修改的,一开始做了好半天,都没做好,最后,在我的坚持下,终于做出来了.今天先到这儿,去休息了! 获取时间对象:new D ...
- Fedora删除旧版本内核
由于Fedora升级非常快,因此非常有必要清除陈旧的内核,否则的话,每次登录系统会出现多个引导项,看着烦人也占用内存: 1.查看当前使用的内核 [root@w ~]# uname -r 4.9.14- ...