当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。在异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行。

说明

Kernel版本:4.14.111
ARM处理器,Contex-A7

ARM中异常中断的种类

  ARM体系中的异常中断下图所示
在这里插入图片描述

ARM异常中断向量表

在这里插入图片描述
  ARM的异常中断向量表可以是高端向量表,也可以是低端向量表,两者取其一。区别是基地址不同。高端向量是ARM架构可选配置,可以通过硬件外部输入管脚来配置是低端向量还是高端向量,不能通过指令来改变向量的位置,但如果ARM芯片内部有标准ARM协处理器,那么协处理器CP15的寄存器C1的bit13可以用来切换低端和高端向量地址,等于0时为低端向量,等于1时为高端向量。
  Linux内核分用户空间、内核空间,通常32位处理器,用户空间0-3G,内核空间3-4G,所以Linux内核使用高端向量表。

内核异常向量表

  Linux内核在链接时,_vectors_start和__vectors_end之间保存了异常向量表。__stubs_start和__stubs_end 保存了异常处理的函数。查看链接文件vmlinux.lds.S文件,可以看到:

	/*
	 * The vectors and stubs are relocatable code, and the
	 * only thing that matters is their relative offsets
	 */
	__vectors_start = .;
	.vectors 0xffff0000 : AT(__vectors_start) {			(1)
		*(.vectors)
	}
	. = __vectors_start + SIZEOF(.vectors);
	__vectors_end = .;

	__stubs_start = .;
	.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {
		*(.stubs)										(2)
	}
	. = __stubs_start + SIZEOF(.stubs);
	__stubs_end = .;

1)链接时,将.vectors段内容链接到虚拟地址0xffff0000地址。(这里我理解为在vmlinux镜像中.vectors段连续,夹在__vectors_start和__vectors_end 中间,但是链接的虚拟地址指向0xffff0000)
2)同上。
  arch/arm/kernel/entry-armv.S 中.vectors段保存了异常向量表。

	.section .vectors, "ax", %progbits
.L__vectors_start:
	W(b)	vector_rst
	W(b)	vector_und
	W(ldr)	pc, .L__vectors_start + 0x1000
	W(b)	vector_pabt
	W(b)	vector_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_irq
	W(b)	vector_fiq

  异常向量表,相当于保存了发生异常情况时,需要跳转的指令。
  考虑一个问题,为什么硬件跳转异常向量表,如vector_irq,就会跳转到vector_irq标号处执行;为什么能跳到呢?相当于函数指针效果?
  看一下b跳转的指令格式:
bit[31:28]:条件码
bit[27:25]:101
bit24:是否链接标识
bit[23:0]:跳转的偏移量
  b跳转是一个相对跳转,依赖于当前的PC值和label相对于当前PC值的偏移量,这个偏移量在编译链接的时候就已经确定了,会存在b跳转指令机器码的bit[23:0],是24bit有符号数;因为ARM指令是word对齐的,最低2bit永远为0;所以左移两位后表示有效偏移的而是26bit的有符号数,也就是可以向前和向后都可以跳转32MB的范围。
  vmlinux.lds.S中,链接时的虚拟地址已经确定了,当建立完页面映射,异常向量表拷贝后,当异常来临时,就能通过向量表找到正确的label处执行了。

异常向量表跳转

  根据上文异常向量表,有异常发生时,就可以跳转了,但是跳转到哪里呢?有些函数在代码中找不到。比如vector_irq。

vector_srub宏

	.macro	vector_stub, name, mode, correction=0
	.align	5

vector_\name:					//定义了一个vector_name的label,如果参数name是irq,那就是vector_irq  
	.if \correction
	sub	lr, lr, #\correction	//如果要修正lr PC指针,它是返回地址 
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr			//r0 lr入栈
	mrs	lr, spsr										//此刻spsr值为。
	str	lr, [sp, #8]		@ save spsr			//保存spsr寄存器值(中断前的cpsr)此时irq栈的内容为r0,lr,cpsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr										//cpsr寄存器赋值给r0
	eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)		//设置处理器模式,切换到svc32模式
	msr	spsr_cxsf, r0							//处理后的r0值赋值给spsr寄存器

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f							//中断前的cpsr已保存到lr,获取中断前的模式,usr or svc
 THUMB(	adr	r0, 1f			)
 THUMB(	ldr	lr, [r0, lr, lsl #2]	)
	mov	r0, sp									//sp保存到r0,切换模式后sp会变成对应模式的sp,所以这里要保存
 ARM(	ldr	lr, [pc, lr, lsl #2]	)			//根据lr获取的中断前的模式,左移两位,获取偏移量。在加上当前pc位置,得到新的lr,即中断处理函数地址。
	movs	pc, lr			@ branch to handler in SVC mode		// irq/fiq中断向量表正好紧接当前指令之后,即pc等价于irq/fiq中断向量表基地址,
																//lr为中断前模式,pc + lr * 4即得到对应模式的中断入口函数地址,
																//例如__irq_usr、__irq_svc,从不同模式进入中断,处理流程有所不同,此处跳转到对应模式的中断处理程序
																//在PC指针跳转的时候,会切换到svc32模式。
ENDPROC(vector_\name)

	.align	2
	@ handler addresses follow this label
1:
	.endm

  下面看一下.stub段包含的内容:

	.section .stubs, "ax", %progbits
	@ This must be the first word
	.word	vector_swi

vector_rst:
 ARM(	swi	SYS_ERROR0	)
 THUMB(	svc	#0		)
 THUMB(	nop			)
	b	vector_und

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

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

/*=============================================================================
 * FIQ "NMI" handler
 *-----------------------------------------------------------------------------
 * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86
 * systems.
 */
	vector_stub	fiq, FIQ_MODE, 4

	.long	__fiq_usr			@  0  (USR_26 / USR_32)
	.long	__fiq_svc			@  1  (FIQ_26 / FIQ_32)
	.long	__fiq_svc			@  2  (IRQ_26 / IRQ_32)
	.long	__fiq_svc			@  3  (SVC_26 / SVC_32)
	.long	__fiq_svc			@  4
	.long	__fiq_svc			@  5
	.long	__fiq_svc			@  6
	.long	__fiq_abt			@  7
	.long	__fiq_svc			@  8
	.long	__fiq_svc			@  9
	.long	__fiq_svc			@  a
	.long	__fiq_svc			@  b
	.long	__fiq_svc			@  c
	.long	__fiq_svc			@  d
	.long	__fiq_svc			@  e
	.long	__fiq_svc			@  f

	.globl	vector_fiq

  当用户空间(usr模式)发生外部中断时,会跳转到__irq_usr处执行,内核空间(svc模式)发生异常中断时,会跳转到__irq_svc处执行。
  除了fiq外,每个异常向量只有usr和svc有入口,而其他都是invalid,是因为linux只会从usr(application)和svc(kernel)两种mode跳转到exception。为什么只会从这两种mode跳转呢?因为linux异常前的状态;要么是内核态处于svc模式,执行__xxx_svc代码;要么是用户态处于usr模式,执行__xxx_usr代码。
  vector_rst和vector_swi比较特殊,没有使用vector_stub宏定义。
  rst说明是系统出错,用软件中断SYS_ERROR0来处理;
  swi是跳到软中断,vector_swi在arch/arm/kernel/entry-common.S中实现,主要是系统调用相关。这里就不展开描述了。

注意:arm的8中异常向量和7种工作模式不是一一对应的,但是存在关联。向量0是reset,如果是cpu运行到了向量0说明是系统出错,用软件中断SYS_ERROR0来处理;向量2也是跳到软中断;软中断会陷入svc模式。向量3和4都会陷入abt模式。在调用vector_stub 宏时,都已经提前设定好了。

内核启动建立异常向量表

  从上文描述可以看到,内核异常向量表在0xffff0000-0xffff0fff这4KB空间,具体处理函数在0xffff1000-0xffff1fff。从System.map中也可以看到处理函数符号
在这里插入图片描述
  那么内核启动过程中,必然要给这2个page分配物理内存,建立映射,并把vmlinux中的异常向量表拷贝到对应的物理页面中。
start_kernel->setup_arch->paging_init->devicemaps_init

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);		(1)

	early_trap_init(vectors);					(2)
	...

1)申请2个page大小内存
2)调用early_trap_init拷贝异常向量表和处理函数
start_kernel->setup_arch->paging_init->devicemaps_init->early_trap_init

void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
	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.
	 */
	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.
	 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);	//拷贝内核当前__vectors_end - __vectors_start中间内容到申请的第一个页面中。即异常向量表。未拷贝前向量表存在内核镜像中。
	memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);//拷贝内核当前__stubs_end - __stubs_start中间内容到申请的第二个的页面中。即异常向量处理函数。

	kuser_init(vectors_base);

	flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#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
}

  完成拷贝异常向量表和处理函数拷贝后,我们知道,申请的内存虚拟地址是随机的,不可能是我们需要的地址0xffff0000。所以需要对这两个页面进行映射,映射到0xffff0000开始的地址。所以继续来看devicemaps_init函数。

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);		(1)

	early_trap_init(vectors);					(2)
	...
		/*
	 * 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);					//将vectors页面映射到0xffff0000
	

	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);					//将stubs页面映射到0xffff1000地址。
}

  拷贝完成后,当异常发生时,硬件跳转到异常向量表地址,就不会发生找不到页表的情况了。

Logo

更多推荐