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

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.
 */
	.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寄存器中。
每种处理器模式下各自的寄存器分布情况如下:
linux中断系统那些事之----中断初始化过程_第1张图片
在这里需要重点说明的是:
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=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);
}

关于中断向量表中的相对偏移量的计算
即每条中断向量中,为什么都要加一个stubs_offset的偏移量呢?
具体见如下的向量表内容:
	.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:

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

你可能感兴趣的:(linux中断系统)