异常处理体系结构

在裸板程序中,按键中断的head.S如下:

@******************************************************************************
@ File:head.S
@ 功能:初始化,设置中断模式、管理模式的栈,设置好中断处理函数
@******************************************************************************       
   
.extern     main
.text 
.global _start 
_start:
@******************************************************************************       
@ 异常向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************       
    b   Reset

@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
    b   HandleUndef 
 
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
    b   HandleSWI

@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
    b   HandlePrefetchAbort

@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
    b   HandleDataAbort

@ 0x14: 保留
HandleNotUsed:
    b   HandleNotUsed

@ 0x18: 中断模式的向量地址
    b   HandleIRQ

@ 0x1c: 快中断模式的向量地址
HandleFIQ:
    b   HandleFIQ

Reset:                  
    ldr sp, =4096           @ 设置栈指针,以下都是C函数,调用前需要设好栈
    bl  disable_watch_dog   @ 关闭WATCHDOG,否则CPU会不断重启
    
    msr cpsr_c, #0xd2       @ 进入中断模式
    ldr sp, =3072           @ 设置中断模式栈指针

    msr cpsr_c, #0xd3       @ 进入管理模式
    ldr sp, =4096           @ 设置管理模式栈指针,
                            @ 其实复位之后,CPU就处于管理模式,
                            @ 前面的“ldr sp, =4096”完成同样的功能,此句可省略

    bl  init_led            @ 初始化LED的GPIO管脚
    bl  init_irq            @ 调用中断初始化函数,在init.c中
    msr cpsr_c, #0x53       @ 设置I-bit=0,开IRQ中断
    
    ldr lr, =halt_loop      @ 设置返回地址
    ldr pc, =main           @ 调用main函数
halt_loop:
    b   halt_loop

HandleIRQ:
    sub lr, lr, #4                  @ 计算返回地址
    stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器
                                    @ 注意,此时的sp是中断模式的sp
                                    @ 初始值是上面设置的3072
    
    ldr lr, =int_return             @ 设置调用ISR即EINT_Handle函数后的返回地址  
    ldr pc, =EINT_Handle            @ 调用中断服务函数,在interrupt.c中
int_return:
    ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将spsr的值复制到cpsr
    

由此可见中断处理的过程为:

1.按键按下,

2.cpu核自动完成下面3件事

将cpsr的值复制到异常模式的spsr

将cpsr的工作模式设为异常中断模式

PC值等于异常向量表的地址,比如如上head.S中,PC=0x18即执行b HandleIRQ指令, 进入中断入口

3.进入中断入口HandleIRQ处执行又分为:

保存被中断的现场(比如管理模式下的r0-r12寄存器,返回地址)

调用中断处理函数

恢复被中断的现场(比如将保存在中断栈中的管理模式的模式的寄存器r0,r12,被中断指令的下一条地址lr,spsr的值复制到cpsr)

编码时需要

①设置异常向量表,设置各种模式的栈地址,init.c相应的设置中断引脚,中断打开,中断优先级,总中断cpsr的值设置等等

②跳转到异常入口处

    b   HandleIRQ
③跳到异常入口处执行

保存被中断的现场(比如管理模式下的r0-r12寄存器,返回地址)

调用中断处理函数

恢复被中断的现场(比如将保存在中断栈中的管理模式的模式的寄存器r0,r12,被中断指令的下一条地址lr,spsr的值复制到cpsr)

其中调用中断处理函数分为:
获得中断号
对不同的中断,进行相应处理或者调用相应中断服务函数
清中断,清中断在②之前或之后要根据中断是否发生或者是否可忽略来定
对linux2.6.22来说,trap.c中有函数
void __init early_trap_init(void)
函数调用memcpy将异常向量表代码复制到CONFIG_VECTORS_BASE即虚拟地址0xffff0000处,跳转目的代码复制到0xffff0000+0x200
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);


	unsigned long vectors = CONFIG_VECTORS_BASE;//xyc: .config中CONFIG_VECTORS_BASE=0xffff0000
	extern char __stubs_start[], __stubs_end[];
	extern char __vectors_start[], __vectors_end[];
	extern char __kuser_helper_start[], __kuser_helper_end[];
	int kuser_sz = __kuser_helper_end - __kuser_helper_start;

	/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
	 //xyc:__vectors_start,异常向量入口在entry-armv.S文件中
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
开始异常向量表代码在entry-armv.S (arch\arm\kernel)中,异常向量表保存在__vectors_start全局变量地址处,异常向量表如下:

<pre name="code" class="cpp">	.globl	__stubs_start
__vectors_start:swi SYS_ERROR0b vector_und + stubs_offsetldr pc, .LCvswi + stubs_offsetb vector_pabt + stubs_offsetb vector_dabt + stubs_offsetb vector_addrexcptn + stubs_offsetb vector_irq + stubs_offsetb vector_fiq + stubs_offset.globl __vectors_end__vectors_end:
 
 
 比如发生中断,执行(vector_irq由宏vector_stub实现),其中stubs_offset=__vectors_start + 0x200 - __stubs_start=vectors+ 0x200 - (vectors + 0x200)=0   
	b	vector_irq + stubs_offset

而vector_irq由宏定义
vector_stub irq, IRQ_MODE, 4

<pre name="code" class="cpp">	.macro	vector_stub, name, mode, correction=0
	.align	5
vector_\name:
 
	.if \correction
	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
	str	lr, [sp, #8]		@ save spsr

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

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f
	mov	r0, sp
	ldr	lr, [pc, lr, lsl #2]
	movs	pc, lr			@ branch to handler in SVC mode
	.endm

因此vector_irq展开为( ①中断入口):
vector_irq:
	sub	lr, lr, #4

	@
	@ 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)
	msr	spsr_cxsf, r0

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f
	mov	r0, sp
	ldr	lr, [pc, lr, lsl #2]
	movs	pc, lr			@ branch to handler in SVC mode

a进程产生了中断b,中断b处理中又产生了中断c,a/b/c 共享一个cpsr如果b,c为父子进程,则b c共享一个b_bpsr,所以a的cpsr保存在b_bpsr中,b中断中产生c的话,b的cpsr值自动保存在b_cpsr,而 a的cpsr已经保存在b_bpsr中,所以需要将保存在b_bpsr的a的cpsr 值压栈,

然后跳到:
	.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
__irq_usr表示在用户态发生了中断:

即跳到__irq_usr标号地址处:
__irq_usr:
	usr_entry

#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif
	get_thread_info tsk
#ifdef CONFIG_PREEMPT
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	add	r7, r8, #1			@ increment it
	str	r7, [tsk, #TI_PREEMPT]
#endif

	irq_handler
#ifdef CONFIG_PREEMPT
	ldr	r0, [tsk, #TI_PREEMPT]
	str	r8, [tsk, #TI_PREEMPT]
	teq	r0, r7
	strne	r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on
#endif

	mov	why, #0
	b	ret_to_user

	.ltorg

	.align	5

usr_entry是个宏( ②保存寄存器的值):
	.macro	usr_entry
	sub	sp, sp, #S_FRAME_SIZE
	stmib	sp, {r1 - r12}

	ldmia	r0, {r1 - r3}
	add	r0, sp, #S_PC		@ here for interlock avoidance
	mov	r4, #-1			@  ""  ""     ""        ""

	str	r1, [sp]		@ save the "real" r0 copied
					@ from the exception stack

#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
	@ make sure our user space atomic helper is aborted
	cmp	r2, #TASK_SIZE
	bichs	r3, r3, #PSR_Z_BIT
#endif
#endif

	@
	@ We are now ready to fill in the remaining blanks on the stack:
	@
	@  r2 - lr_<exception>, already fixed up for correct return/restart
	@  r3 - spsr_<exception>
	@  r4 - orig_r0 (see pt_regs definition in ptrace.h)
	@
	@ Also, separately save sp_usr and lr_usr
	@
	stmia	r0, {r2 - r4}
	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
	.endm

然后继续在 __irq_usr以下执行
irq_handler
irq_handler是一个宏
	.macro	irq_handler
	get_irqnr_preamble r5, lr
1:	get_irqnr_and_base r0, r6, r5, lr
	movne	r1, sp
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	adrne	lr, 1b
	bne	asm_do_IRQ

最后调用asm_do_IRQ中断处理函数,处理完中断处理,以下的被中断的现场恢复就不分析了

总结异常处理:
⑴cpu进入异常模式
b vector_irq + stubs_offset
⑶   调用中断处理函数  asm_do_IRQ


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值