发布时间:2026-06-01阅读(0)
从前都在X86上分析内核,做开发、trouble shooting,对于其他架构了解较少,对于新架构的学习,甚至还有些抵触,这次趁分析问题的机会,顺便学习了一下ARM架构的基础知识,权当笔记。
这里主要是AArch32架构(即32位,后面就都简写成ARM了),相对比较简单,入门必备。
ARM处理器模式在引入安全扩展之前,ARM有7中处理器模式,其中6种是特权模式,剩下一种为用户程序运行的非特权模式。特权模式下,可以做一些user模式下不能做的操作,比如mmu配置和cache操作。
TrustZone Security Extensions引入了独立于模式的两种安全状态,和一种新的Monitor模式。如此就有8种CPU模式了。
TrustZone Security Extensions的环境中,通过将所有的硬件和软件资源按设备来划分,来提升系统安全性。 在CPU处于Normal (Non-secure) state,其不能访问在Secure state下分配的内存。
当前处理器所处模式,由CPSR(当前程序状态寄存器)寄存器的值决定,改变cpu模式可以通过特权软件显示设置该寄存器,也可以由异常触发(发生异常后,CPU就会自动切换到对应的模式)。
嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧。

需要的朋友私信【内核】即可领取
寄存器ARM架构提供16个32位的通用寄存器(R0-R15),R0-R14可用作普通的数据存储。
R15为PC寄存器,修改R15可以改变程序的执行流程。PC值指向当前执行指令的地址加8处,PC是有读写限制的。
R14也称LR(Link register),通常用于保存当前函数的返回地址(上一级函数),当其空闲时,也可以用作普通寄存器用。每种模式下都有自己独立(物理上)的R14。
R13也称SP,即用于保存堆栈的栈顶指针,当其空闲时,也可以用作普通寄存器用。每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈。当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
程序也可以访问CPSR,SPSR是前一个执行模式下CPSR的副本。
虽然软件可以访问这些寄存器,但是不同模式下,部分寄存器实际对应的物理存储位置可能不同,也就是说,不同模式下,部分寄存器(除R0-7和R15外)实际物理上是不同(虽然对软件来说是透明的,软件看到的是相同的逻辑上的寄存器),这些寄存器只能在相应模式下才能访问。
Program Status Registers。CPSR用于存储:flags、当前CPU模式、中断禁用标记、当前CPU状态等。在user模式下,CPSR对应的寄存器为APSR(限制版本的CPSR)。
Coprocessor 15CP15,系统控制协处理器,用于控制Core的需要特性,包括c0-c15主要的32位寄存器,这些寄存器通常也通过名称访问,如CP15.SCTLR
cache问题让程序执行的时间变得不可知。
对于实时性要求高的系统来说有点不可接受。
外设需要用up-to-date的数据,需要cache控制。
其他tag占物理空间,但不计算在cache size内
invalidate操作:丢掉cache。
clean操作:将脏数据刷入内存,保证一致。
ARM64中包含如下几种类型的异常:
ARM中,异常由级别之分,具体如下图所示,只要关注:
普通的用户程序处于EL0,级别最低
内核处于EL1,HyperV处于EL2,EL1-3属于特权级别。
异常处理Arm中的异常处理过程与X86比较相似,同样包括硬件自动完成部分和软件部分,同样需要设置中断向量,保存上下文,不同的异常类型的处理方式可能有细微差别。 这里不详述了。
需要关注:用户态(EL0)不能处理异常,当异常发生在用户态时,异常级别(EL)会发生切换,默认切换到EL1(内核态)。
中断向量表Arm64架构中的中断向量表有点特别(相对于X86来说~),包含16个entry,这16个entry分为4组,每组包含4个entry,每组中的4个entry分别对应4种类型的异常:
4个组的分类根据发生异常时是否发生异常级别切换、和使用的堆栈指针来区别。分别对应于如下4组:
## 中断向量表
Linux内核中,中断向量表实现在entry.S文件中,代码如下:
/* * Exception vectors. */.align11/*el1代表内核态,el0代表用户态*/ENTRY(vectors)ventryel1_sync_invalid// Synchronous EL1t ventryel1_irq_invalid// IRQ EL1tventryel1_fiq_invalid// FIQ EL1t/*内核态System Error ,使用SP_EL0(用户态栈)*/ventryel1_error_invalid// Error EL1tventryel1_sync// Synchronous EL1hventryel1_irq// IRQ EL1hventryel1_fiq_invalid// FIQ EL1h/*内核态System Error ,使用SP_EL1(内核态栈)*/ventryel1_error_invalid// Error EL1hventryel0_sync// Synchronous 64-bit EL0ventryel0_irq// IRQ 64-bit EL0ventryel0_fiq_invalid// FIQ 64-bit EL0/*用户态System Error ,使用SP_EL1(内核态栈)*/ventryel0_error_invalid// Error 64-bit EL0#ifdef CONFIG_COMPATventryel0_sync_compat// Synchronous 32-bit EL0ventryel0_irq_compat// IRQ 32-bit EL0ventryel0_fiq_invalid_compat// FIQ 32-bit EL0ventryel0_error_invalid_compat// Error 32-bit EL0#elseventryel0_sync_invalid// Synchronous 32-bit EL0ventryel0_irq_invalid// IRQ 32-bit EL0ventryel0_fiq_invalid// FIQ 32-bit EL0ventryel0_error_invalid// Error 32-bit EL0#endifEND(vectors)
可以明显看出分组和分类的情况。
invalid类处理带invalid后缀的向量都是Linux做未做进一步处理的向量,默认都会进入bad_mode()流程,说明这类异常Linux内核无法处理,只能上报给用户进程(用户态,sigkill或sigbus信号)或die(内核态)
带invalid后缀的向量最终都调用了inv_entry,inv_entry实现如下:
/* * Invalid mode handlers */ /*Invalid类异常都在这里处理,统一调用bad_mode函数*/.macroinv_entry, el, reason, regsize = 64kernel_entry el, \regsize/*传入bad_mode的三个参数*/movx0, sp/*reason由上一级传入*/movx1, #\reason/*esr_el1是EL1(内核态)级的ESR(异常状态寄存器),用于记录异常的详细信息,具体内容解析需要参考硬件手册*/mrsx2, esr_el1/*调用bad_mode函数*/bbad_mode.endm
调用bad_mode,是C函数,通知用户态进程或者panic。
/* * bad_mode handles the impossible case in the exception vector. */asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr){siginfo_t info;/*获取异常时的PC指针*/void __user *pc = (void __user *)instruction_pointer(regs);console_verbose();/*打印异常信息,messages中可以看到。*/pr_crit("Bad mode in %s handler detected, code 0xx -- %s\n",handler[reason], esr, esr_get_class_string(esr));/*打印寄存器内容*/__show_regs(regs);/*如果发生在用户态,需要向其发送信号,这种情况下,发送SIGILL信号,所以就不会有core文件产生了*/info.si_signo = SIGILL;info.si_errno = 0;info.si_code = ILL_ILLOPC;info.si_addr = pc;/*给用户态进程发生信号,或者die然后panic*/arm64_notify_die("Oops - bad mode", regs, &info, 0);}
arm64_notify_die:
void arm64_notify_die(const char *str, struct pt_regs *regs, struct siginfo *info, int err){/*如果发生异常的上下文处于用户态,则给相应的用户态进程发送信号*/if (user_mode(regs)) {current->thread.fault_address = 0;current->thread.fault_code = err;force_sig_info(info->si_signo, info, current);} else {/*如果是内核态,则直接die,最终会panic*/die(str, regs, err);}}
场景的场景中(不考虑EL2和EL3),IRQ处理分两种情况:用户态发生的中断和内核态发生的中断,相应的中断处理接口分别为:
el0_syncel1_sync
相应代码如下:
el1_sync:
.align6el1_irq:/*保存中断上下文*/kernel_entry 1enable_dbg#ifdef CONFIG_TRACE_IRQFLAGSbltrace_hardirqs_off#endif/*调用中断处理默认函数*/irq_handler/*如果支持抢占,处理稍复杂*/#ifdef CONFIG_PREEMPTget_thread_info tskldrw24, [tsk, #TI_PREEMPT]// get preempt countcbnzw24, 1f// preempt count != 0ldrx0, [tsk, #TI_FLAGS]// get flagstbzx0, #TIF_NEED_RESCHED, 1f// needs rescheduling?blel1_preempt1:#endif#ifdef CONFIG_TRACE_IRQFLAGSbltrace_hardirqs_on#endif/*恢复上下文*/kernel_exit 1ENDPROC(el1_irq)
代码非常简单,主要就是调用了irq_handler()函数,不做深入解析了,有兴趣可以自己再看看代码。
el0_sync处理类似,主要区别在于:其涉及用户态和内核态的上下文切换和恢复。
.align6el0_irq:kernel_entry 0el0_irq_naked:enable_dbg#ifdef CONFIG_TRACE_IRQFLAGSbltrace_hardirqs_off#endif/*退出用户上下文*/ct_user_exitirq_handler #ifdef CONFIG_TRACE_IRQFLAGSbltrace_hardirqs_on#endif/*返回用户态*/bret_to_userENDPROC(el0_irq)
Copyright © 2024 有趣生活 All Rights Reserve吉ICP备19000289号-5 TXT地图HTML地图XML地图