日期:2014-05-16  浏览次数:20914 次

ARMv8 Linux内核异常处理过程分析


看了Linaro提供的开源ARMv8 Linux内核源码,发现ARMv8异常处理与ARMv7及之前的架构有所不同,简单分析。

LinaroARMv8工程:http://www.linaro.org/engineering/engineering-projects/armv8

1.1 Linux内核异常处理相关文件

Linux内核中,异常处理主要由两个文件完成,entry.S和traps.c,当然还有一些其它异常处理函数分布于fault.c, memory.c等等。entry.S包含异常的入口、进入异常处理C函数前的压栈、退出C函数前的出栈、一些fork函数相关的处理代码(暂不分析)、任务切换汇编处理过程(cpu_switch_to函数,暂不分析)。traps.c主要包含异常处理C函数。

本文主要分析entry.S,对于traps.c作简要介绍。

1.2 执行kernel_entry之前的栈



1.3 执行kernel_entry时的栈



1.4 执行kernel_exit 时的栈


1.5 entry.s代码分析

/*
 * Low-level exception handling code
 *
 * Copyright (C) 2012 ARM Ltd.
 * Authors:	Catalin Marinas <catalin.marinas@arm.com>
 *		Will Deacon <will.deacon@arm.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/init.h>
#include <linux/linkage.h>

#include <asm/assembler.h>
#include <asm/asm-offsets.h>
#include <asm/errno.h>
#include <asm/thread_info.h>
#include <asm/unistd.h>
#include <asm/unistd32.h>

/*
 * Bad Abort numbers
 *-----------------
 */
#define BAD_SYNC		0
#define BAD_IRQ		1
#define BAD_FIQ		2
#define BAD_ERROR	3

//根据该结构体内容
/*
struct pt_regs {			
	union {
		struct user_pt_regs user_regs;//结构体user_pt_regs和结构体pt_regs内容一样
		struct {					//共用体存储31个通用寄存器,外加sp,pc,pstate三个特殊寄存器
									//该结构体用于异常处理的压栈弹栈操作
			u64 regs[31];
			u64 sp;
			u64 pc;
			u64 pstate;
		};
	};
	u64 orig_x0;
	u64 syscallno;
};
*/


//S_FRAME_SIZE定义在asm-offsets.c中,DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs));
//即结构体pt_regs的大小,结构体pt_regs的定义见上面
//S_LR定义:DEFINE(S_LR,offsetof(struct pt_regs, regs[30]));
//即31号寄存器在结构体pt_regs中的偏移量
//阅读以下内容请参考图1 和图2
	.macro	kernel_entry, el, regsize = 64
	sub	sp, sp, #S_FRAME_SIZE - S_LR	// room for LR, SP, SPSR, ELR,见图2中sp'指向的位置			
	.if	\regsize == 32
	mov	w0, w0				// zero upper 32 bits of x0
	.endif
	/*
	 *.macro	push, xreg1, xreg2	//压栈两个寄存器
	 *stp	\xreg1, \xreg2, [sp, #-16]!	//注意!!!push指令也改变sp的值!!!
	 *.endm
	 */
	push	x28, x29		//进行压栈操作,push也是一个宏定义,因为ARMv8没有push指令,用stp代替
	push	x26, x27	
	push	x24, x25
	push	x22, x23
	push	x20, x21
	push	x18, x19
	push	x16, x17
	push	x14, x15
	push	x12, x13
	push	x10, x11
	push	x8, x9
	push	x6, x7
	push	x4, x5
	push	x2, x3
	push	x0, x1		//此时sp指向位置见图2中sp''
	.if	\el == 0		//如果异常级是el0,把el0的sp栈指针给x21寄存器
	mrs	x21, sp_el0
	.else
	add	x21, sp, #S_FRAME_SIZE	//如果异常级不是el0,把sp指针指向的地方加上pt_regs大小后的地址放入x21,
								//即指向没进入kernel_entry函数钱的sp指向的位置,见图2中x21指向的地址
	.endif
	mrs	x22, elr_el1			//把el1的lr寄存器给x22
	mrs	x23, spsr_el1			//把spsr给x23
	stp	lr, x21, [sp, #S_LR]	//把lr,x21寄存器存入sp+S_LR指向的地方
	stp	x22, x23, [sp, #S_PC]	//把lr,存入sp+s_PC指向的位置,用于异常返回

	/*
	 * Set syscallno to -1 by default (overridden later if real syscall).
	 */
	.if	\el == 0
	mvn	x21, xzr
	str	x21, [sp, #S_SYSCALLNO]
	.endif
	/*
	 * Registers that may be useful after this macro is invoked:
	 *
	 * x21 - aborted SP
	 * x22 - aborted PC
	 * x23 - aborted PSTATE
	*/
	.endm

	
	.macro	kernel_exit, el, ret = 0
	//把此时sp(即图2中sp'')+S_PC位置处开始的16字节内容分别给x21,x22
	//即把栈中存的x21和x22内容取出来
	ldp	x21, x22, [sp, #S_PC]		// l