在学习裸机的时候我们知道,异常的触发分为下面几个过程:
1.设置好异常向量表(把异常向量表放在异常向量的位置)
2.写异常处理函数
3.执行异常处理
下面列出简单的代码实现
.text
.global _start
.global __reset_exception
.global __undefined_instruction
.global __software_interrupt
.global __prefetch_abort
.global __data_abort
.global __not_used
.global __irq
.global __fiq
_start:
b __reset_exception
b __undefined_instruction
b __software_interrupt
b __prefetch_abort
b __data_abort
b __not_used
b __irq
b __fiq
.align 4
__reset_exception:
/* 开发板制锁*/
ldr r0, = 0xe010e81c
ldr r1, = 0x301
str r1, [r0]
/* 关闭看门狗 */
ldr r0, = 0xe2700000
mov r1, #0
str r1, [r0]
/* 设置SVC栈地址 */
ldr sp, = 0xd0037d80
/* 设置0x30000000为异常向量表的起始地址 */
ldr r0, = _start
mcr p15, 0, r0, c12, c0, 0
/* 启动icache */
bl enable_icache
/* 设置时钟 */
bl init_clock
/* 初始化DDR */
bl sdram_init
/* 创建页表 */
bl create_page_table
/* 使能mmu */
bl enable_mmu
/* 拷贝代码到ddr的0x30000000位置 */
bl copy2sdram
/* 清bss段 */
bl clear_bss
/* 从片内iram跳转到ddr */
ldr pc, = sdram
sdram:
bl uart0_init
und_code:
.word 0xdeadc0de /* 该指令在指令表中不存在,所以会译码错误 */
swi 0x33 /* 触发软中断 */
/* 开irq中断 */
mrs r0, cpsr
bic r0, r0, #1<<7
msr cpsr, r0
ldr sp, = 0x45000000
/* 调用main函数 */
bl main
b .
enable_icache:
mrc p15, 0, r1, c1, c0, 0 @Read Control Regist
orr r1, r1,#(1<<12) @enable instructon cache
//bic r1, r1,#(1<<12)
mcr p15, 0, r1, c1, c0, 0
mov pc, lr
enable_mmu:
/* translation table base write cp5 */
ldr r1, = 0x4f000000
mrc p15, 0, r2, c2, c0, 0 @ Read Translation Table Base Register
orr r2, r2, r1
mcr p15, 0, r2, c2, c0, 0 @ Write Translation Table Base Register
/* set domain 0xffffffff */
ldr r0, = 0xffffffff
mcr p15, 0, r0, c3, c0, 0 @ Read Domain Access Control Register
/* enable i/d canche */
mrc p15, 0, r1, c1, c0, 0 @Read Control Regist
orr r1, r1,#(1<<12) @enable instructon cache
orr r1, r1,#(1<<2) @enable data cache
orr r1, r1,#(1<<0) @enable mmu
mcr p15, 0, r1, c1, c0, 0 @write Control Regist
mov pc, lr
/* 未定义指令异常的处理 */
__undefined_instruction:
/* 执行到这里之前:
* 1. lr 保存被异常的下一条即将执行的指令的地址
* 2. SPSR保存被异常大断的模式的CPSR
* 3. CPSR中的m4-m0被设置为11011,进入__undefined模式
* 4. 调到0xd0037404的地方执行程序
*/
/* undefine的sp未设置,先设置它 */
ldr sp, = 0x31000000
/* 在undef异常可能会修改r0-r12,先保存 */
stmfd sp!, {r0-r12, lr}
mrs r0, cpsr
and r0, r0, #0x1f
bl undefine_instruct_exception
@ldmfd sp!, {r0-r12, pc}^
ldmfd sp!, {r0-r12, lr}
movs pc,lr
.align 4
/* 软中断异常的处理 */
__software_interrupt:
/* 执行到这里之前:
* 1. lr 保存被异常的下一条即将执行的指令的地址
* 2. SPSR保存被异常大断的模式的CPSR
* 3. CPSR中的m4-m0被设置为11011,进入__undefined模式
* 4. 调到0xd0037404的地方执行程序
*/
/* undefine的sp未设置,先设置它 */
ldr sp, = 0x32000000
/* 在undef异常可能会修改r0-r12,先保存 */
stmfd sp!, {r0-r12, lr}
mrs r0, cpsr
and r0, r0, #0x1f
bl software_interrupt_exception
ldmfd sp!, {r0-r12, pc}^
.align 4
__prefetch_abort:
.align 4
__data_abort:
.align 4
__not_used:
.align 4
/* 中断的处理 */
__irq:
ldr sp, = 0x33000000
stmfd sp!, {r0-r12, lr}
bl irq_excepion
ldmfd sp!, {r0-r12, lr}
subs pc,lr,#4
.align 4
__fiq:
.align 4
上面的代码主要说明这么几点:
1.默认启动ARM核CPU的异常向量表的起始地址在0x0位置,但硬件厂商都自己做了扩充,提供了各种启动机制等等,它们的代码也就放置在0地址处开始,而且是出厂就已经烧录好的,通常只做了中断的跳转,即发生中断断后,在0地址+irq偏移执行跳转,先让程序跳转到0x30037400+irq偏移的位置,然后让用户自己在0x30037400+irq偏移的位置,放置中断处理函数的地址。
2.上面的例子做了未定义指令异常和软中断异常,因为三星出厂并没有处理这两个异常,所以我们要用,就要自己重新定义异常向量表的地址了。我上面程序的链接脚本.text段地址在0x3000,0000,所以通过设置cp15的c12寄存器的值为0x3000,0000中,在以后发生异常就会直接到0x3000,0000位置执行相应后续的处理。
3.细心的朋友注意到了irq中断处理完后,返回地址是先减4,后返回,而未定义指令异常,软中断等则没有,这是什么原因呢。(分析下面这位朋友的博文就清楚了)
https://blog.csdn.net/Setul/article/details/53992920
linux的异常处理其实也和裸机中的流程一样,只不过linux要对所有的异常都进行具体分析处理。
先看一下异常向量表在启动过程的流程所在的位置
head_common.S
b start_kernel(void);
....
local_irq_disable(); /* 关中断 */
....
setup_arch(&command_line);
paging_init(mdesc);
devicemaps_init(mdesc);
early_trap_init(vectors); /* 设置异常向量表 */
....
trap_init(); /* 空函数 */
....
early_irq_init(); /* 初始化irq_desc数组 */
init_IRQ(); /* 芯片相关的中断的初始化
....
local_irq_enable(); /* 开中断 */
开关中断比较好理解,比较异常向量表还没设置,所以此时发生中断肯定就跑飞了,所以要关中断
1.异常向量表的建立
void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
/* 异常向量表 exception vectors的基址 */
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;
/*
* Poison the vectors page with an undefined instruction. This
* instruction is chosen to be undefined for both ARM and Thumb
* ISAs. The Thumb version is an undefined instruction with a
* branch back to the undefined instruction.
* 上面官方注释写的很明白,即用一条未定义指令初始化异常向量所在这一页(4K)
*/
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;
/*
* 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.
*/
/* 拷贝异常向量表到vectors起始地址 */
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
/* 拷贝异常处理核心代码到异常向量表base+0x1000位置 */
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
/* 从用户空间可以访问的内核段提供的用户代码拷贝到固定位置 */
kuser_init(vectors_base);
/* 刷异常向量这两页的,如果icache中存有相关代码的话(因为刚设置,所以要用新内存的,不能用老的icache里面的) */
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT); /* 清理权限管理 */
#else /* ifndef CONFIG_CPU_V7M */
/*
* on V7-M there is no need to copy the vector table to a dedicated
* memory area. The address is configurable and so a table in the kernel
* image can be used.
*/
#endif
}
虽然我们知道上面的vectors_base是0xffff,0000.但还是看一下代码
可以看到其实这里是这里只是早期的申请2页的空间而已。并不一定是在虚拟地址0xffff,0000
看一下真正是怎么放到高端地址去的。
static void __init devicemaps_init(const struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void *vectors;
/*
* Allocate the vector page early.
*/
vectors = early_alloc(PAGE_SIZE * 2); /* 申请2*4k地址的空间 */
early_trap_init(vectors); /* 在这块空间填充异常相关的地址,代码等 */
for (addr = VMALLOC_START; addr; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
/*
* Map the kernel if it is XIP.
* It is always first in the modulearea.
*/
#ifdef CONFIG_XIP_KERNEL
map.pfn = __phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK);
map.virtual = MODULES_VADDR;
map.length = ((unsigned long)_etext - map.virtual + ~SECTION_MASK) & SECTION_MASK;
map.type = MT_ROM;
create_mapping(&map);
#endif
/*
* Map the cache flushing regions.
*/
#ifdef FLUSH_BASE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS);
map.virtual = FLUSH_BASE;
map.length = SZ_1M;
map.type = MT_CACHECLEAN;
create_mapping(&map);
#endif
#ifdef FLUSH_BASE_MINICACHE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M);
map.virtual = FLUSH_BASE_MINICACHE;
map.length = SZ_1M;
map.type = MT_MINICLEAN;
create_mapping(&map);
#endif
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
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); /* 物理地址到虚拟地址的应设 */
if (!vectors_high()) { /* 判断如果是低地址映射,则覆盖掉前面高地址的映射 */
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/* Now create a kernel read-only mapping */
map.pfn += 1;
map.virtual = 0xffff0000 + PAGE_SIZE;
map.length = PAGE_SIZE;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
/*
* Ask the machine support to map in the statically mapped devices.
*/
if (mdesc->map_io)
mdesc->map_io();
else
debug_ll_io_init();
fill_pmd_gaps();
/* Reserve fixed i/o space in VMALLOC region */
pci_reserve_io();
/*
* Finally flush the caches and tlb to ensure that we're in a
* consistent state wrt the writebuffer. This also ensures that
* any write-allocated cache lines in the vector page are written
* back. After this point, we can start to touch devices again.
*/
local_flush_tlb_all();
flush_cache_all();
}
通过上面可以发现,起始并不是直接到0xffff,0000复制异常的内容。
而是通过下面三步实现的:
1.系统先申请一块内存,用异常向量表填充这个内存区域。
2.通过虚拟地址找到物理地址,并把这块内存区域再次映射到0xffff,0000区域
3.之后检查,如果不是高端映射,则映射到0地址(页表会覆盖掉前面的高端映射)
其中检查是否是高端映射是通过检查cp15的c1寄存器的V位来判断的
#if __LINUX_ARM_ARCH__ >= 4
#define vectors_high() (get_cr() & CR_V)
#else
#define vectors_high() (0)
#endif
#ifdef CONFIG_CPU_CP15
extern unsigned long cr_alignment; /* defined in entry-armv.S */
static inline unsigned long get_cr(void)
{
unsigned long val;
asm("mrc p15, 0, %0, c1, c0, 0 @ get CR" : "=r" (val) : : "cc");
return val;
}
其中cp15的c1寄存器是在proc-v7.S中设置的.
arch/arm/mm/proc-v7.S
2.异常向量表的内容
arch/arm/kernel/entry-armv.S
对比手册,位置是一样的,唯一就是swi异常是直接跳到0xFFFF,1000位置(即我们前面搬移向量表时也搬移了__stubs_start开始的代码段)
__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
.data
.globl cr_alignment
cr_alignment:
.space 4
#ifdef CONFIG_MULTI_IRQ_HANDLER
.globl handle_arch_irq
handle_arch_irq:
.space 4
#endif
异常的初步跳转分析
/*
* Vector stubs.
*
* This code is copied to 0xffff1000 so we can use branches in the
* vectors, rather than ldr's. Note that this code must not exceed
* a page size.
*
* Common stub entry macro: 其中中断可以在用户模式,也可以在系统模式进入
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.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 先把lr和r0入栈
mrs lr, spsr /* 读出spsr */
str lr, [sp, #8] @ save spsr cpsr也保存到栈中
@
@ Prepare for SVC32 mode. IRQs remain disabled.强制进入SVC模式
@
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 /* lr在前面存的是spsr的值,这里只保留低四位 */
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp /* 栈地址存在r0中传给下一个函数 */
ARM( ldr lr, [pc, lr, lsl #2] ) /* 以pc为基址,spsr的低四位*4(左移2bit)为偏移 ,找到对应的表地址 */
movs pc, lr @ branch to handler in SVC mode 调到表中执行处理异常
ENDPROC(vector_\name) /* 宏的结束位置代码 */
.align 2
@ handler addresses follow this label
1:
.endm
.section .stubs, "ax", %progbits
__stubs_start: /* 异常处理,也即将被搬运到0xFFFF,1000开始位置 */
@ This must be the first word 这句注释说的很明白,因为软中断指令是W(ldr) pc, __vectors_start + 0x1000,
发生后是直接跳到0xFFFF,1000处,而__stubs_start被搬移到0xFFFF,1000位置,所以vector_swi 必须在first word
/* 这条汇编的入口在其它地方定义,即发生软中断(系统调用)后,先到0xFFFF,0008,再跳到0xFFFF,1000
* ,之后再次跳到vector_swi 标号处执行软中断异常
*/
.word vector_swi
vector_rst:
ARM( swi SYS_ERROR0 ) /* 复位异常,我们这里还是执行软中断异常 */
THUMB( svc #0 )
THUMB( nop )
b vector_und
/*
* Interrupt dispatcher 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)
.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
/*
* Data abort dispatcher 数据异常
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub dabt, ABT_MODE, 8 /* 这句用最前面的宏替换 */
.long __dabt_usr @ 0 (USR_26 / USR_32)
.long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
.long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
.long __dabt_svc @ 3 (SVC_26 / SVC_32)
.long __dabt_invalid @ 4
.long __dabt_invalid @ 5
.long __dabt_invalid @ 6
.long __dabt_invalid @ 7
.long __dabt_invalid @ 8
.long __dabt_invalid @ 9
.long __dabt_invalid @ a
.long __dabt_invalid @ b
.long __dabt_invalid @ c
.long __dabt_invalid @ d
.long __dabt_invalid @ e
.long __dabt_invalid @ f
/*
* Prefetch abort dispatcher 预取址异常
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub pabt, ABT_MODE, 4 /* 这句用最前面的宏替换 */
.long __pabt_usr @ 0 (USR_26 / USR_32)
.long __pabt_invalid @ 1 (FIQ_26 / FIQ_32)
.long __pabt_invalid @ 2 (IRQ_26 / IRQ_32)
.long __pabt_svc @ 3 (SVC_26 / SVC_32)
.long __pabt_invalid @ 4
.long __pabt_invalid @ 5
.long __pabt_invalid @ 6
.long __pabt_invalid @ 7
.long __pabt_invalid @ 8
.long __pabt_invalid @ 9
.long __pabt_invalid @ a
.long __pabt_invalid @ b
.long __pabt_invalid @ c
.long __pabt_invalid @ d
.long __pabt_invalid @ e
.long __pabt_invalid @ f
/*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/
vector_stub und, UND_MODE /* 未定义指令异常 */
.long __und_usr @ 0 (USR_26 / USR_32)
.long __und_invalid @ 1 (FIQ_26 / FIQ_32)
.long __und_invalid @ 2 (IRQ_26 / IRQ_32)
.long __und_svc @ 3 (SVC_26 / SVC_32)
.long __und_invalid @ 4
.long __und_invalid @ 5
.long __und_invalid @ 6
.long __und_invalid @ 7
.long __und_invalid @ 8
.long __und_invalid @ 9
.long __und_invalid @ a
.long __und_invalid @ b
.long __und_invalid @ c
.long __und_invalid @ d
.long __und_invalid @ e
.long __und_invalid @ f
.align 5
/*=============================================================================
* Address exception handler
*-----------------------------------------------------------------------------
* These aren't too critical.
* (they're not supposed to happen, and won't happen in 32-bit data mode).
*/
vector_addrexcptn: /* 没使用,进入这里直接死循环 */
b vector_addrexcptn
/*=============================================================================
* Undefined FIQs fiq内核没实现处理,需要我们自己实现代码,觉大多数情况下我们也不会用到
*-----------------------------------------------------------------------------
* Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
* MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
* Basically to switch modes, we *HAVE* to clobber one register... brain
* damage alert! I don't think that we can execute any code in here in any
* other mode than FIQ... Ok you can switch to another mode, but you can't
* get out of that mode without clobbering one register.
*/
vector_fiq:
subs pc, lr, #4 /* 到fiq后,直接返回,没有做任何处理 */
.globl vector_fiq_offset
.equ vector_fiq_offset, vector_fiq /* #define vector_fiq_offset vector_fiq */
.section .vectors, "ax", %progbits
__vectors_start: /* 异常向量表 */
W(b) vector_rst /* 复位异常 */
W(b) vector_und /* 未定义指令异常 */
W(ldr) pc, __vectors_start + 0x1000 /* 软中断swi异常 */
W(b) vector_pabt /* 预取址异常*/
W(b) vector_dabt /* 数据异常(比如访问了不能访问的地址) */
W(b) vector_addrexcptn /* 保留的,没使用,进入直接死循环 */
W(b) vector_irq /* 中断 */
W(b) vector_fiq /* 快速中断 */
.data
.globl cr_alignment
cr_alignment:
.space 4
#ifdef CONFIG_MULTI_IRQ_HANDLER
.globl handle_arch_irq
handle_arch_irq:
.space 4
#endif
上面就是内核的异常处理比较底层的代码了。
其中复位异常和swi都是用swi直接跳转到vector_swi,在vector_swi里面实现的。
而und,irq,pabt,dabt都是用宏+一系列跳转实现的,fiq未实现。
下面用我们最常用的irq异常,拆开上面的宏来分析
/**************************下面是宏*******************************/
.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 先把lr和r0入栈
mrs lr, spsr /* 读出spsr */
str lr, [sp, #8] @ save spsr cpsr也保存到栈中
@
@ Prepare for SVC32 mode. IRQs remain disabled.强制进入SVC模式
@
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 /* lr在前面存的是spsr的值,这里只保留低四位 */
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp /* 栈地址存在r0中传给下一个函数 */
ARM( ldr lr, [pc, lr, lsl #2] ) /* 以pc为基址,spsr的低四位*4(左移2bit)为偏移 ,找到对应的表地址 */
movs pc, lr @ branch to handler in SVC mode 调到表中执行处理异常
ENDPROC(vector_\name) /* 宏的结束位置代码 */
/***************************************************************************/
/*
* Interrupt dispatcher 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)
.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
贴出代码前,先把宏中的内容说明一下
.macro vector_stub, name, mode, correction=0
vector_stub irq, IRQ_MODE, 4
irq替换掉name
mode为IRQ_MODE(用宏定义,如下表的红框的低四位所示,irq为2)
4替换掉correction (如果不传这个参数,默认值是0)
其中为什么correction在每个模式下不同,如下表的return code。详细原因查看前面贴的那个链接。
替换后
/*
* Interrupt dispatcher irq异常
*/
vector_irq: /* 宏的起始位置代码 */
sub lr, lr, #4 /* 计算返回地址 */
stmia sp, {r0, lr} @ save r0, lr 先把lr和r0入栈
mrs lr, spsr /* 读出spsr */
str lr, [sp, #8] @ save spsr cpsr也保存到栈中
mrs r0, cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE) /* 强制进入SVC模式 */
msr spsr_cxsf, r0
and lr, lr, #0x0f /* lr在前面存的是spsr的值,这里只保留低四位(进入异常前的模式) */
mov r0, sp /* 栈地址存在r0中传给下一个函数 */
ARM( ldr lr, [pc, lr, lsl #2] ) /* 以pc为基址,spsr的低四位*4(左移2bit)为偏移 ,找到对应的表地址 */
movs pc, lr @ branch to handler in SVC mode 调到表中执行处理异常
/* pc = 当前指令 + 8,所以执行ldr lr, [pc, lr, lsl #2]时,pc指向 __irq_usr */
.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
看了上面的注释应该差不多理解了。
其中SPSR的低四位为模式位。
进入异常模式前要么是在user模式,要么是在supervisor模式,如果是其它模式则统一做无效处理。(因为其它异常也是在svc模式处理【前面代码强制,所有的异常处理在svc模式】)
所以无论是irq,und,dabr,pabt他们均只实现了pc位置0偏移【用户模式进入异常】和3<<2的偏移【管理模式进入异常】,以及无效模式处理程序。
如下简单列出的所有汇编代码的标号,就是armv7内核的所有的异常处理的有效代码标号:
__pabt_invalid:
...
__dabt_invalid:
...
__irq_invalid:
...
__und_invalid:
...
__dabt_svc:
...
__irq_svc:
...
__und_svc:
...
__pabt_svc:
...
__dabt_usr:
...
__irq_usr:
...
__und_usr:
...
__pabt_usr:
...
/* 上面的所有都在arch/arm/kernel/entry-armv.S中定义 */
/*下面这个在arch/arm/kernel/entry-common.S */
ENTRY(vector_swi)
因为太多,这里就不一一分析了。一下一节我以最常用到的irq异常的处理来分析。
后面再升入学习一段后,再以swi的来分析,基本就可以算掌握arm的异常处理了。