Arm架构异常处理流程之中断

ARM 159浏览
【摘要】
本文将为您介绍linux内核是如何实现arm中断处理的。缺页异常、中断和系统调用同属arm异常处理,笔者计划分三篇文档分别介绍一下,其实在汇编阶段三种处理流程有很多相通之处,不过为了阅读方便,即使相同的部分也会重新在各自文档中介绍一遍。

【写作原因】

1 了解一下linux源码是如何实现arm中断处理的。

2 对于定位一些中断有关的问题,比如中断过多等会有所帮助

【正文一】linux系统arm中断处理之汇编阶段

关闭和开启当前处理器上的本地中断,会产生中断信号,但不处理。

local_irq_disable()关闭中断指令:cpsid i;

local_irq_enable()开启中断指令:cpsie i;

关闭和开启中断,不会产生中断信号。

disable_irq/enable_irq

2 为了介绍方便介绍,先列出两个知识点。 

2.1 linux系统为实现异常处理引入了栈帧的概念:

#define ARM_cpsr           uregs[16]
#define ARM_pc               uregs[15]
#define ARM_lr                uregs[14]
#define ARM_sp               uregs[13]
#define ARM_ip                uregs[12]
#define ARM_fp               uregs[11]
#define ARM_r10             uregs[10]
#define ARM_r9               uregs[9]
#define ARM_r8               uregs[8]
#define ARM_r7               uregs[7]
#define ARM_r6               uregs[6]
#define ARM_r5               uregs[5]
#define ARM_r4               uregs[4]
#define ARM_r3               uregs[3]
#define ARM_r2               uregs[2]
#define ARM_r1               uregs[1]
#define ARM_r0               uregs[0]
#define ARM_ORIG_r0   uregs[17]

2.2 Arm的几种模式介绍:

处理器模式    缩写       对应的M[4:0]编码       Privilegelevel

User                   usr              10000                               PL0

FIQ                   fiq              10001                               PL1

IRQ                   irq              10010                              PL1

Supervisor               svc             10011                              PL1

Monitor                 mon             10110                              PL1

Abort                   abt             10111                              PL1

Hyp                    hyp             11010                              PL2

Undefined               und              11011                               PL1

System                 sys              11111                               PL1

2.3 ARM 异常处理总入口(entry-armv.s):

/*注释:
1)Arm架构异常处理向量表起始地址__vectors_start。
2)Arm架构定义7种异常包括中断、系统调用、缺页异常等,发生异常时处理器会跳转到相应入口。
3)异常向量表起始位置有cp15协处理器的控制寄存器c1的bit13
决定:v=0时,异常向量起始于0xffff0000;v=1时起始于0x0.
4)举例:当IRQ发生时跳转到0x18这个虚拟地址上(内核态虚拟地址)
head.s中设置了cp15寄存器器(proc-v7.s->__v7_setup()函数设置的)
*/
__vectors_start:
        W(b)          vector_rst
        W(b)          vector_und
/*
系统调用入口点:
 __vectors_start + 0x1000=__stubs_start
此时pc指向系统调用异常 的处理入口:vector_swi
用户态通过swi指令产生软中断。
因为系统调用异常代码编译到其他文件,其入口地址与异常向量相隔
较远,使用b指令无法跳转过去(b指令只能相对当前pc跳转+/-32M范围)
*/
        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

3 中断处理vector_irq):     

3.1  vector_irq : 它是通过vetcor_stub宏定义的。   

/*注释:
 当irq发生时,硬件完成如下操作:
 1arm在irq模式下有自己的lr寄存器lr_irq ,spsr_irq,sp_irq.
  r14_irq=lr_irq = address of next instruction to be executed+4;
 2spsr_irq = cpsr:保存了处理器当前状态、中断屏蔽位及各条件标志位,保存后
  cpsr会切换到irq模式。
 3cpsr[4:0]=0b10010:设置arm为irq模式
 4cpsr[4] = 0:arm状态执行。
 5cpsr[7]=1:禁止irq
 6pc=0xffff0018:将pc值设置成异常中断的中断向量地址,即:vector_irq地址。

 之后进入下面的中断处理。
 vector_stub 函数定义了vetor_irq
 */
        vector_stub     irq, IRQ_MODE, 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)

3.2 vecotr_stub 宏定义。

1)vector_stub 宏尤为关键,arm任何异常都是通过其将r0/lr/spsr保存到异常模式的栈中。

2vector_stub通过 vector_name实现其功能:

/*注释:
该接口负责保存异常发生前一时刻cpu寄存器到异常mode的栈中,保存r0,lr,spsr寄存器的值到sp_dabt或sp_irq上
注意:
1 此时的sp是异常状态下的sp,这个栈只有12byte大小,在cpu_init()中初始化。
2 arm在IRQ/svc/ABORT几种模式下sp是不能共用的,详见:A2.5.7
3 此时lr中保存的实际上是异常的返回地址,异常发生,切换到svc模式后,会将lr保存到svc模式栈中(pt_regs->pc)
   最后从异常返回时再将pt_regs->pc加载入arm寄存器pc中,实现异常返回。
   本函数只是其中一个步骤,即为将异常发生时刻lr保存到svc模式栈中(pt_regs->pc)做准备。
4 spsr是异常发生那一刻(即进入异常模式前是什么模式)的cpsr状态,如内核态下发生中断:则spsr是svc模式10111;
如用户态下发生中断,则spsr是user模式10000.
5 此时cpu正处于异常状态(如中断),此时cpsr为10010;
6 要进行真正的异常处理,需要退出异常模式进入svc模式.
*/
        .macro     vector_stub, name, mode, correction=0
/*强制cacheline=32byte对齐*/
        .align        5
vector_name:
        .ifcorrection
/*注释:
需要调整返回值,对应irq异常将lr减去4
因为异常发生时,arm将pc地址+4赋值给了lr
*/
        sub   lr, lr, #correction
        .endif

        @
        @Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @(parent CPSR)
        @spsr中保存异常发生时刻的cpsr ; book:A2.6 Exceptions;
        @注意此时的栈sp是异常时(abt mode或irq mode)的栈sp和svc mode里的栈sp不同
        @dabt异常时的sp只有12byte大小 ;cpu_init中初始化
        @save r0, lr;将r0和lr保存到异常模式的栈上[sp]=r0;[sp+4]=lr_dabt;stmia        sp,{r0, lr}没有sp!,因此sp不变
        @r0也要入栈,以为r0会用作传递参数(异常状态下的sp)
        stmia        sp, {r0, lr}
        /*将spsr保存到异常模式的栈上[sp+8]=spsr*/
        mrs  lr, spsr            @ read spsr to lr
        str    lr, [sp, #8]                  @save spsr  //sp+8=lr=(spsr_dabt)

        @
        @Prepare for SVC32 mode.  IRQs remaindisabled.
        @cpsr中保存的是异常模式:如中断10010;dabt10111
        mrs  r0, cpsr
        /*
        注意:
        1 dabt处理时:r0=r0^(0x17^0x13)=r0^0x4,bit3取反之后10011变为svc模式;
        2 IRQ处理时:r0=10010=r0^(0x12^0x13)=r0^0x1=10011变为svc模式
        */
        eor   r0, r0, #(mode ^ SVC_MODE | PSR_ISETSTATE)
        msr  spsr_cxsf, r0

        @
        @the branch table must immediately follow this code
        @
        and  lr, lr, #0x0f    /* 用户态(user mode)lr=0;内核态(svn mode)lr=3; */
/*
r0=sp;
注意:
1此时r0中保存了异常状态下sp栈地址,这个栈上保存了r0,lr(异常返回地址),
                           spsr(异常发生时,cpu的状态,当然异常返回时需要恢复该状态)
2之后的函数会把r0中保存的异常模式的sp上信息,加载到svc模式下的sp栈上。
异常处理返回时再将svc mode的栈加载到arm寄存器上。
*/

        mov r0, sp

/*
lr中是保存发生异常时arm的cpsr状态到spsr
1 user模式发生异常则lr=10000&0x0f;lr=pc+lr<<2pc+0时执行 __dabt_usr;
2 svc模式发生异常则lr=10011&0x0f;lr=pc+lr<<2 pc+12时执行 __data_svc

lr=3时执行__dabt_svc
*/

 ARM(     ldr    lr,[pc, lr, lsl #2]         )

/* movs中s表示把spsr恢复给cpsr,上面可知spsr保存的是svc模式,不过此时中断还是关闭的
异常处理一定要进入svc模式原因:
1)异常处理一定要PL1特权级。2)使能嵌套中断。
如果一个中断模式(示例用户态发生中断,arm从usr进入irq模式)中重新允许中断,
且这个中断模式中使用了bl指令,bl会把pc放到lr_irq中,这个地址会被当前模式下产生的中断破坏
这种情况下中断无法返回。所以为了避免这种情况,中断处理过程应该切换到svc模式,bl指令可以把
pc(即子程序返回地址)保存到lr_svc.
*/
        movs         pc, lr                           @branch to handler in SVC mode  把spsr copy到cpsr,spsr是svc模式,自此arm切换到了svc模式。
ENDPROC(vector_name)

3.3 回过头来再看中断异常处理:

中断异常:vector_irq->

        vector_stub     irq, IRQ_MODE, 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)

3.4 进入中断处理的流程:vector_stub irq->__irq_user

user模式下发生中断则会跳转到如下函数:        
   

__irq_usr:
        usr_entry--进入异常处理流程上面已经分析过。
        kuser_cmpxchg_check
        irq_handler--异常处理。
        get_thread_infotsk
        mov why, #0
        b       ret_to_user_from_irq --退出异常处理;
 UNWIND(.fnend             )
ENDPROC(__irq_usr)

3.5 armuser模式下的缺页异常处理:__irq_user->usr_entry

注意arm在user模式下无论中断还是缺页异常都会进行user_entry处理:

该函数是在vector_name处理完成后跳转过来的,此时r0中保存了异常模式的sp
此sp保存了r0/lr/spsr/可以参看上面vector_name的介绍。

        .macro     usr_entry
 UNWIND(.fnstart )
 UNWIND(.cantunwind )        @ don't unwind theuser space
/*这个sp是svc模式的栈地址,为理解方便可以记作:sp_svc,和r0中保存的异常模式的sp(记做:sp_dabt)不是一个,注意区分 */
        sub   sp, sp, #S_FRAME_SIZE   /*sp=sp-S_FRAME_SIZE*/
/*将r1-r12保存到sp_svc上:
注意1 ib=incressment berfore;sp=sp+4;[sp]=r1;...sp=sp+12*4;
ia=incressment after
2 sp_svc保持不变,因为不是stmib      sp!, {r1 - r12}
此处使用ib目的是为r0预留栈空间;
*/
 ARM(     stmib        sp,{r1 - r12}     )
 THUMB(        stmia        sp, {r0 - r12}     )
/*
 此时r0保存的是异常时的sp如:sp_dabt  (vector_stub    dabt中实现r0=sp;sp=lr;sp+4=spsr)
 缺页异常为例:
 r3=[r0]=[sp_dabt]=r0; 异常模式和svc模式r0是通用的。
 r4=r0=r0+4=lr_dabt; 异常发生时刻的lr值,即异常返回地址,异常返回时,需要将其赋值给arm的pc寄存器。
 r5=spsr_dabt;...r0=r0+3*4 异常发生时刻cpu的状态,异常返回时,需要将其赋值给arm的cpsr寄存器。
 r0是异常mode下sp_dabt,,
 在vector_dabt中初始:[r3]=dabt_r0;[r4]=dabt_lr;[r5]=dabt_spsr
 此处将异常mode(dabt、irq等)的栈信息保存到svc或user模式下的普通寄存器
 之后再把普通寄存器入栈
 */
        ldmia        r0, {r3 - r5}
        /* r0保存了svc模式下sp上用于保存pc的栈地址 */
        add  r0, sp, #S_PC             @here for interlock avoidance r0=&pt_regs->ARM_pc
        mov r6, #-1                         @  "" ""     ""       "" r6=-1

        /*开始将异常时的r0,dabt_lr,dabt_spsr,dabt_sp,dabt_lr保存到当前sp(即pt_regs) */
        str    r3, [sp]               @save the "real" r0 copied  [sp] = r3 中断那一刻的r0保存到栈顶
                                                       @from the exception stack

        @普通寄存器入栈
        @We are now ready to fill in the remaining blanks on the stack:
        @
        @  r4 - lr_<exception>, already fixed upfor correct return/restart
        @  r5 - spsr_<exception>
        @  r6 - orig_r0 (see pt_regs definition inptrace.h)
        @
        @Also, separately save sp_usr and lr_usr
        @将异常时的栈保存到当前模式下的pt_regs中
/*
此时,r0中保存的是svc模式下(即当前模式)pt_regs->pc指针栈地址;
注意此处r0没有加"!"(r0!),所以r0值不变
[r0]=r4;r0+4=r5;
即:pt_regs->ARM_pc=dabt_lr;
pt_regs->ARM_cpsr=r5=dabt_spsr;
pt_regs->ARM_ORIG_r0=r6=-1
至此:svc模式下栈中保存了pc指针和cpsr
异常返回时将pt_regs->ARM_pc,pt_regs->ARM_cpsr分别加载到arm寄存器
pt_regs->ARM_pc加载到pc寄存器后自然跳转到异常发生时刻的lr_dabt,从而实现异常返回。
pt_regs->ARM_cpsr同理。
*/
        stmia        r0, {r4 - r6}
/*
r0=pt_regs->pc
此分支[r0-4]=pt_regs->lr=lr_svc;
[r0-8]=pt_regs->sp=sp_svc ;db=decrementbefore
注意:
1当前栈上(pt_regs)只有r0/pc/cpsr/orig_r0上保存的是异常发生时刻的r0/lr/spsr/-1
2 sp和lr保存的是此刻svcmode的sp和lr值
*/
 ARM(     stmdb       r0,{sp, lr}^                         )
 THUMB(        store_user_sp_lrr0, r1, S_SP - S_PC       )
/*完成将异常时的dabt_r0,dabt_lr,dabt_spsr,dabt_sp,dabt_lr保存到当前sp(即pt_regs) */
        @
        @Enable the alignment trap while in kernel mode
        @
        alignment_trapr0
        @
        @Clear FP to mark the first stack frame
        @
        zero_fp
        ct_user_exitsave = 0
        .endm

3.6 中断异常处理函数irq_handler中发生了缺页异常:

1 vector_stub irq 中已经分析过,如果是user模式下发生中断则会跳转到如下函数:    
      

__irq_usr:
        usr_entry--进入异常处理流程上面已经分析过。
        kuser_cmpxchg_check
        irq_handler--异常处理。
        get_thread_infotsk
        mov why, #0
        b       ret_to_user_from_irq --退出异常处理;
 UNWIND(.fnend             )
ENDPROC(__irq_usr)

2 中断处理函数irq_handler:如果中断异常处理函数irq_handler中发生了缺页异常,该如何处理?

        .macro     irq_handler
        //handle_arch_irq系统注册的中断处理函数不只一个如:ambvic_handle_irq。
        ldr    r1, =handle_arch_irq
        /*
        当前模式下的栈地址即pt_regs保存到r0中;因为
        中断异常和缺页异常用同一r0,所以中断处理中发生缺页异常时r0寄存器会改变
        不过在中断处理入口时,r0已经入栈,所以没有影响
        */
        mov r0, sp
        /*
        lr中保存了中断处理的返回地址
        注意:
        1)此lr不能保存的svc的栈中,因为栈上pt_regs->lr在usr_entry保存了lr。
        2)中断异常和缺页异常的处理过程中arm都进入了svc模式,而arm在svc模式下看到
        的lr当然是相同的.所以中断处理过程发生缺页异常,有可能改变lr寄存器的值,
        引起中断处理不能正确返回。

        */
        adr   lr, BSYM(9997f)
/*
跳转到中断处理函数:此时发生缺页异常,会在svc_exit中改变lr的值为缺页异常发生时刻的lr
*/
        ldr    pc, [r1]
9997:
        .endm

3 缺页异常处理请参考博文:Arm架构异常处理流程之缺页异常

3.1 armsvc模式下的中断处理:

1)首先回忆一下中断处理:

vector_irq->
        vector_stub     irq, IRQ_MODE, 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)

2)arm在svc模式下的中断处理:__irq_svc

__irq_svc:
 svc_entry
 irq_handler
#ifdef CONFIG_PREEMPT
 get_thread_info tsk
 ldr r8, [tsk, #TI_PREEMPT]  @ get preempt count
 ldr r0, [tsk, #TI_FLAGS]  @ get flags
 teq r8, #0    @ if preempt count != 0
 movne r0, #0    @ force flags to 0
 tst r0, #_TIF_NEED_RESCHED
 blne svc_preempt
#endif
 svc_exit r5, irq = 1   @ return from exception
 UNWIND(.fnend  )
ENDPROC(__irq_svc)

3) arm在svc模式下的中断异常处理:__irq_svc->svc_entry

 .macro     svc_entry, stack_hole=0
 UNWIND(.fnstart          )
 UNWIND(.save {r0 - pc}                  )
        sub   sp, sp, #(S_FRAME_SIZE + stack_hole - 4)//sp指向r1

 SPFIX(    tst    sp, #4                 )//判断bit2是否为0

 SPFIX(    subeq       sp,sp, #4 )
        stmia        sp, {r1 - r12}  //svc mode的r1-r12入栈;[sp]=r1;[sp+4]=r2

        ldmia        r0, {r3 - r5}//r3 =dabt_r0;r4=dabt_lr;r5=dabt_spsr
        add  r7, sp, #S_SP - 4        @ here for interlock avoidance //r7指向pt_regs第12个 @  r7=pt_regs->sp
        mov r6, #-1                         @ ""  ""      ""      ""
/*此时r2指向栈顶,因为之前sub     sp, sp, #(S_FRAME_SIZE + stack_hole -4)*/
        add  r2, sp, #(S_FRAME_SIZE + stack_hole - 4)
 SPFIX(    addeq       r2, r2, #4 )
 /*此时r3中保存的是发生异常时刻的r0;[sp, #-4]!表示sp=sp-4:
                此时sp位于栈低指向r0*/
        str    r3, [sp, #-4]!              @ save the "real" r0 copied //吧dabt_r0保存到sp-4;sp=sp-4
                                              @from the exception stack

        mov r3, lr //保存svc mode的lr_svc到r3

        @
        @We are now ready to fill in the remaining blanks on the stack:
        @
        @  r2 - sp_svc=pt_regs->ARM_sp
        @  r3 - lr_svc=pt_regs->ARM_lr=lr_svc
        @  r4 - lr_<exception>=pt_regs->ARM_pc,already fixed up for correct return/restart
        @  r5 -spsr_<exception>==pt_regs->ARM_cpsr
        @  r6 - orig_r0==pt_regs->ARM_ORIG_r0 (seept_regs definition in ptrace.h)
        @  r7=pt_regs->ARM_sp
        /*[r7]=pt_regs->ARM_sp=r2(此时r2指向栈顶)*/
        stmia        r7, {r2 - r6}

        .endm

4) arm在svc模式下的中断异常处理:__irq_svc->svc_exit

        .macro svc_exit, rpsr, irq = 0
        .if      irq != 0
        @IRQs already off

        .else
        @IRQs off again before pulling preserved data off the stack
        disable_irq_notrace
        .endif
        /*
        经过svc_entry处理:sp指向栈底;(pt_regs*)(sp)->ARM_sp却指向了栈顶。
        此时[lr] =pt_regs->ARM_sp是栈顶
        */
        ldr    lr, [sp, #S_SP]                     @ top of the stack
/*
从sp->s_sp中取出64位数放到r0和r1中
即 : r0 = [pt_regs->ARM_lr];r1=[pt_regs->ARM_pc]
*/
        ldrd  r0, r1, [sp, #S_LR]            @ calling lr and pc
        clrex                                               @clear the exclusive monitor 清除某块内存的独占访问标志
/*
此时lr是栈顶:
将lr/pc/spsr放到栈上:
lr=lr-4;[lr]=spsr=pt_regs->ARM_cpsr;
lr=lr-4;[lr]=r1=[pt_regs->ARM_pc];
lr=lr-4;[lr]=r0=[pt_regs->ARM_lr];
至此lr中保存的地址上保存了指向了svc_entry中指定的lr;
*/
        stmdb       lr!, {r0, r1, rpsr}              @ calling lr and rfe context
        /*出栈:赋值r0-r12寄存器 */
        ldmia        sp, {r0 - r12}
/*
此时lr上保存的是栈地址即&pt_regs->ARM_lr
sp=lr;lr=pt_regs->ARM_lr此时sp保存的地址上保存的是pt_regs->ARM_lr的值
*/
        mov sp, lr
/*
lr=[sp]真正从栈地址&pt_regs->ARM_lr上取出pt_regs->ARM_lr放入lr寄存器;
sp=sp+4;
至此sp上保存了pc地址
*/
        ldr    lr, [sp], #4
        rfeia sp!
        .endm

3.8 退出异常处理的流程:

   vector_stub    irq已经分析过,如果是user模式下发生中断则会跳转到如下函数:
__irq_usr:
        usr_entry--进入异常处理流程上面已经分析过。
        kuser_cmpxchg_check
        irq_handler--异常处理。
        get_thread_infotsk
        mov why, #0
        b       ret_to_user_from_irq --退出异常处理;
 UNWIND(.fnend             )
ENDPROC(__irq_usr)

【正文二】linux系统arm中断处理之c语言阶段

一.跳转到中断处理的c语言函数入口

        .macro     irq_handler
        //handle_arch_irq系统注册的中断处理函数不只一个如:ambvic_handle_irq。
        ldr    r1, =handle_arch_irq
        /*
        当前模式下的栈地址即pt_regs保存到r0中;因为
        中断异常和缺页异常用同一r0,所以中断处理中发生缺页异常时r0寄存器会改变
        不过在中断处理入口时,r0已经入栈,所以没有影响
        */
        mov r0, sp
        /*
        lr中保存了中断处理的返回地址
        注意:
        1)此lr不能保存的svc的栈中,因为栈上pt_regs->lr在usr_entry保存了lr。
        2)中断异常和缺页异常的处理过程中arm都进入了svc模式,而arm在svc模式下看到
        的lr当然是相同的.所以中断处理过程发生缺页异常,有可能改变lr寄存器的值,
        引起中断处理不能正确返回。

        */
        adr   lr, BSYM(9997f)
        /*
        跳转到中断处理函数:此时发生缺页异常,会在svc_exit中改变lr的值为缺页异常发生时刻的lr
        */
        ldr    pc, [r1]
9997:
        .endm

二. c函数处理流程说明:

1  在上文介绍irq_handler过程中我们了解到中断处理函数跳转到了handle_arch_irq函数中。

2 handle_arch_irq被注册为gic_handle_irq函数

内核源文件:irq-gic.c

函数:gic_init_bases()

注册过程:gic_init_bases()->set_handle_irq(gic_handle_irq) 中实现handle_arch_irq=gic_handle_irq

3 中断处理c语言函数入口实际为:gic_handle_irq();

4 gic_handle_irq中会回调通过request_irq注册的中断回调函数。

5 c 语言中断处理流程中irq_enter()/irq_exit()成对出现,irq_exit()时会处理软中断。

因为c语言处理部分比较简单,在此不做详细介绍,只给出以上大体流程,感兴趣可以参考源代码看一下。

【总结】

以上分析了linux系统是如何实现的arm中断处理。

[实例分析]:

ps:本部分可以忽略不看。

1 gic_handle_irq的处理过程,的反汇编。该项主要为分析中断处理过程发生缺页异常的情况,可以忽略。

        .macro     irq_handler保存了lr;不过在lr入栈到 出栈过程中,缺页异常改变了lr的值

        (中断处理函数和缺页处理函数真正处理时,arm都处于svc模式,所以用同一个lr)。

vmlinux:  

c000862c <gic_handle_irq>:

c000862c:        e92d41f0        push          {r4, r5, r6, r7, r8, lr} --lr入栈

c0008630:       e30058b0       movw       r5, #2224; 0x8b0

c0008634:       e34c5055       movt         r5, #49237       ;0xc055

c0008638:       e1a06000       mov r6,r0

c000863c:        e5957004       ldr    r7,[r5, #4]

c0008640:       e287400c       add  r4,r7, #12

c0008644:       ea000001       b       c0008650<gic_handle_irq+0x24>

c0008648:       e595058c       ldr    r0,[r5, #1420]; 0x58c

c000864c:        eb017631       bl      c0065f18<__handle_domain_irq> ;bl会把pc放到lr中,改变了lr值,所以lr要先push入栈

c0008650:       e594c000       ldr    ip,[r4]

c0008654:       e7e9005c       ubfx r0, ip, #0, #10

c0008658:       e1a03006       mov r3,r6

c000865c:        e2401010       sub   r1,r0, #16

c0008660:       e3a02001       mov r2,#1

c0008664:       e3510ffb         cmp r1, #1004; 0x3ec

c0008668:       e1a01000       mov r1,r0

c000866c:        9afffff5   bls    c0008648<gic_handle_irq+0x1c>

c0008670:       e350000f        cmp r0, #15

c0008674:       88bd81f0        pophi        {r4, r5, r6, r7, r8, pc}--lr出栈

c0008678:       e587c010       str    ip,[r7, #16]

c000867c:        e1a01006       mov r1,r6

c0008680:       eb00332b       bl      c0015334<handle_IPI>

c0008684:       eafffff1   b       c0008650<gic_handle_irq+0x24>

 

c0065fd8 <__handle_domain_irq>:

c0065fd8:        e92d41f0        push          {r4, r5, r6, r7, r8, lr}

 

/usr/bin # cat /proc/interrupts

           CPU0              CPU1      

 29:            111356            111840      GIC 29 arch_timer

 30:         0               0       GIC 30  arch_timer

 33:         0               0       GIC 33 RTC Alarm

 36:            39758             0       GIC 36 uart-pl011

 37:         0               0       GIC 37 uart-pl011

 48:         0               0       GIC 48 adc

 55:            28860             0       GIC 55 himci

 57:            8293             0       GIC 57 10050000.ethernet

 59:            55539             0         GIC 59 VOU Interrupt

IPI0:         0               0         CPU wakeup interrupts

IPI1:         0               0         Timer broadcast interrupts

IPI2:           12901             110103      Rescheduling interrupts

Err:         0

/usr/bin #

// cpu0cpu1上都可以发生中断irq=59 (3=0x1|0x2)

/usr/bin # cat/proc/irq/59/smp_affinity


[arm64位]

1 ARM64的启动过程之(六):异常向量表的设定:http://www.wowotech.net/?post=238

2 arm64 linux : http://blog.csdn.net/lidan113lidan/article/details/48677735

           ARMv8 Linux内核异常处理过程分析

           ARM64的启动过程之(一):内核第一个脚印

3 代码流程:

arch/arm64/kernel/entry.s:   
el0_sync()/el0_irq() -> ret_to_user() -> work_pending() -> do_notify_resume()  
   
        // (1) 在arm64架构中,kernel运行在el1,用户态运行在el0。
        // el0_sync是用户态发生异常的入口,el0_irq是用户态发生中断的的入口。
        // 异常包括几种:系统调用el0_svc、数据异常el0_da、指令异常el0_ia等等几种。
        .align        11
ENTRY(vectors)
        ventry        el0_sync                        // Synchronous 64-bit EL0
        ventry        el0_irq                                // IRQ 64-bit EL0
        // (2) 用户态异常el0_sync
        .align        6
el0_sync:
        kernel_entry 0
        mrs        x25, esr_el1                        // read the syndrome register
        lsr        x24, x25, #ESR_EL1_EC_SHIFT        // exception class
        cmp        x24, #ESR_EL1_EC_SVC64                // SVC in 64-bit state
        b.eq        el0_svc
        cmp        x24, #ESR_EL1_EC_DABT_EL0        // data abort in EL0
        b.eq        el0_da
        cmp        x24, #ESR_EL1_EC_IABT_EL0        // instruction abort in EL0
        b.eq        el0_ia
        cmp        x24, #ESR_EL1_EC_FP_ASIMD        // FP/ASIMD access
        b.eq        el0_fpsimd_acc
        cmp        x24, #ESR_EL1_EC_FP_EXC64        // FP/ASIMD exception
        b.eq        el0_fpsimd_exc
        cmp        x24, #ESR_EL1_EC_SYS64                // configurable trap
        b.eq        el0_undef
        cmp        x24, #ESR_EL1_EC_SP_ALIGN        // stack alignment exception
        b.eq        el0_sp_pc
        cmp        x24, #ESR_EL1_EC_PC_ALIGN        // pc alignment exception
        b.eq        el0_sp_pc
        cmp        x24, #ESR_EL1_EC_UNKNOWN        // unknown exception in EL0
        b.eq        el0_undef
        cmp        x24, #ESR_EL1_EC_BREAKPT_EL0        // debug exception in EL0
        b.ge        el0_dbg
        b        el0_inv
        // (2.1) 用户态数据访问el0_da
el0_da:
        /*
         * Data abort handling
         */
        mrs        x26, far_el1
        // enable interrupts before calling the main handler
        enable_dbg_and_irq
        ct_user_exit
        bic        x0, x26, #(0xff << 56)
        mov        x1, x25
        mov        x2, sp
        bl        do_mem_abort
        b        ret_to_user
        // (3) 用户态中断el0_irq
        .align        6
el0_irq:
        kernel_entry 0
el0_irq_naked:
        enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
        bl        trace_hardirqs_off
#endif
        ct_user_exit
        irq_handler
#ifdef CONFIG_TRACE_IRQFLAGS
        bl        trace_hardirqs_on
#endif
        b        ret_to_user
ENDPROC(el0_irq)
        // (4) 返回用户态的处理函数ret_to_user
        // 判断thread_info->flags与#_TIF_WORK_MASK,是否有置位,有则跳转到work_pending执行。
        // _TIF_SIGPENDING置位即代表了进程有信号需要处理
        // #define _TIF_WORK_MASK                (_TIF_NEED_RESCHED | _TIF_SIGPENDING |
        //                         _TIF_NOTIFY_RESUME | _TIF_FOREIGN_FPSTATE)
ret_to_user:
        disable_irq                                // disable interrupts
        ldr        x1, [tsk, #TI_FLAGS]
        and        x2, x1, #_TIF_WORK_MASK
        cbnz        x2, work_pending
        enable_step_tsk x1, x2
no_work_pending:
#ifdef CONFIG_MTK_COMPAT
        kernel_exit_compat ret = 0
#else
        kernel_exit 0, ret = 0
#endif
ENDPROC(ret_to_user)
        // (5) work_pending
fast_work_pending:
        str        x0, [sp, #S_X0]                        // returned x0
work_pending:
        tbnz        x1, #TIF_NEED_RESCHED, work_resched
        /* TIF_SIGPENDING, TIF_NOTIFY_RESUME or TIF_FOREIGN_FPSTATE case */
        ldr        x2, [sp, #S_PSTATE]
        mov        x0, sp                                // 'regs'
Markdown
Toggle Zen Mode
Preview
        tst        x2, #PSR_MODE_MASK                // user mode regs?
        b.ne        no_work_pending                        // returning to kernel
        enable_irq                                // enable interrupts for do_notify_resume()
        bl        do_notify_resume
        b        ret_to_user
work_resched:
        bl        schedule