免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2953 | 回复: 5
打印 上一主题 下一主题

[中断] 关于中断返回地址的一个问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2014-08-08 12:56 |只看该作者 |倒序浏览
大家好,我有一个关于中断返回地址的问题:

对于ARM处理器,当发生中断的时候,硬件会进行一系列的动作,具体如下:
1、修改CPSR(Current Program Status Register)寄存器中的M[4:0]。M[4:0]表示了ARM处理器当前处于的模式( processor modes)。对于中断,应该对应IRQ mode
2、保存发生中断那一点的CPSR值(step 1之前的状态,用SPSR_irq保存)和PC值(保存在lr_irq寄存器中)。对于thumb state,lr_irq = PC,对于ARM state,lr_irq = PC - 4
3、mask IRQ exception。也就是设定CPSR.I = 1
4、设定PC值为IRQ exception vector。

HW在保存PC值的时候为何要减去4?我的理解是这样的(不一定对)。由于ARM采用流水线结构,当CPU正在执行某一条指令的时候,其实取指的动作早就执行了,这时候PC值=正在执行的指令地址 + 8。一旦发生了中断,当前正在执行的指令当然要执行完毕,但是已经完成取指、译码的指令则终止执行,但无论如何,PC值其实是比正在执行的指令超前的。减去4只是让返回地址设定为发生中断那条指令的下一条指令。

硬件动作之后,轮到软件上场了(代码位于linux/arch/arm/kernel/entry-armv.S,我看的是3.14内核):
异常向量表如下:
__vectors_start:
        W(b)        vector_rst
        W(b)        vector_und
        W(ldr)        pc, __vectors_start + 0x1000
        W(b)        vector_pabt
        W(b)        vector_dabt
        W(b)        vector_addrexcptn
        W(b)        vector_irq
        W(b)        vector_fiq

对于中断,代码会跳转到vector_irq,具体的代码如下:
/*
* Interrupt dispatcher
*/
        vector_stub        irq, IRQ_MODE, 4----这里的参数是4,为何要减去4?????

        .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

vector_stub是一个宏定义,展开后就是vector_irq符号。vector_stub的代码如下:
        .macro        vector_stub, name, mode, correction=0
        .align        5

vector_\name:
        .if \correction
        sub        lr, lr, #\correction----------(A)
        .endif

        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
        stmia        sp, {r0, lr}                @ save r0, lr
        mrs        lr, spsr
        str        lr, [sp, #8]                @ save spsr

        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs        r0, cpsr
        eor        r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
        msr        spsr_cxsf, r0

        @
        @ the branch table must immediately follow this code
        @
        and        lr, lr, #0x0f
THUMB(        adr        r0, 1f                        )
THUMB(        ldr        lr, [r0, lr, lsl #2]        )
        mov        r0, sp
ARM(        ldr        lr, [pc, lr, lsl #2]        )
        movs        pc, lr                        @ branch to handler in SVC mode
ENDPROC(vector_\name)

问题来了,上面程序中的(A)又一次调整了保存的PC值,又一次减去了4,如果这里减去4,那么这时候lr中保存的就是发生中断点的PC值了,但是,对于中断,我们期望返回的不是发生中断那一点的指令,我猜想调整到中断向量之前,发生中断那一点的指令已经被执行完毕,因此返回现场的时候应该回到下一条指令(也就是PC+4)就ok了,我到底哪个地方搞错了呢?

论坛徽章:
1
拜羊年徽章
日期:2015-03-03 16:15:43
2 [报告]
发表于 2014-08-08 19:20 |只看该作者
你用的是哪一款cpu, arm core是哪一版的?

论坛徽章:
0
3 [报告]
发表于 2014-08-08 23:44 |只看该作者
和具体哪一种ARM core可能关系不大,因为这段中断处理的代码适合所有的ARM core。

论坛徽章:
0
4 [报告]
发表于 2014-08-08 23:47 |只看该作者
补充一下,上面关于ARM 硬件中断处理部分的内容我是参考了ARMV7的reference manual。我相信其他的也是应该类似的。

论坛徽章:
9
辰龙
日期:2014-08-18 20:38:42未羊
日期:2014-09-04 08:50:45丑牛
日期:2014-09-06 00:12:55寅虎
日期:2014-12-22 20:50:56摩羯座
日期:2015-01-14 22:28:15巳蛇
日期:2015-01-23 20:39:272015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之青岛
日期:2016-03-13 23:37:1915-16赛季CBA联赛之深圳
日期:2016-03-29 18:52:38
5 [报告]
发表于 2014-08-09 00:02 |只看该作者
回复 1# wowotech
ARM不同异常类型返回地址是不一样的,LR并不一定的返回的地址。

详情请参考http://blog.chinaunix.net/uid-20937170-id-3220124.html。为了方便你大家阅读,我拷贝过来了。

ARM中支持7种异常,其中包括复位、未定义指令异常、软中断异常、预取指令中止、数据中止、IRQ、IFQ。每一种异常运行在特定的处理器模式下。我在此逐一的分析。
一般异常发生后,CPU都会进行一系列的操作,这些操作有一部分是CPU自动完成,有一部分是需要我们程序员完成。
首先说明CPU会自动完成的部分,用ARM结构手册中的代码描述如下:
R14_<exception_mode> = return link                //这个可以参看寄存器的说明,两个作用
SPSR_< exception_mode > = CPSR
CPSR[4:0] = exception mode number
CPSR[5] = 0 ;                      //AEM指令
If <exception_mode>==Reset or Fiq then    //只有在复位和FIQ模式下才会关闭FIQ中断
CPSR[6] = 1 ;
CPSR[7] = 1 ;                             //任何异常模式下都会关闭IRQ中断
PC = exception vector address
从上面的代码中我们可以发现CPU自动处理的过程包括如下:
1、  拷贝CPSR到SPSR_<mode>
2、  设置适当的CPSR位: 改变处理器状态进入ARM状态;改变处理器模式进入相应的异常模式;设置中断禁止位禁止相应中断。
3、  更新LR_<mode>,这个寄存器中保存的是异常返回时的链接地址
4、  设置PC到相应的异常向量
以上的操作都是CPU自动完成,异常的向量表如下:

返回地址问题
异常的返回地址也是需要我们注意的地方,不同的异常模式返回地址也是存在差异的,这主要是因为各种异常产生的机理存在差别所导致的。这样我们的需要在异常进入处理函数之前或者在返回时调整返回地址,一般采用进入异常处理函数前进行手动调整。下面每一种异常R14保存的值都给了出来,其中也包含了CPU自动处理的部分,根据保存的R14就可以知道怎样实现地址的返回。
复位异常:

可以看出该模式下的先对来说返回地址也比较简单,不需要做太多的描述。
未定义的指令异常:

返回的方式也比较简单:
       MOVS  PC, R14
软中断异常:

返回的方式也比较简单:
       MOVS  PC, R14
预取指令中止异常:

返回需要做下面的调整:
SUBS      PC, R14, #4
数据中止

返回地址需要做下面的调整:
如果需要重新访问数据则:
SUBS      PC, R14, #8
如果不需要重新访问数据则:
SUBS      PC, R14, #4
IRQ中断的处理过程:

返回地址需要做下面的调整:
       SUBS PC,R14,#4
IFQ中断:

返回地址需要做下面的调整:
       SUBS  PC, R14 ,#4
从上面的代码可以知道,对于每一种异常,保存的返回地址都是不一样的,一般都需要我们手动的跳转,当然调整的时机也需要我们选择,是在进入处理前跳转还是返回时调整都是需要我们程序员控制的。
在ARM Developer Suite Developer Guide中对ARM处理器的异常处理操作提供能更加详细的解释,每一种异常下的处理方式如下文描述:
异常返回时另一个非常重要的问题是返回地址的确定,在前面曾提到进入异常时处理器会有一个保存LR 的动作,但是该保存值并不一定是正确的返回地址,下面以一个简单的指令执行流水状态图来对此加以说明。

我们知道在ARM 架构里,PC值指向当前执行指令的地址加8处,也就是说, 当执行指令A(地址0x8000)时,PC 等于指令C 的地址(0x8008)。假如指令A 是“BL”指令,则当执行该指令时,会把PC(=0x8008)保存到LR 寄存器里面,但是接下去处理器会马上对LR 进行一个自动的调整动作:LR=LR-0x4。这样,最终保存在 LR 里面的是 B 指令的地址,所以当从 BL 返回时,LR 里面正好是正确的返回地址。同样的调整机制在所有LR自动保存操作中都存在,比如进入中断响应时,处理器所做的LR 保存中,也进行了一次自动调整,并且调整动作都是LR=LR-0x4。
下面,我们对不同类型的异常的返回地址依次进行说明:
假设在指令A 处(地址0x8000)发生了异常,进入异常响应后,LR 上经过调整保存的地址值应该是B 的地址0x8004。
1、 如果发生的是软件中断,即A 是“SWI”指令
异常是由指令本身引起的,从 SWI 中断返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。
MOVS pc, lr
2、 发生的是Undefined instruction异常
异常是由指令本身引起的,从异常返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。
MOVS pc, lr
3、 发生的是IRQ或FIQ中断
因为指令不可能被中断打断,所以A指令执行完以后才能响应中断,此时PC已更新,指向指令D的地址(地址0x800C),LR 上经过调整保存的地址值是C 的地址0x8008。中断返回后应该执行B指令,所以返回操作是:
SUBS pc, lr, #4
4、 发生的是Prefetch Abort异常
该异常并不是处理器试图从一个非法地址取指令时触发,取出的指令只是被标记为非法,按正常处理流程放在流水线上,在执行阶段触发Prefetch Abort异常,此时LR 上经过调整保存的地址值是B 的地址0x8004。异常返回应该返回到A指令,尝试重新取指令,所以返回操作是:
SUBS pc, lr, #4
5、 发生的是“Data Abort”
CPU访问存储器时触发该异常,此时PC指向指令D的地址(地址0x800C),LR 上经过调整保存的地址值是C 的地址0x8008。异常返回后,应回到指令A,尝试重新操作存储器,所以返回操作是:
SUBS pc, lr, #8
以上就是ARM异常的CPU操作部分,接下来就是程序员应该完成的操作。
1.         由于CPU会自动跳转到对应的异常向量中,因此只需要在在各个异常向量中存放对应的操作,最简单的都是存放一个B指令跳转到对应的异常处理函数的操作即可。但由于B指令的跳转返回只有+-32M,而异常处理函数的地址可能会超过+-32M,因此可以采用另一种方式实现方式:在异常向量中保存一条指令LDR PC [addr],其中的addr中就保存了异常处理函数的地址,当然addr的相对地址要小于+-32M。这样也就解决了跳转范围的问题。
2.         接下来就是异常处理函数对应的操作,可以在进入异常处理之前就进行返回地址的调整,这样后面就不用进行处理啦,当然也可以在返回过程中再调整。一般都是在这个过程中进行调整。进行压栈操作,保存对应的环境变量。调用实际的处理过程等。
3.         出栈,恢复CPU的状态和寄存器的值。由于第一步中已经调整好返回地址,这一步不需要再次调整。当然如果之前没有调整,这里则需要进行相应的调整。

   

论坛徽章:
0
6 [报告]
发表于 2014-08-10 23:37 |只看该作者
根据您的描述,您认为发生了IRQ异常后,lr_irq保存的是返回指令+4(硬件动作),因此需要软件减去4进行调整。好吧,我再重新阅读ARM ARM的文档看看。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP