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 研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了解内 ...
随机推荐
- Spring-Framework 源码阅读之@Autowired和AutowiredAnnotationBeanPostProcessor
今天接下去讲我们的内容,上次的解析了AnnotationBeanUtils这个类的运用和源码.今天主要关注的是Autowired和 AutowiredAnnotationBeanPostProcess ...
- GPUImage原理
GPUImage是一个开元的基于GPU的图片或视频的处理框架,其本身内置了多达120多种常见的滤镜效果,并且支持照相机和摄像机的实时滤镜,并且能够自定义图像滤镜. 美颜的基本概念 OpenGL ES: ...
- git分支管理之Feature分支
软件开发中,总有无穷无尽的新的功能要不断添加进来. 添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合 ...
- Windows下搭建Git 服务器: BONOBO GIT SERVER + TortoiseGit
本文将介绍如何在Windows操作系统下搭建Git服务器和客户端.服务器端采用的是Bonobo Git Server,一款用ASP.NET MVC开发的Git源代码管理工具,界面简洁,基于Web方式配 ...
- Office远程代码执行漏洞CVE-2017-0199复现
在刚刚结束的BlackHat2017黑帽大会上,最佳客户端安全漏洞奖颁给了CVE-2017-0199漏洞,这个漏洞是Office系列办公软件中的一个逻辑漏洞,和常规的内存破坏型漏洞不同,这类漏洞无需复 ...
- 验证码的Java实现--jsp
<%@ page language="java" pageEncoding="UTF-8" %> <%@ page contentType=& ...
- .NET并行计算和并发2-Foreground and Background Threads
后台线程不会使托管执行环境处于运行状态,除此之外,后台线程与前台线程是一样的. 一旦所有前台线程在托管进程(其中 .exe 文件是托管程序集)中被停止,系统将停止所有后台线程并关闭.
- 原来,负载均衡可以这样用,多核CPU可以这样用
负载均衡作为一个处理高并发,大流量的访问的业务场景,已经几乎是常识性的知识了. 而本文的意义在于需求:由于大流量请求,导致服务无法正常响应,在不增加购买机器成本的场景下,如何提高服务器的业务处理能力? ...
- struts2整合JFreechart 饼图、折线图、柱形图
struts2整合JFreechart 饼图.折线图.柱形图 上效果图: 当然可以将数据导出图片格式存储.具体下的链接里的文件有保存成图片的操作. 因为是strust2整合JFreechart,所以s ...
- MD5 in JAVA
using Apache Commons 需要引入org.apache.commons.codec.digest.DigestUtils这个包,pom.xml文件配置如下: <!-- https ...