异常向量表

异常向量表的映射和调用

在开始学习Interrupt子系统之前,需要了解的一些知识

1.异常向量表

向量表/页的工作方式是 CPU 将程序计数器和处理器状态存储在内部寄存器中,并将程序计数器放在相应的向量地址。向量表可以放在内存中的两个位置:地址 0x00000000 或地址 0xFFFF0000。该位置通过 CP15 控制寄存器 1 中的单个位来选择。Linux 支持将向量放在任一位置,但优先选择 0xFFFF0000。如果 MMU 关闭并且您与从地址 0x00000000 开始的物理内存有 1:1 映射,则使用地址 0x00000000 通常最有帮助。如果 MMU 处于打开状态(对我们来说是打开的),则使用的地址是虚拟地址,即使向量表也要经过 MMU 转换,并且通常使用内存中高位(地址 0xFFFF0000)的向量。

先看一下异常向量的构造

arch/arm/kernrl/entry-armv.S

	.section .vectors.bhb.bpiall, "ax", %progbits
	RELOC_TEXT_NONE
	W(b)	vector_rst
	W(b)	vector_bhb_bpiall_und
ARM(	.reloc	., R_ARM_LDR_PC_G0, .L__vector_bhb_bpiall_swi	)
THUMB(	.reloc	., R_ARM_THM_PC12, .L__vector_bhb_bpiall_swi	)
	W(ldr)	pc, .
	W(b)	vector_bhb_bpiall_pabt
	W(b)	vector_bhb_bpiall_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_bhb_bpiall_irq
	W(b)	vector_bhb_bpiall_fiq

在运行的时并不能保证这段代码的地址就是0xFFFF0000,所以就需要把这段代码所在的虚拟地址设置为0xFFFF0000,然后中断发生时,执行跳转指令 W(b) vector_bhb_bpiall_irq。

并且所处的段是 .vectors,下面还会提及。

arch/arm/kernrl/head.S 这是arm架构运行的第一个文件

	/*
	 * The following calls CPU specific code in a position independent
	 * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
	 * xxx_proc_info structure selected by __lookup_processor_type
	 * above.
	 *
	 * The processor init function will be called with:
	 *  r1 - machine type
	 *  r2 - boot data (atags/dt) pointer
	 *  r4 - translation table base (low word)
	 *  r5 - translation table base (high word, if LPAE)
	 *  r8 - translation table base 1 (pfn if LPAE)
	 *  r9 - cpuid
	 *  r13 - virtual address for __enable_mmu -> __turn_mmu_on
	 *
	 * On return, the CPU will be ready for the MMU to be turned on,
	 * r0 will hold the CPU control register value, r1, r2, r4, and
	 * r9 will be preserved.  r5 will also be preserved if LPAE.
	 */
	ldr	r13, =__mmap_switched		@ address to jump to after
						@ mmu has been enabled
	badr	lr, 1f				@ return (PIC) address
#ifdef CONFIG_ARM_LPAE
	mov	r5, #0				@ high TTBR0
	mov	r8, r4, lsr #12			@ TTBR1 is swapper_pg_dir pfn
#else
	mov	r8, r4				@ set TTBR1 to swapper_pg_dir
#endif
	ldr	r12, [r10, #PROCINFO_INITFUNC]
	add	r12, r12, r10
	ret	r12
1:	b	__enable_mmu
ENDPROC(stext)
	.ltorg

首先将 __mmap_switched 的地址放到 r13寄存器

然后跳转__enable_mmu去开启MMU

在启动MMU后回跳转执行到 __mmap_switched

	__INIT
__mmap_switched:

	...
	#ifdef CONFIG_KASAN
	bl	kasan_early_init
#endif
	mov	lr, #0
	b	start_kernel
ENDPROC(__mmap_switched)
	...

通过汇编跳转到了 start_kernel() init\main.c

start_kernel() 
	setup_arch()
		paging_init()
			devicemaps_init()

我们进入这个函数

static void __init devicemaps_init(const struct machine_desc *mdesc)
{
	/*
	 * Allocate the vector page early.
	 */
	vectors = early_alloc(PAGE_SIZE * 2);
	early_trap_init(vectors);
	...
	/*
	 * 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
	...
}
  • early_alloc(PAGE_SIZE * 2); 申请8k的内存
  • early_trap_init(vectors); 把异常向量表复制到这8k的位置

其余部分就是映射,将0xffff0000 ------> vectors

2.向量表的映射

进入early_trap_init()

void __init early_trap_init(void *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.
	 */
	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.
	 */
	copy_from_lma(vectors_base, __vectors_start, __vectors_end);
	copy_from_lma(vectors_base + 0x1000, __stubs_start, __stubs_end);

	kuser_init(vectors_base);

	flush_vectors(vectors_base, 0, PAGE_SIZE * 2);
}

可以看到将 __vectors_start copy到了这块物理地址上

那么 __vectors_start 是什么呢?这就要去内核查看链接脚本了

arch/arm/kernel/vmlinux.lds

__vectors_start = LOADADDR(.vectors); __vectors_end = LOADADDR(.vectors) + SIZEOF(.vectors); 

__vectors_start = LOADADDR(.vectors) 会获取这个段的地址,那么向量表刚好就在.vectors。到此就完成了向量表的复制。

3.调用过程

回到向量表,当发生异常时,会来调用 vector_bhb_bpiall_irq

/*
 * Interrupt dispatcher
 */
	vector_stub	irq, IRQ_MODE, 4
	...

而 vector_stub 是一个宏,展开为:

.macro	vector_stub, name, mode, correction=0
.align	5
...
vector_\name:
	.if \correction
	sub	lr, lr, #\correction
	.endif

	@ Save r0, lr_<exception> (parent PC)
	stmia	sp, {r0, lr}		@ save r0, lr

	@ Save spsr_<exception> (parent CPSR)
.Lvec_\name:
	mrs	lr, spsr					@从SPSR寄存器(当前程序状态寄存器)中获取打断的程序位于什么模式下
	str	lr, [sp, #8]		@ save spsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	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				@提取模式
 THUMB(	adr	r0, 1f			)
 THUMB(	ldr	lr, [r0, lr, lsl #2]	)
	mov	r0, sp
 ARM(	ldr	lr, [pc, lr, lsl #2]	)
	movs	pc, lr			 @执行
ENDPROC(vector_\name)

根据提取出来的模式来执行以下的函数

/*
 * Interrupt dispatcher
 */
	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

在用户态发生执行 __irq_usr

在内核态发生执行 __irq_svc,(在linux中,禁止在其他模式下产生中断)

最后来看一下这两个函数

arch/arm/kernrl/entry-armv.S

__irq_usr:

__irq_usr:
	usr_entry				@保存现场
	kuser_cmpxchg_check
	irq_handler from_user=1   @处理
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq  @恢复现场
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

__irq_svc:

__irq_svc:
	svc_entry					@保存现场
	irq_handler from_user=0		  @处理

#ifdef CONFIG_PREEMPTION
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	ldr	r0, [tsk, #TI_FLAGS]		@ get flags
	teq	r8, #0					   @ if preempt count != 0
	movne	r0, #0				   @ force flags to 0
	tst	r0, #_TIF_NEED_RESCHED
	blne	svc_preempt
#endif
	svc_exit r5, irq = 1			@恢复现场
 UNWIND(.fnend		)
ENDPROC(__irq_svc)

处理的流程都是一样的,而这里有个处理函数 irq_handler,它是由 GIC Driver 提供的。

	.macro	irq_handler, from_user:req
	mov	r1, sp
	ldr_this_cpu r2, irq_stack_ptr, r2, r3
	.if	\from_user == 0
	@
	@ If we took the interrupt while running in the kernel, we may already
	@ be using the IRQ stack, so revert to the original value in that case.
	@
	subs	r3, r2, r1		@ SP above bottom of IRQ stack?
	rsbscs	r3, r3, #THREAD_SIZE	@ ... and below the top?
#ifdef CONFIG_VMAP_STACK
	ldr_va	r3, high_memory, cc	@ End of the linear region
	cmpcc	r3, r1			@ Stack pointer was below it?
#endif
	bcc	0f			@ If not, switch to the IRQ stack
	mov	r0, r1
	bl	generic_handle_arch_irq      <----------------调用的函数
	b	1f

generic_handle_arch_irq 以后再做分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值