Arm架构异常中断处理流程

本文深入剖析了ARM处理器的中断处理流程,从硬件中断模式切换到软件中断处理,详细阐述了从用户空间到内核空间的转换,包括异常向量表的设置、中断模式的栈准备、异常处理函数以及中断核心处理函数的工作原理。通过对vector_irq、vector_stub、__irq_usr等关键函数的解析,揭示了中断处理中硬件现场的保存与恢复机制。
摘要由CSDN通过智能技术生成

转载:https://blog.csdn.net/eleven_xiy/article/details/71157618?spm=1001.2014.3001.5506

转载:https://www.cxymm.net/article/michaelcao1980/43309325

中断模式的stack准备

ARM处理器有多种process mode,例如user mode(用户空间的AP所处于的模式)、supervisor mode(即SVC mode,大部分的内核态代码都处于这种mode)、IRQ mode(发生中断后,处理器会切入到该mode)等。对于linux kernel,其中断处理处理过程中,ARM 处理器大部分都是处于SVC mode。但是,实际上产生中断的时候,ARM处理器实际上是进入IRQ mode,因此在进入真正的IRQ异常处理之前会有一小段IRQ mode的操作,之后会进入SVC mode进行真正的IRQ异常处理。由于IRQ mode只是一个过度,因此IRQ mode的栈很小,只有12个字节

    struct stack { 
        u32 irq[3]; 
        u32 abt[3]; 
        u32 und[3]; 
    } ____cacheline_aligned;

    static struct stack stacks[NR_CPUS];

除了irq mode,linux kernel在处理abt mode(当发生data abort exception或者prefetch abort exception的时候进入的模式)和und mode(处理器遇到一个未定义的指令的时候进入的异常模式)的时候也是采用了相同的策略。也就是经过一个简短的abt或者und mode之后,stack切换到svc mode的栈上,这个栈就是发生异常那个时间点current thread的内核栈。anyway,在irq mode和svc mode之间总是需要一个stack保存数据,这就是中断模式的stack,系统初始化的时候,cpu_init函数中会进行中断模式stack的设定:

    void notrace cpu_init(void) 
    {

        unsigned int cpu = smp_processor_id();------获取CPU ID 
        struct stack *stk = &stacks[cpu];---------获取该CPU对于的irq abt和und的stack指针

    ……

    #ifdef CONFIG_THUMB2_KERNEL 
    #define PLC    "r"------Thumb-2下,msr指令不允许使用立即数,只能使用寄存器。 
    #else 
    #define PLC    "I" 
    #endif

        __asm__ ( 
        "msr    cpsr_c, %1\n\t"------让CPU进入IRQ mode 
        "add    r14, %0, %2\n\t"------r14寄存器保存stk->irq 
        "mov    sp, r14\n\t"--------设定IRQ mode的stack为stk->irq 
        "msr    cpsr_c, %3\n\t" 
        "add    r14, %0, %4\n\t" 
        "mov    sp, r14\n\t"--------设定abt mode的stack为stk->abt 
        "msr    cpsr_c, %5\n\t" 
        "add    r14, %0, %6\n\t" 
        "mov    sp, r14\n\t"--------设定und mode的stack为stk->und 
        "msr    cpsr_c, %7"--------回到SVC mode 
            :--------------------上面是code,下面的output部分是空的 
            : "r" (stk),----------------------对应上面代码中的%0 
              PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),------对应上面代码中的%1 
              "I" (offsetof(struct stack, irq[0])),------------对应上面代码中的%2 
              PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),------以此类推,下面不赘述
              "I" (offsetof(struct stack, abt[0])), 
              PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), 
              "I" (offsetof(struct stack, und[0])), 
              PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) 
            : "r14");--------上面是input操作数列表,r14是要clobbered register列表 
    }

嵌入式汇编的语法格式是:asm(code : output operand list : input operand list : clobber list);大家对着上面的code就可以分开各段内容了。在input operand list中,有两种限制符(constraint),“r"或者"I”,"I"表示立即数(Immediate operands),"r"表示用通用寄存器传递参数。clobber list中有一个r14,表示在汇编代码中修改了r14的值,这些信息是编译器需要的内容

SVC模式的stack准备

我们经常说进程的用户空间和内核空间,对于一个应用程序而言,可以运行在用户空间,也可以通过系统调用进入内核空间。在用户空间,使用的是用户栈,也就是我们软件工程师编写用户空间程序的时候,保存局部变量的stack。陷入内核后,当然不能用用户栈了,这时候就需要使用到内核栈。所谓内核栈其实就是处于SVC mode时候使用的栈

Linux kernel在创建进程(包括用户进程和内核线程)的时候都会分配一个(或者两个,和配置相关)page frame,底部是struct thread_info数据结构,顶部(高地址)就是该进程的内核栈。当进程切换的时候,整个硬件和软件的上下文都会进行切换,这里就包括了svc mode的sp寄存器的值被切换到调度算法选定的新的进程的内核栈上来。

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

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

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

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

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

disable_irq/enable_irq

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

#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.3 ARM 异常处理总入口(entry-armv.s):

处理器模式    缩写       对应的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):

异常向量表的准备

对于ARM处理器而言,当发生异常的时候,处理器会暂停当前指令的执行,保存现场,转而去执行对应的异常向量处的指令,当处理完该异常的时候,恢复现场,回到原来的那点去继续执行程序。系统所有的异常向量(共计8个)组成了异常向量表。向量表(vector table)的代码如下:

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

对于本文而言,我们重点关注vector_irq这个exception vector。异常向量表可能被安放在两个位置上: (1)异常向量表位于0x0的地址。这种设置叫做Normal vectors或者Low vectors。 (2)异常向量表位于0xffff0000的地址。这种设置叫做high vectors 具体是low vectors还是high vectors是由ARM的一个叫做的SCTLR寄存器的第13个bit (vector bit)控制的。对于启用MMU的ARM Linux而言,系统使用了high vectors。为什么不用low vector呢?对于linux而言,0~3G的空间是用户空间,如果使用low vector,那么异常向量表在0地址,那么则是用户空间的位置,因此linux选用high vector。当然,使用Low vector也可以,这样Low vector所在的空间则属于kernel space了(也就是说,3G~4G的空间加上Low vector所占的空间属于kernel space),不过这时候要注意一点,因为所有的进程共享kernel space,而用户空间的程序经常会发生空指针访问,这时候,内存保护机制应该可以捕获这种错误(大部分的MMU都可以做到,例如:禁止userspace访问kernel space的地址空间),防止vector table被访问到。对于内核中由于程序错误导致的空指针访问,内存保护机制也需要控制vector table被修改,因此vector table所在的空间被设置成read only的。在使用了MMU之后,具体异常向量表放在那个物理地址已经不重要了,重要的是把它映射到0xffff0000的虚拟地址就OK了,具体代码如下:

    static void __init devicemaps_init(const struct machine_desc *mdesc) 
    { 
        …… 
        vectors = early_alloc(PAGE_SIZE * 2); -----分配两个page的物理页帧

        early_trap_init(vectors); -------copy向量表以及相关help function到该区域

        …… 
        map.pfn = __phys_to_pfn(virt_to_phys(vectors)); 
        map.virtual = 0xffff0000; 
        map.length = PAGE_SIZE; 
    #ifdef CONFIG_KUSER_HELPERS 
        map.type = MT_HIGH_VECTORS; 
    #else 
        map.type = MT_LOW_VECTORS; 
    #endif 
        create_mapping(&map); ----------映射0xffff0000的那个page frame

        if (!vectors_high()) {---如果SCTLR.V的值设定为low vectors,那么还要映射0地址开始的memory 
            map.virtual = 0; 
            map.length = PAGE_SIZE * 2; 
            map.type = MT_LOW_VECTORS; 
            create_mapping(&map); 
        }


        map.pfn += 1; 
        map.virtual = 0xffff0000 + PAGE_SIZE; 
        map.length = PAGE_SIZE; 
        map.type = MT_LOW_VECTORS; 
        create_mapping(&map); ----------映射high vecotr开始的第二个page frame

    …… 
    }

为什么要分配两个page frame呢?这里vectors table和kuser helper函数(内核空间提供的函数,但是用户空间使用)占用了一个page frame,另外异常处理的stub函数占用了另外一个page frame。为什么会有stub函数呢?稍后会讲到。

在early_trap_init函数中会初始化异常向量表,具体代码如下

 void __init early_trap_init(void *vectors_base) 
    { 
        unsigned long vectors = (unsigned long)vectors_base; 
        extern char __stubs_start[], __stubs_end[]; 
        extern char __vectors_start[], __vectors_end[]; 
        unsigned i;

        vectors_page = vectors_base;

        将整个vector table那个page frame填充成未定义的指令。起始vector table加上kuser helper函数并不能完全的充满这个page,有些缝隙。如果不这么处理,当极端情况下(程序错误或者HW的issue),CPU可能从这些缝隙中取指执行,从而导致不可知的后果。如果将这些缝隙填充未定义指令,那么CPU可以捕获这种异常。 
        for (i = 0; i < PAGE_SIZE / sizeof(u32); i++) 
            ((u32 *)vectors_base)[i] = 0xe7fddef1;

      拷贝vector table,拷贝stub function 
        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); 
        memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

        kuser_init(vectors_base); ----copy kuser helper function

        flush_icache_range(vectors, vectors + PAGE_SIZE * 2); 
        modify_domain(DOMAIN_USER, DOMAIN_CLIENT); 
    }

3.1 异常中断发生时硬件操作

/*注释:
	arm在irq模式下有自己的lr寄存器lr_irq ,spsr_irq,sp_irq.
 当irq发生时,硬件完成如下操作:
 1,lr_irq(r14_irq) = address of next instruction to be executed+4;
 2,spsr_irq = cpsr	:保存了处理器当前状态、中断屏蔽位及各条件标志位,保存后cpsr会切换到irq模式。
 3,cpsr[4:0]=0b10010:设置arm为irq模式,CPU切换到IRQ模式,伴随着 lr,sp,splr的切换
 4,cpsr[5] = 0 		:arm状态执行。
 5,cpsr[7]=1:禁止irq
 6,pc=0xffff0018:将pc值设置成异常中断的中断向量地址,即:vector_irq地址。
 
 接着进入软件操作,进入下面的中断处理。
 vector_stub 函数定义了vetor_irq
 */
        vector_stub     irq, IRQ_MODE, 4		 @ IRQ_MODE 0x12
        .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保存到异常模式的栈中

2,vector_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
	.align	5 @/*强制cacheline=32byte对齐*/

vector_\name:
	.if \correction
	sub	lr, lr, #\correction  
	/*注释:需要调整返回值,对应irq异常将lr减去4,因为异常发生时,arm硬件上操作将异常的返回地址+4赋值给了lr*/    
	.endif

	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@spsr中保存异常发生时刻的cpsr ; 
    @注意此时的栈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}	@ [sp]=r0;[sp+4]=lr_dabt;stmia sp,{r0, lr},没有sp!,因此sp不变
	mrs	lr, spsr			@ /*将spsr保存到异常模式的栈上[sp+8]=spsr*/
	str	lr, [sp, #8]		@ [sp+8]=spsr= 异常前的cpsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@ cpsr中的是模式异常模式:如中断10010;dabt10111
	 /* 注意:
        1 dabt处理时:r0=r0^(0x17^0x13)=r0^0x4,bit3取反之后10011变为svc模式;
        2 IRQ处理时:r0=10010=r0^(0x12^0x13)=r0^0x1=10011变为svc模式 */
	mrs	r0, cpsr
	eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
	msr	spsr_cxsf, r0 	@更新spsr中的cxsf域,全部更新,spsr存的是SVC模式
		/*
		c 指  CPSR中的control field ( PSR[7:0])
        f 指  flag field (PSR[31:24])
        x 指  extend field (PSR[15:8])
        s 指  status field ( PSR[23:16])
        */
        
	@ the branch table must immediately follow this code
	@ lr保存了发生IRQ时候的CPSR,通过and操作,可以获取CPSR.M[3:0]的
	@ /* 从用户态进入异常(user mode)lr=0;从内核态进入异常(svn mode)lr=3; */
	and	lr, lr, #0x0f
    
	/*	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<<2 ,pc+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			@movs中s表示把spsr恢复给cpsr,branch to handler in SVC mode
ENDPROC(vector_\name)

	.align	2
	@ handler addresses follow this label
1:
	.endm

1,我们期望在栈上保存发生中断时候的硬件现场(HW context),这里就包括ARM的core register。当发生IRQ中断的时候,lr中保存了发生中断的PC+4,如果减去4的话,得到的就是发生中断那一点的PC值。

2,当前是IRQ mode,SP_irq在初始化的时候已经设定(12个字节)。在irq mode的stack上,依次保存了发生中断那一点的r0值、PC值以及CPSR值(具体操作是通过spsr进行的,其实硬件已经帮我们保存了CPSR到SPSR中了)。为何要保存r0值?因为随后的代码要使用r0寄存器,因此我们要把r0放到栈上,只有这样才能完完全全恢复硬件现场。

3,可怜的IRQ mode稍纵即逝,这段代码就是准备将ARM推送到SVC mode。如何准备?其实就是修改SPSR的值,SPSR不是CPSR,不会引起processor mode的切换(毕竟这一步只是准备而已)。

4,很多异常处理的代码返回的时候都是使用了stack相关的操作,这里没有。“movs pc, lr ”指令除了字面上意思(把lr的值付给pc),还有一个隐含的操作(movs中‘s’的含义):把SPSR copy到CPSR,从而实现了模式的切换。

irq栈sp_irq内容:
r0
lr_irq,异常返回地址
spsr_irq,异常返回时的cpsr,异常前的cpsr
3.3 接着看vector_irq后面的中断异常处理:

中断异常: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模式下发生中断则会跳转到如下函数:
    .align    5 
__irq_usr: 
    usr_entry	@---------(1)保存用户现场的描述 
    kuser_cmpxchg_check @---和本文描述的内容无关,这些不就介绍了 
    irq_handler @----------核心处理内容,请参考本章第二节的描述 
    get_thread_info tsk @------tsk是r9,指向当前的thread info数据结构 
    mov    why, #0 @--------why是r8 
    b    ret_to_user_from_irq @----中断返回,下一章会详细描述

3.4.1 arm在user模式下的缺页异常处理:__irq_user->usr_entry
保存现场,在SVC mode stack

注意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_irq)不是一个,注意区分 */
        sub   sp, sp, #S_FRAME_SIZE   /*sp=sp-S_FRAME_SIZE (18)*/
        
/*注意
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}     )  @将r1-r12保存到sp_svc上,保存异常时的普通寄存器现场

/*下面的指令:
 此时r0保存的是异常时的sp如:sp_irq  (vector_stub    irq中实现sp=r0;sp+4=lr;sp+8=spsr)
 缺页异常为例:
 r3=[r0]; 异常模式和svc模式r0是通用的。
 r4=[r0+4]=lr_dabt;  异常发生时刻的lr值,即异常返回地址,异常返回时,需要将其赋值给arm的pc寄存器。
 r5=[r0+8]=spsr_dabt;异常发生时刻cpu的状态,异常返回时,需要将其赋值给arm的cpsr寄存器。
 r0是异常mode下sp_dabt,      
 为什么要给r0赋值?因此kernel不想修改sp的值,保持sp指向栈顶
 */                         
        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
        
        str    r3, [sp]               @save the "real" r0 copied  [sp] = r3 中断那一刻的r0保存到栈顶
                                      @from the exception stack
	        						  @到此普通寄存器r0-r12都备份到svc stack(pt_regs)

/*  根据struct pt_regs的定义(这个数据结构反应了内核栈上的保存的寄存器的排列信息),从低地址到高地址依次为:
    ARM_r0  <--------sp指向这个位置
    ARM_r1 
    ARM_r2 
    ARM_r3 
    ARM_r4 
    ARM_r5 
    ARM_r6 
    ARM_r7 
    ARM_r8 
    ARM_r9 
    ARM_r10 
    ARM_fp 
    ARM_ip 
    ARM_sp  
    ARM_lr 
    ARM_pc <---------r0指向了这个位置 
    ARM_cpsr 
    ARM_ORIG_r0   <---sp_svc 指向的*/
    
        @接下来填充 pt_regs 的 pc,spsr,orig_r0 和 保存用户模式的sp_usr 和 lr_usr
        @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=lr_irq 
[r0+4]=r5=spsr_irq
即:pt_regs->ARM_pc=dabt_lr;
pt_regs->ARM_cpsr=r5=dabt_spsr;
pt_regs->ARM_ORIG_r0=r6=-1
至此:svc模式下栈中保存了pc指针和cpsr和orig_r0
异常返回时将pt_regs->ARM_pc,pt_regs->ARM_cpsr分别加载到arm寄存器
pt_regs->ARM_pc加载到pc寄存器后自然跳转到异常发生时刻的lr_dabt,从而实现异常返回。
pt_regs->ARM_cpsr同理。           
*/              
        stmia        r0, {r4 - r6} @实际上这段操作就是从irq stack就中断现场搬移到内核栈上
/*
r0=pt_regs->pc
此分支[r0-4]=pt_regs->lr=lr_usr;
[r0-8]=pt_regs->sp=sp_usr ;db=decrementbefore
注意:
2 sp和lr保存的是usrmode的sp和lr值
3, ^ Data is transferred into or out of the User mode registers instead of the current mode
registers.
*/
 ARM(     stmdb       r0,{sp, lr}^                         )

        @Enable the alignment trap while in kernel mode
        alignment_trap r0
        @Clear FP to mark the first stack frame
        zero_fp
        ct_user_exitsave = 0
        .endm
struct pt_regs

保存现场后struct pt_regs 都填充完了,以中断为例,保存的都是中断发生时的硬件状态

r0-r12备份了公共寄存器,

sp,lr备份了用户模式寄存器

pc 备份了异常返回时的地址

cpsr 备份了异常返回时的cpsr状态

 根据struct pt_regs的定义(这个数据结构反应了内核栈上的保存的寄存器的排列信息),从低地址到高地址依次为:
    ARM_r0 
    ARM_r1 
    ARM_r2 
    ARM_r3 
    ARM_r4 
    ARM_r5 
    ARM_r6 
    ARM_r7 
    ARM_r8 
    ARM_r9 
    ARM_r10 
    ARM_fp 
    ARM_ip 
    ARM_sp  
    ARM_lr 
    ARM_pc <---- 异常返回时的地址
    ARM_cpsr <--- 异常返回时的cpsr
    ARM_ORIG_r0   <---sp_svc 指向的
从中断返回用户空间ret_to_user_from_irq
 ENTRY(ret_to_user_from_irq) 
        ldr    r1, [tsk, #TI_FLAGS] 
        tst    r1, #_TIF_WORK_MASK---------------A 
        bne    work_pending 
    no_work_pending: 
        asm_trace_hardirqs_on ------和irq flag trace相关,暂且略过

        /* perform architecture specific actions before user return */ 
        arch_ret_to_user r1, lr----有些硬件平台需要在中断返回用户空间做一些特别处理 
        ct_user_enter save = 0 ----和trace context相关,暂且略过

        restore_user_regs fast = 0, offset = 0------------B 
    ENDPROC(ret_to_user_from_irq) 
    ENDPROC(ret_to_user)

A:thread_info中的flags成员中有一些low level的标识,如果这些标识设定了就需要进行一些特别的处理,这里检测的flag主要包括: #define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME) 这三个flag分别表示是否需要调度、是否有信号处理、返回用户空间之前是否需要调用callback函数。只要有一个flag被设定了,程序就进入work_pending这个分支。

B:从字面的意思也可以看成,这部分的代码就是将进入中断的时候保存的现场(寄存器值)恢复到实际的ARM的各个寄存器中,从而完全返回到了中断发生的那一点。具体的代码如下:

   .macro    restore_user_regs, fast = 0, offset = 0 
    ldr    r1, [sp, #\offset + S_PSR] @----r1保存了pt_regs中的spsr,也就是发生中断时的CPSR 
    ldr    lr, [sp, #\offset + S_PC]!   @ ----lr保存了PC值,同时sp移动到了pt_regs中PC的位置 
    msr    spsr_cxsf, r1 @--------赋值给spsr,进行返回用户空间的准备 
    clrex                    @ clear the exclusive monitor 

    .if    \fast 
    ldmdb    sp, {r1 - lr}^            @ get calling r1 - lr 
    .else 
    ldmdb    sp, {r0 - lr}^ @------将保存在内核栈上的数据保存到用户态的r0~r14寄存器 
    .endif 
    mov    r0, r0   @---------NOP操作,ARMv5T之前的需要这个操作 
    add    sp, sp, #S_FRAME_SIZE - S_PC @----现场已经恢复,移动svc mode的sp到原来的位置 
    movs    pc, lr               @--------返回用户空间 
    .endm

3.5 arm在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)
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
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

中断核心处理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

2 handle_arch_irq被注册为gic_handle_irq函数

set_handle_irq(gic_handle_irq) 中实现handle_arch_irq=gic_handle_irq

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值