创建公司网站免费,企业网站seo诊断报告,中国关键词,购物车网站建设文章目录 一、异常简介1.1 Exception levels1.2 异常类型 二、系统调用简介2.1 SVC指令2.2 VBAR2.3 系统调用保存现场2.4 系统调用返回 三、Linux 内核分析参考资料 一、异常简介
在ARM64体系架构中#xff0c;异常是处理器在执行指令时可能遇到的不寻常情况或事件。这些异常… 文章目录 一、异常简介1.1 Exception levels1.2 异常类型 二、系统调用简介2.1 SVC指令2.2 VBAR2.3 系统调用保存现场2.4 系统调用返回 三、Linux 内核分析参考资料 一、异常简介
在ARM64体系架构中异常是处理器在执行指令时可能遇到的不寻常情况或事件。这些异常可以是由软件或硬件引发的。ARM64体系架构定义了一套异常模型用于处理和响应这些异常情况。
1.1 Exception levels
Armv8-A体系结构定义了一组异常级别EL0到EL3其中
● 如果ELn是异常级别则n的值增加表示软件执行权限增加。 ● 在EL0执行称为无特权执行。EL0为非特权模块应用层。 ● 在EL1执行称为特权特权执行。EL1为特权模块操作系统内核层。 ● EL2提供对虚拟化的支持。 ● EL3支持在两种安全状态安全状态和非安全状态之间切换。
EL0 Applications.
EL1 OS kernel and associated functions that are typically described as privileged.
EL2 Hypervisor.
EL3 Secure monitor.一个armv8系统实现可能不包括所有的Exception级别。所有实现都必须包括EL0和EL1。EL2和EL3是可选的。
从上面可看到armv8最大支持EL0EL3四个exception levelEL0的execution privilege最低EL3的execution privilege最高。当发生异常的时候系统的exception会迁移到更高的exception level或者维持不变但是绝不会降低。此外不会有任何的异常会去到EL0。
1.2 异常类型
在Linux ARM64架构中同步异常和异步异常与处理器的异常处理机制和中断控制器有关。
1同步异常Synchronous Exceptions 在ARM64架构中同步异常与当前指令的执行直接相关它们在指令执行期间同步地引发。常见的同步异常包括
●未定义指令异常当尝试执行在ARM64架构中未定义的指令时会引发该异常。包括在不适当的异常级别执行指令、禁用的指令、以及未被分配的指令位模式。
●非法执行状态异常当PSTATE.IL非法执行状态设置为1时尝试执行指令会引发该异常。
●堆栈指针SP未对齐异常当堆栈指针未按照对齐要求进行对齐时会触发该异常。
●程序计数器PC未对齐异常当尝试执行具有错误对齐的指令时会引发该异常。
●引发异常的指令特定指令如SVC、HVC或SMC可能触发异常。
●被捕获指令异常当尝试执行被定义为在较高异常级别被捕获的指令时会引发该异常。
●内存相关异常由内存地址转换系统引发的异常包括与指令执行或内存访问相关的指令异常和数据异常。
●数据地址未对齐异常当尝试使用错误对齐的地址访问内存时会触发该异常。
●调试异常与调试相关的各种异常例如断点指令异常、断点异常、观测点异常、向量捕获异常和软件单步异常。
●被捕获的浮点异常如果支持在支持浮点异常捕获的实现中当发生被捕获的IEEE浮点异常时会引发该异常。
对于同步异常Linux内核会根据异常类型执行相应的异常处理程序例如调用适当的异常处理函数、进行错误处理或发送信号给相关进程。
2异步异常Asynchronous Exceptions 在ARM64架构中异步异常是与当前指令执行无直接关联的异常它们以异步的方式引发。常见的异步异常包括
在Armv8-A架构中被带入AArch64状态的异步异常也被称为中断。中断分为两种类型 物理中断Physical interrupts这些是从处理器外部发送给处理器的信号。它们包括
SError系统错误中断。
IRQ普通中断请求。
FIQ快速中断请求。虚拟中断Virtual interrupts软件在EL2执行时可以启用和挂起的中断。虚拟中断从EL0或EL1传递到EL1。 虚拟中断的名称与物理中断对应
vSError虚拟系统错误中断。
vIRQ虚拟普通中断请求。
vFIQ虚拟快速中断请求。对于异步异常ARM64架构中的处理方式通常是由中断控制器来管理和处理。中断控制器会根据中断优先级和配置将异步事件传递给处理器并触发相应的中断处理程序。
二、系统调用简介
2.1 SVC指令
系统调用属于异常的同步软件异常。
系统调用是通过执行SVC、HVC或SMC指令触发的这里我们指讨论SVC指令 默认情况下执行SVC指令会生成一个Supervisor Call这是一个针对EL1的同步异常。这为在EL0执行的软件提供了一种调用在EL1执行的操作系统或其他软件的机制。
在ARM64体系架构中EL0是用户空间EL1是内核空间。
SVC指令提供了在不同执行级别EL之间进行通信和交互的机制。通过执行SVC指令EL0中的用户空间程序可以请求EL1中运行的操作系统或其他软件执行特权操作。
SVC Generate exception targeting Exception level 1Supervisor Call causes an exception to be taken to EL1.2.2 VBAR
当处理器处于使用AArch64执行状态时当处理器执行到一个异常级别Exception level引发异常时执行将被强制转移到异常向量exception vector所指示的地址上。异常向量表vector table位于该异常级别的内存中占据一系列以字对齐的地址。
在ARMv8-A体系结构中每个异常级别都有一个关联的向量基地址寄存器Vector Base Address RegisterVBAR它定义了该异常级别对应的异常基地址。向量基地址决定了异常向量表在特定异常级别下的起始地址。当异常发生时处理器使用与当前异常级别相关联的VBAR寄存器来查找相应的向量表并跳转到该表中对应的异常向量。
通过为每个异常级别使用独立的向量表和VBAR寄存器ARMv8-A体系结构在不同特权级别下的异常处理中提供了灵活性和定制性。该机制能够高效处理异常和中断确保在不同情况下的适当处理和恢复。
处理器在用户空间EL0执行系统调用时即执行svc指令发生了异常处理器跳转和执行相关的异常处理指令。异常相关的处理指令存储在一个表中即异常向量表。对于ARM64体系架构EL1对应一个异常向量表其地址存放在向量基址寄存器VBAR_EL1, Vector Base Address Register 中。
对于AArch64状态的异常the vector table提供以下信息 以下是关于异常的一些信息 1异常类型
— Synchronous exception.
— SError.
— IRQ.
— FIQ2异常级别和相关信息 异常发生的异常级别指示异常发生的特权级别例如EL0用户级别、EL1操作系统级别等。 正在使用的堆栈指针用于跟踪异常处理期间的堆栈操作。 寄存器文件的状态指示异常发生时寄存器文件中各个寄存器的值和状态。 说明 (1)这个表有四个异常条目每个异常条目有四种异常类型同步异常Synchronous exceptionIRQFIQ和SError。 (2)每一个异常入口占用0x80 bytes空间每一个异常入口可以放置多32条指令。ARMv8指令集支持64位指令集但每一条指令的位宽是32位而不是64位。
0x80(128) / 4 32 四个异常条目 (1)如果发生异常并不会导致exception level切换并且使用的栈指针是SP_EL0对应着第一个异常条目。 (2)如果发生异常并不会导致exception level切换并且使用的栈指针是SP_EL1/2/3对应着第二个异常条目。 (3)如果发生异常会导致exception level切换并且比目的exception level低一级的exception level运行在AARCH64模式对应着第三个异常条目。 (4)如果发生异常会导致exception level切换并且比目的exception level低一级的exception level运行在AARCH32模式对应着第四个异常条目。
对于AARCH64模式异常指令svc处理器会导致exception level切换异常等级从EL0切换到EL1对应着第三个异常条目如果发生异常会导致exception level切换并且比目的exception level低一级的exception level运行在AARCH64模式那么使用第三个异常条目。
Lower Exception level, where the implemented level immediately lower than the target level is using AArch64.b对于异常指令svc属于同步异常Synchronous exception因此svc异常处理器会跳转到 VBAR_EL1 0x400 地址处的异常向量中。
2.3 系统调用保存现场
当系统调用从用户态到内核态的时候首先要做的第一件事情就是将用户态运行过程中的 CPU 上下文保存起来其实主要就是保存在这个结构的寄存器变量里。这样当从内核系统调用返回的时候才能让进程在刚才的地方接着运行下去。
应用层程序执行svc系统调用指令时会将用户态此时所有通用寄存器的状态保存起来以便从系统调用返回时恢复状态。将进程用户态此时所有通用寄存器的状态保存在该进程的内核栈中的pt_regs栈框中。
在内核栈的最高地址端存放的是另一个结构 pt_regs这个结构体保存着进程从应用层进入到内核层时用户态寄存器的状态。 struct pt_regs结构体
/** This struct defines the way the registers are stored on the stack during an* exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for* stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.*/
struct pt_regs {union {struct user_pt_regs user_regs;struct {u64 regs[31];u64 sp;u64 pc;u64 pstate;};};u64 orig_x0;
#ifdef __AARCH64EB__u32 unused2;s32 syscallno;
#elses32 syscallno;u32 unused2;
#endifu64 orig_addr_limit;/* Only valid when ARM64_HAS_IRQ_PRIO_MASKING is enabled. */u64 pmr_save;u64 stackframe[2];
};如下图所示 关于进程内核栈请参考Linux 进程管理之内核栈和struct pt_regs
2.4 系统调用返回
当操作系统的异常处理这里只描述svc异常完成后执行一条ERET指令就可以从异常返回。寄存器ELR_ELx存放svc指令异常返回的地址发生系统调用svc指令时系统肯定是在用户空间的地址将svc指令的下一条指令的地址保存在寄存器ELR_ELx中当系统调用返回时即执行ERET指令时返回svc异常指令现场从寄存器ELR_ELx取出svc指令的下一条指令的地址执行该指令。执行ERET指令时会从寄存器ELR_ELx恢复PC指针。
三、Linux 内核分析
1汇编入口
// linux-5.4.18/arch/arm64/kernel/entry.S/** Exception vectors.*/.pushsection .entry.text, ax.align 11
ENTRY(vectors)kernel_ventry 1, sync_invalid // Synchronous EL1tkernel_ventry 1, irq_invalid // IRQ EL1tkernel_ventry 1, fiq_invalid // FIQ EL1tkernel_ventry 1, error_invalid // Error EL1tkernel_ventry 1, sync // Synchronous EL1hkernel_ventry 1, irq // IRQ EL1hkernel_ventry 1, fiq_invalid // FIQ EL1hkernel_ventry 1, error // Error EL1hkernel_ventry 0, sync // Synchronous 64-bit EL0kernel_ventry 0, irq // IRQ 64-bit EL0kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0kernel_ventry 0, error // Error 64-bit EL0#ifdef CONFIG_COMPATkernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
#elsekernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
#endif
END(vectors)Linux 内核使用vectors作为异常向量表刚好和arm官方手册Vector Base Address Register (VBAR)的相对应。
前面说到对于同步异常指令svc处理器会跳转到 VBAR_EL1 0x400 地址处的异常向量中。 VBAR_EL1 存放的是vectors的基地址。
一个表项128字节十六进制即 0x80。
偏移异常描述Current Exception level with SP_EL00x00kernel_ventry 1, sync_invalid // Synchronous EL1t0x80kernel_ventry 1, irq_invalid // IRQ EL1t0x100kernel_ventry 1, fiq_invalid // FIQ EL1t0x180kernel_ventry 1, error_invalid // Error EL1tCurrent Exception level with SP_ELx, x00x200kernel_ventry 1, sync // Synchronous EL1h0x280kernel_ventry 1, irq // IRQ EL1h0x300kernel_ventry 1, fiq_invalid // FIQ EL1h0x380kernel_ventry 1, error // Error EL1hLower Exception level, where the implemented level immediately lower than the target level is using AArch640x400kernel_ventry 0, sync // Synchronous 64-bit EL00x480kernel_ventry 0, irq // IRQ 64-bit EL00x500kernel_ventry 0, fiq_invalid // FIQ 64-bit EL00x580kernel_ventry 0, error // Error 64-bit EL0Lower Exception level, where the implemented level immediately lower than the target level is using AArch320x600kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL00x680kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL00x700kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL00x780kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
vectors的基地址 0x400
kernel_ventry 0, sync // Synchronous 64-bit EL0// linux-5.4.18/arch/arm64/kernel/entry.S/** EL0 mode handlers.*/.align 6
el0_sync:kernel_entry 0mrs x25, esr_el1 // read the syndrome registerlsr x24, x25, #ESR_ELx_EC_SHIFT // exception classcmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit stateb.eq el0_svccmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0b.eq el0_dacmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0b.eq el0_ia......在el0_sync汇编函数中首先通过kernel_entry保存异常现场。然后从esr_el1寄存器中读取异常类型EC当异常类型为ESR_ELx_EC_SVC64是跳转到 el0_svc 汇编函数。
// linux-5.4.18/arch/arm64/kernel/entry.S/** SVC handler.*/.align 6
el0_svc:gic_prio_kentry_setup tmpx1mov x0, spbl el0_svc_handlerb ret_to_user
ENDPROC(el0_svc)el0_svc 汇编函数跳转到 el0_svc_handler 函数。
2C语言入口
// linux-5.4.18/arch/arm64/kernel/syscall.casmlinkage void el0_svc_handler(struct pt_regs *regs)
{el0_svc_common(regs, regs-regs[8], __NR_syscalls, sys_call_table);
}// linux-5.4.18/arch/arm64/include/asm/ptrace.h/** This struct defines the way the registers are stored on the stack during an* exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for* stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.*/
struct pt_regs {union {struct user_pt_regs user_regs;struct {u64 regs[31];u64 sp;u64 pc;u64 pstate;};};u64 orig_x0;......
};// linux-5.4.18/include/uapi/asm-generic/unistd.h#define __NR_syscalls 436// linux-5.4.18/arch/arm64/include/asm/syscall.htypedef long (*syscall_fn_t)(const struct pt_regs *regs);// linux-5.4.18/arch/arm64/kernel/sys.cconst syscall_fn_t sys_call_table[__NR_syscalls] {[0 ... __NR_syscalls - 1] __arm64_sys_ni_syscall,
#include asm/unistd.h
};static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,const syscall_fn_t syscall_table[])
{regs-orig_x0 regs-regs[0];regs-syscallno scno;invoke_syscall(regs, scno, sc_nr, syscall_table);}static void invoke_syscall(struct pt_regs *regs, unsigned int scno,unsigned int sc_nr,const syscall_fn_t syscall_table[])
{long ret;if (scno sc_nr) {syscall_fn_t syscall_fn;syscall_fn syscall_table[array_index_nospec(scno, sc_nr)];ret __invoke_syscall(regs, syscall_fn);}regs-regs[0] ret;
}static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)
{return syscall_fn(regs);
}3流程简介
el0 svc--el1 vectors--kernel_ventry 0, sync // Synchronous 64-bit EL0--el0_sync--el0_svc--el0_svc_handler--el0_svc_common--invoke_syscall--__invoke_syscall--syscall_fn(regs)从系统调用表中sys_call_table根据系统调用号取出对应的系统调用回调函数然后去执行对应的系统调用回调函数。
参考资料
Linux 5.4.18 armv8手册
https://blog.csdn.net/luteresa/article/details/120263414 https://zhuanlan.zhihu.com/p/578252899 http://www.wowotech.net/238.html