linux中断向量的初始化
异常向量表的加载地址: 就是在向量表加载到内存,但在运行之前的地址
异常向量表的运行地址: 实际运行时的中断向量表地址
V=0 ~ 0x00000000~0x0000001C
V=1 ~ 0xffff0000~0xffff001C
linux内核目前都是将0xffff0000设置为中断向量表的开始地址
/*
* We group all the following data together to optimise
* for CPUs with separate I & D caches.
*/
.align 5
.LCvswi:
.word vector_swi
.globl __stubs_end
__stubs_end:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
/*
*interruption ventor entry table
*rubbitxiao
*/
.globl __vectors_start
__vectors_start:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
以上向量表的反汇编代码如下:
000002c4 <__stubs_end>:
2c4: ef9f0000 svc 0x009f0000
2c8: ea0000dd b 644 <__switch_to+0x19c>
2cc: e59ff410 ldr pc, [pc, #1040] ; 6e4 <__switch_to+0x23c>
2d0: ea0000bb b 5c4 <__switch_to+0x11c>
2d4: ea00009a b 544 <__switch_to+0x9c>
2d8: ea0000fa b 6c8 <__switch_to+0x220>
2dc: ea000078 b 4c4 <__switch_to+0x1c>
2e0: ea0000f7 b 6c4 <__switch_to+0x21c>
由以上可知中断向量表是一个跳转表,对应8个中断向量,每个异常向量都有固定的pc地址相对应,并且都对应个特定的处理器模式,而每种处理器模式又有各自特有的备份寄存器。他们的对应关系如下:
偏移量 中断类型 处理器的模式 0x00 复位 特权模式 1 0x04 未定义的指令 未定义指令终止模式 6 0x08 软件中断 特权模式 6 0x0C 指令预取终止 终止模式 5 0x10 数据访问终止 终止模式 2 0x14 保留 未使用 未使用 0x18 外部中断请求 IRQ模式 4 0x1C 快速中断请求 FIQ模式 3
以上异常向量表的基地址为0xffff0000,再加上各自的偏移量,就是各异常向量的虚拟地址,当异常产生时,硬件处理器会自动将各入口地址复制到pc寄存器中。
r0-r12,r15(pc),cpsr是各种处理器模式(除fiq模式)都公共的通用寄存器,而r13(sp),r14(lr)每个模式都有自己的备份寄存器,譬如当处理器从user模式切换到svc模式时,则是使用的管理模式自己私有的r13_svn和r14_svn寄存器,而不再使用用户模式的r13_user和r14_user寄存器,同理除用户模式外其他特权模式都有自己的cpsr寄存器。
在用户模式下是不能够操作特权模式下的私有寄存器的,而特权模式则可以操作用户模式下的r13_user和r14_user寄存器。
另外各种处理器模式是怎么切换的呢?
除用户模式外的其他6种模式称为特权模式,这些模式中,程序可以访问所有系统资源,也可以任意进行处理器模式的切换。处理器模式可以通过软件控制进行切换(直接设置CPSR寄存器的后五位就可以在6种特权模式之间互相切换),也可以通过外部中断或异常处理过程进行切换(例如,在USR模式下,发生中断后切换到IRQ模式)。
是通过^后缀来实现的,该后缀的含义如下:'^'是一个后缀标志,不能在User模式和Sys系统模式下使用该标志.该标志有两个存在目的:
1.对于LDM操作,同时恢复的寄存器中含有pc(r15)寄存器,那么指令执行的同时cpu自动将spsr拷贝到cpsr中
如:在IRQ中断返回代码中[如下为ads环境下的代码gliethttp]ldmfd {r4} //读取sp中保存的的spsr值到r4中msr spsr_cxsf,r4 //对spsr的所有控制为进行写操作,将r4的值全部注入spsrldmfd {r0-r12,lr,pc}^//当指令执行完毕,pc跳转之前,将spsr的值自动拷贝到cpsr中
2.数据的送入、送出发生在User用户模式下的寄存器,而非当前模式寄存器,如:
ldmdb sp,{r0 - lr}^;表示sp栈中的数据回复到User分组寄存器r0-lr中,而不是恢复到当前模式寄存器r0-lr当然对于User,System,IRQ,SVC,Abort,Undefined这6种模式来说r0-r12是共用的,只是r13和r14为分别独有,对于FIQ模式,仅仅r0-r7是和前6中模式的r0-r7共用,r8-r14都是FIQ模式下专有
setup_arch(arch/arm/kernel/setup.c)
paging_init
devicemaps_init(arch/arm/mm/mmu.c)
static void __init devicemaps_init(struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void *vectors;
/*
* Allocate the vector page early.
*/
vectors = early_alloc(PAGE_SIZE);//分配一个空闲的物理页
early_trap_init(vectors); //将中断向量拷贝到这个物理页
for (addr = VMALLOC_START; addr; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
... ... ... ...
/*
* 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));//将刚分配的并且已经拷贝了中断向量的物理页映射到0xffff0000地址,即为中断向量表的基地址
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map, false);//建立映射
if (!vectors_high()) {//如果不是使用的高端向量,则将物理页映射到0x00的位置。
map.virtual = 0;
map.type = MT_LOW_VECTORS;
create_mapping(&map, false);
}
... ...
}
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[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
vectors_page = vectors_base;
/*
* 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.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);//拷贝中断向量表到0xffff0000开始的位置,总计大小为4*8=32byte
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);//拷贝异常处理程序到0xffff0000+0x200的位置
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
* Do processor specific fixups for the kuser helpers
*/
kuser_get_tls_init(vectors);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),
sigreturn_codes, sizeof(sigreturn_codes));
memcpy((void *)(vectors + KERN_RESTART_CODE - CONFIG_VECTORS_BASE),
syscall_restart_code, sizeof(syscall_restart_code));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
/*
*interruption ventor entry table
*rubbitxiao
*/
.globl __vectors_start
__vectors_start:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset//为什么每条跳转指令都需要添加stubs_offset常量呢?
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end: