arm中断疑惑:
当中断产生时,通过中断向量表
b ResetHandler
b HandlerUndef ;handler for Undefined mode
b HandlerSWI ;handler for SWI interrupt
b HandlerPabort ;handler for PAbort
b HandlerDabort ;handler for DAbort
b . ;reserved
b HandlerIRQ ;handler for IRQ interrupt
b HandlerFIQ ;handler for FIQ interrupt
调转到handlerIRQ,handlerIRQ根据INTOFFSET判断具体是哪个中断,然后进入相应的中断服务程序,
我的疑惑是中断服务程序执行完后,它怎么样回到被打断的地方继续运行,这是一个IRQ的主体程序
IsrIRQ
sub sp,sp,#4 ;reserved for PC
stmfd sp!,{r8-r9}
ldr r9,=INTOFFSET
ldr r9,[r9]
ldr r8,=HandleEINT0
add r8,r8,r9,lsl #2
ldr r8,[r8]
str r8,[sp,#8]
ldmfd sp!,{r8-r9,pc}
注册一个时钟中断程序:
pISR_TIMER0=(unsigned)timer0Handler;
IsrIRQ注册到了0x18的地方
当时钟time0中断产生时,pc先指向IsrIRQ,然后跳转到pISR_TIMER0这个地址执行具体的中断服务程序,运行完了后,该怎么回来?
我查了一下__irq中断程序关键字,但这个只是会保存中断函数的进入现场,和恢复退出现场,它怎么回到user状态下被打断的地方继续运行?
__irq 保存现场,就是保存了PC值以及其他寄存器啊,当你中断服务程序结束时,就自动恢复了,PC值也恢复了啊,也就是回到了你原来的未中断状态,你主程序用C写的吧,那么编译器编译成汇编的时候自动加保存和恢复现场的代码进去的
但是为什么别人都是那样写的啊?肯定有点什么原因吧。。。能不能说一下为什么?
引用: 引用 1 楼 sherlock_lai 的回复:
__irq?保存现场,就是保存了PC值以及其他寄存器啊,当你中断服务程序结束时,就自动恢复了,PC值也恢复了啊,也就是回到了你原来的未中断状态,你主程序用C写的吧,那么编译器编译成汇编的时候自动加保存和恢复现场的代码进去的
哪断你不明白?ARM的中断跳转都是这样的,你这个程序是非向量中断模式,第一次跳转是通过判断寄存器的值,第二次跳转的向量表是pISR_xxxx这些,你的的程序包换的.h文件里肯定有#define pisr_xxxx ... 的语句的。 关于__irq 这个关键字应该是和你的C编译器有关的,如果你用汇编的话,就要用LDR PC,R14 来返回了,记得是这样的,R13,R14记不清楚了,反正是保存PC的那个寄存器
能否留下qq交流一下?
引用: 引用 3 楼 sherlock_lai 的回复:
哪断你不明白?ARM的中断跳转都是这样的,你这个程序是非向量中断模式,第一次跳转是通过判断寄存器的值,第二次跳转的向量表是pISR_xxxx这些,你的的程序包换的.h文件里肯定有#define pisr_xxxx ... 的语句的。 关于__irq 这个关键字应该是和你的C编译器有关的,如果你用汇编的话,就要用LDR PC,R14 来返回了,记得是这样的,R13,R14记不清楚了,反正是保存PC的那个寄存器
当执行中断程序的时候CPU会自动把PC压入堆栈!
执行isrIRQ时候 调用ASM是这个:
stmfd sp!,{r8-r9}
你没发现isrIRQ最后一条指令:
ldmfd sp!,{r8-r9,pc}
上面指令多出个PC,也就是说把堆栈里面的数据放到PC里面,这个数据正是中断前压入堆栈的数据!
PC就变成了以前的PC,所以回到了以前的断点处执行程序!
不是啊ldmfd sp!,{r8-r9,pc}这句话是说转向具体的中断处理函数,而不是返回被中断的地方
在中断处理结束后,返回的时候要用到R14(LR),对IRQ来讲,会用到R14_IRQ来保存返回
地址。
在整个执行过程进入中断处理之前,要保存上下文环境。具体会将下条指令的地址放到R14_IRQ
中,同时将CPSR的值放到SPSR_IRQ中,修改CPSR中的模式位为IRQ,关掉FIQ、IRQ,把跳转地址放
到PC中。从PC中取地址就可以得到中断的处理入口。
在处理完中断服务程序后,将上述的过程反过来,恢复到中断处理前的状态。
具体到指令:
IsrIRQ
sub sp,sp,#4 ;
stmfd sp!,{r8-r9} ;将用到的寄存器压栈
ldr r9,=INTOFFSET ;下面这个不是很清楚
ldr r9,[r9]
ldr r8,=HandleEINT0
add r8,r8,r9,lsl #2
ldr r8,[r8]
str r8,[sp,#8]
ldmfd sp!,{r8-r9,pc}^ ;恢复用到的寄存器中断前的状态
返回的话,就像3楼说的那样,要用到 mov pc,LR LR中保存的就是中断返回地址。
我意思就是具体的中断处理程序(比如我上面说到的timer0Handler中断处理程序)执行完了该返回到哪里?,mov pc,LR 该写在哪里?
IsrIRQ
sub sp,sp,#4 ;reserved for PC
/* 上面这条指令目的是什么?应该是 sub R14,R14,#4 是用来计算offset
* 调整寄存器LR。如果你用SP(R13)的话,只是移动堆栈栈顶指针,对
* PC没有任何影响。
*/
stmfd sp!,{r8-r9}/*保护现场*/
/*下面是你的中断处理过程*/
ldr r9,=INTOFFSET
ldr r9,[r9]
ldr r8,=HandleEINT0
add r8,r8,r9,lsl #2
ldr r8,[r8]
str r8,[sp,#8]
/*恢复现场*/
ldmfd sp!,{r8-r9,pc} /*出栈的话,PC中放什么?stmfd只保存了r8 & r9*/
这段代码中,在进行中断响应时,并没有将LR入栈,而在中断返回时,又需要用到LR,
这就是问题的所在。
解决方案又两个:
(1)
IsrIRQ
sub R14,R14,#4 /* 在进入中断处理之前,R14的地址已经被硬件作了修改。在
* 这里用需要计算一下它的偏移地址。*/
stmfd sp!,{r8-r9,LR}/*保护现场,这里需要将LR入栈。以备返回时候使用。*/
/*下面是你的中断处理过程*/
...
...
/*恢复现场*/
ldmfd sp!,{r8-r9,pc}^/* ^ 这个符号很重要,除了表示正常的数据传输之外,
* 还将SPSR 拷贝到 CPSR中。/
(2)
IsrIRQ
sub R14,R14,#4
stmfd sp!,{r8-r9}/*保护现场,这里需要将LR入栈。以备返回时候使用。*/
/*下面是你的中断处理过程*/
...
...
/*恢复现场*/
ldmfd sp!,{r8-r9}
MOV PC,LR /*中断返回*/
方案(2)有很多隐患。如果有中断嵌套发生,导致R14无效,中断结束后将不能正确返回。
另外CPSR也可能得不到恢复。建议使用方案(1)。
能否留下email或qq交流一下,这个代码好多地方都是这样写的,我也不知道为什么
引用: 引用 10 楼 Cop_007 的回复:
IsrIRQ???
????sub?????sp,sp,#4???????;reserved?for?PC?
????/*?上面这条指令目的是什么?应该是?sub?R14,R14,#4?是用来计算offset
?????*?调整寄存器LR。如果你用SP(R13)的话,只是移动堆栈栈顶指针,对
??????*?PC没有任何影响。
??????*/????
????stmfd???sp!,{r8-r9}/*保护现场*/????
?????/*下面是你的中断处理过程*/
????ldr?????r9,=INTOFFSET?
????ldr?????r9,[r9]?
????ldr?????r8,=HandleEINT…
////////////////////////////////////////////////
当中断产生时,通过中断向量表
b ResetHandler
b HandlerUndef ;handler for Undefined mode
b HandlerSWI ;handler for SWI interrupt
b HandlerPabort ;handler for PAbort
b HandlerDabort ;handler for DAbort
b . ;reserved
b HandlerIRQ ;handler for IRQ interrupt
b HandlerFIQ ;handler for FIQ interrupt
调转到handlerIRQ,handlerIRQ根据INTOFFSET判断具体是哪个中断,然后进入相应的中断服务程序,
我的疑惑是中断服务程序执行完后,它怎么样回到被打断的地方继续运行,这是一个IRQ的主体程序
////////////////////////////////////////////////
IsrIRQ
sub sp,sp,#4 ;reserved for PC 堆栈是递减的,空一个位置
stmfd sp!,{r8-r9} ;保存下面要用到的r8和r9
ldr r9,=INTOFFSET ;获取中断源的偏移量寄存器,三星系列处理器都有32个中断源
ldr r9,[r9] ;得到具体的中断源0--31
ldr r8,=HandleEINT0 ;保存第一个中断源的中断处理函数的地址
add r8,r8,r9,lsl #2 ;每个函数指针四个字节,HandleEINT0 + r9×4,
ldr r8,[r8] ;r8地址处地址保存了对应中断源的中断入口地址,[r8]取出入口地址放在r8中
str r8,[sp,#8] ;将入口地址放在sub sp,sp,#4空出的那个栈位置中
ldmfd sp!,{r8-r9,pc} ;恢复r8和r9,同时将PC指向对应中断源的中断入口地址,也就是下面的timer0Handler
////////////////////////////////////////////////
注册一个时钟中断程序:
pISR_TIMER0=(unsigned)timer0Handler;
当时钟time0中断产生时,pc先指向IsrIRQ,然后跳转到pISR_TIMER0这个地址执行具体的中断服务程序,运行完了后,该怎么回来?
我查了一下__irq中断程序关键字,但这个只是会保存中断函数的进入现场,和恢复退出现场,它怎么回到user状态下被打断的地方继续运行?
////////////////////////////////////////////////
假设timer0Handler函数如下
__irq void timer0Handler(void)
{
C_int_handler(); // process the interrupt
XXXXX// clear the interrupt
}
因为使用了__irq关键字,所以编译器认为其为中断函数,进入时自动将加上保护相关资源的代码
同时退出时将自动恢复相关寄存器
此时所有的寄存器都是IRQ模式下的,且lr保存了user模式下返回地址
timer0Handler
0x000000: STMFD sp!,{r0-r4,r12,lr} ;自动保存BL需要用到的传递参数寄存器和ip(r12)指针
0x000004: BL C_int_handler ;调用相关处理
0x000014: xxxx ;清除中断源
0x00001c: LDMFD sp!,{r0-r4,r12,lr} ;恢复相关寄存器
0x000020: SUBS pc,lr,#4 ;将返回的指令地址减4后,赋值给PC,同时SUBS中断S表示回复CPSR
这样就实现了从中断服务程序返回到user模式下被中断的代码处继续执行
为什么要减4呢?
这和ARM的三级指令流水线相关,当执行到A指令时,此时PC为A指令地址+8,此时A未执行完毕,来了中断,
CPU会自动将PC-4=A+4保存到IRQ模式的LR寄存器中,以便返回,LR=A+4
但目前要返回的地址是A,所以最后pc=lr -4 = A
Cop_007分析的有道理,但那是针对手动保存中断现场的情况
目前lz是利用ARM的关键字生成中断服务程序的,所以那种方法不行
__irq关键字的好处在于从0x18处就可以直接跳转到中断服务函数中,不必写太多汇编代码
但是其不支持可重入中断,即中断不能嵌套
因为在BL C_int_handler 执行过程中若来了中断,将会破坏LR中保存的C_int_handler的返回地址
因此要实现中断嵌套的话,必须手动编写保存现场的代码
在进行函数调用前切换到系统模式运行,这样再来中断时不会破坏系统模式下的LR
可以保证C_int_handler 正确返回
中断服务程序执行完毕后,再切换至IRQ模式,再恢复现场
lz下次提这么复杂的问题最好多搞点分啊
大家都在参予这个帖子但没有得分
这样对不起大家的付出啊
呵呵,其实10分我还觉得少了
5,7,10,14楼,你们越说越扯了,不要误人子弟了,人家12楼说的是正确的
14楼,你看清楚点再发言,人家楼主的代码最后一行跟本没有^号
IsrIRQ
sub sp,sp,#4 ;reserved for PC ;堆栈指针减4,空出一个单元,以便后面放入PC需要的字(看不懂英文吗?)
stmfd sp!,{r8-r9} ;r8,r9入栈,此时存储r8,r9的两个堆栈单元下面的一个单元是空的
ldr r9,=INTOFFSET ;从interrupt offset寄存器读取中断偏移量
ldr r9,[r9]
ldr r8,=HandleEINT0 ;以中断向量0为基地址计算中断向量地址
add r8,r8,r9,lsl #2
ldr r8,[r8]
str r8,[sp,#8] ;把得到的中断向量的内容(timer0Handler的地址)放入最开始空出的堆栈单元,由于这个单元在r8,r9下面,
;所以位置是sp+8
ldmfd sp!,{r8-r9,pc} ;把堆栈顶部的三个单元分别出栈到r8,r9,和pc,此时pc会跳转到中断向量里存储的地址,也就是timer0Handler
以上的过程中LR根本没有被做任何修改,如果楼主的中断服务程序是用C语言写的,并且使用了__irq关键字,那么在进入时候会自动保存LR以及使用到的寄存器,在退出的时候会恢复寄存器,并将LR的值传递到PC,同时把SPSR的内容传递到CPSR,但是这种方法不支持嵌套中断,如果要支持嵌套,在进入的时候要自己保存寄存器,在退出的时候恢复寄存器,同时判断嵌套是否结束,如果未结束,则不恢复CPSR的值,如果嵌套结束,要恢复CPSR的值
还有一点,就是中断后LR中保存的值由于流水线的关系可能不是要返回的地址,如果要自己恢复现场,则需要在将LR装入PC的时候进行修正,如果是用__irq关键字写的C中断服务程序,则不用担心,编译器自动计算的。
可以看下这篇,希望对你有帮助:
http://blog.eeworld.net/libaizhang/archive/2009/07/02/4317141.aspx