linux中断系统那些事之----中断初始化过程

来源:互联网 发布:简小数据统计图 编辑:程序博客网 时间:2024/05/16 17:08

linux中断向量的初始化

中断异常向量表的地址有两种:
异常向量表的加载地址: 就是在向量表加载到内存,但在运行之前的地址
异常向量表的运行地址: 实际运行时的中断向量表地址

中断异常向量表的基地址的确定:
在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0x00000000,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的地址的对应关系如下:

V=0        ~        0x00000000~0x0000001C

V=1        ~        0xffff0000~0xffff001C

linux内核目前都是将0xffff0000设置为中断向量表的开始地址


中断异常向量表的内容(arch/arm/kernel/entry-armv.S)
/* * We group all the following data together to optimise * for CPUs with separate I & D caches. */.align5.LCvswi:.wordvector_swi.globl__stubs_end__stubs_end:.equstubs_offset, __vectors_start + 0x200 - __stubs_start/**interruption ventor entry table*rubbitxiao*/.globl__vectors_start__vectors_start: ARM(swiSYS_ERROR0) THUMB(svc#0) THUMB(nop)W(b)vector_und + stubs_offsetW(ldr)pc, .LCvswi + stubs_offsetW(b)vector_pabt + stubs_offsetW(b)vector_dabt + stubs_offsetW(b)vector_addrexcptn + stubs_offsetW(b)vector_irq + stubs_offsetW(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的值全部注入spsr
ldmfd {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模式下专有

中断异常向量表的建立
该建立过程,就是将中断向量表从加载地址拷贝到运行地址(即0xffff0000)的过程,代码的执行过程如下:
start_kernel(init/main.c)
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);}        ... ...}



上述函数devicemaps_init中的early_trap_init函数内容如下(arch/arm/kernel/traps.c):
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=32bytememcpy((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);}

关于中断向量表中的相对偏移量的计算
即每条中断向量中,为什么都要加一个stubs_offset的偏移量呢?
具体见如下的向量表内容:
.equstubs_offset, __vectors_start + 0x200 - __stubs_start/**interruption ventor entry table*rubbitxiao*/.globl__vectors_start__vectors_start: ARM(swiSYS_ERROR0) THUMB(svc#0) THUMB(nop)W(b)vector_und + stubs_offset//为什么每条跳转指令都需要添加stubs_offset常量呢?        W(ldr)pc, .LCvswi + stubs_offsetW(b)vector_pabt + stubs_offsetW(b)vector_dabt + stubs_offsetW(b)vector_addrexcptn + stubs_offsetW(b)vector_irq + stubs_offsetW(b)vector_fiq + stubs_offset.globl__vectors_end__vectors_end:

首先异常向量中的跳转指令使用的是b而不是ldr指令的原因是因为b指令相较ldr指令更高效,但跳转的范围是相对当前pc值的,并且大小范围不超过正负32KB。关于arm寻址方式中的相对寻址的定义如下:相对寻址是一种特殊的基址寻址,特殊性是它把程序计数器PC中的当前值作为基地址,语句中的地址标号作为偏移量,将两者相加之后得到操作数的地址。
所以各异常处理程序跟异常向量表都是一起拷贝的,因为他们不能离的太远。
内核代码编译生成后,需要将异常向量表拷贝到指定位置(0x00000000 or 0xffff0000),这就需要将内核中的异常向量表设计成与位置无关的
异常向量表的拷贝过程用图表示比较清晰,如下图所示:

向量表搬移及offset偏移量计算示图
图一说明:上面两条有方向的横线,横线方向代表地址生长方向,下面那个是Code/Load视图,是搬移前的代码在生成的二进制内核中的组织情况,上面的Exec view是代码在内存中开始执行后的分配情况。
另外还需要叙说的是:offset = vector_dabt + stub_offset - t2,而t2即使当前pc的值,vector_dabt-t2的值根据相对寻址的概念,就是汇编代码指令中的
vector_dabt标号的值
编译器在汇编这条:W(b)vector_dabt + stubs_offset
指令时,会将vector_dabt标号赋值为:vector_dabt的绝对地址减去当前pc值。
原创粉丝点击