闲话

从前都在X86上分析内核,做开发、trouble shooting,对于其他架构了解较少,对于新架构的学习,甚至还有些抵触,这次趁分析问题的机会,顺便学习了一下ARM架构的基础知识,权当笔记。

这里主要是AArch32架构(即32位,后面就都简写成ARM了),相对比较简单,入门必备。

ARM处理器模式

在引入安全扩展之前,ARM有7中处理器模式,其中6种是特权模式,剩下一种为用户程序运行的非特权模式。特权模式下,可以做一些user模式下不能做的操作,比如mmu配置和cache操作。

Mode Function Privilege
User (USR) Mode in which most programs and applications run Unprivileged
FIQ Entered on an FIQ interrupt exception IRQ Entered on an IRQ interrupt exception  
Supervisor (SVC) Entered on reset or when a Supervisor Call instruction (SVC)  
is executed    
Abort (ABT) Entered on a memory access exception Undef (UND) Entered when an undefined instruction executed  
System (SYS) Mode in which the OS runs, sharing the register view  

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 15

CP15,系统控制协处理器,用于控制Core的需要特性,包括c0-c15主要的32位寄存器,这些寄存器通常也通过名称访问,如CP15.SCTLR

Cache

ARM中的常见Cache架构如下:

cache问题

让程序执行时间变得不可知。

对于实时性要求高的系统来说有点不可接受。

外设需要用up-to-date的数据,需要cache控制。

其他

tag占物理空间,但不计算在cache size内

invalidate操作:丢掉cache。

clean操作:将脏数据刷入内存,保证一致。

Point of coherency and unification

For set/way based clean and invalidate, the operation is performed on a specific level of cache.For operations that use a virtual address, the architecture defines two conceptual points:

Point of Coherency (PoC)

For a particular address, the PoC is the point at which all blocks, for example,cores, DSPs, or DMA engines, that can access memory are guaranteed to see thesame copy of a memory location. Typically, this will be the main external systemmemory.

Point of Unification (PoU)

The PoU for a core is the point at which the instruction and data caches of the coreare guaranteed to see the same copy of a memory location. For example, a unifiedlevel 2 cache would be the point of unification in a system with Harvard level 1caches and a TLB for cacheing translation table entries. If no external cache ispresent, main memory would be the Point of unification.

ARM64中包含如下几种类型的异常:

  1. 中断(Interrupts),就是我们平常理解的中断,主要由外设触发,是典型的异步异常。 ARM64中主要包括两种类型的中断:IRQ(普通中断)和FIQ(高优先级中断,处理更快)。 Linux内核中好像没有使用FIQ,还没有仔细看代码,具体不详述了。
  2. Aborts。 可能是同步或异步异常。包括指令异常(取指令时产生)、数据异常(读写内存数据时产生),可以由MMU产生(比如典型的缺页异常),也可以由外部存储系统产生(通常是硬件问题)。
  3. Reset。复位被视为一种特殊的异常。
  4. Exception generating instructions。由异常触发指令触发的异常,比如Supervisor Call (SVC)、Hypervisor Call (HVC)、Secure monitor Call (SMC)

异常级别(EL)

ARM中,异常由级别之分,具体如下图所示,只要关注:

普通的用户程序处于EL0,级别最低

内核处于EL1,HyperV处于EL2,EL1-3属于特权级别。

异常处理

Arm中的异常处理过程与X86比较相似,同样包括硬件自动完成部分和软件部分,同样需要设置中断向量,保存上下文,不同的异常类型的处理方式可能有细微差别。 这里不详述了。

需要关注:用户态(EL0)不能处理异常,当异常发生在用户态时,异常级别(EL)会发生切换,默认切换到EL1(内核态)。

中断向量表

Arm64架构中的中断向量表有点特别(相对于X86来说~),包含16个entry,这16个entry分为4组,每组包含4个entry,每组中的4个entry分别对应4种类型的异常:

  1. SError
  2. FIQ
  3. IRQ
  4. Synchronous Aborts

4个组的分类根据发生异常时是否发生异常级别切换、和使用的堆栈指针来区别。分别对应于如下4组:

  1. 异常发生在当前级别且使用SP_EL0(EL0级别对应的堆栈指针),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL0级别对应的SP。 这种情况在Linux内核中未进行实质处理,直接进入bad_mode()流程。
  2. 异常发生在当前级别且使用SP_ELx(ELx级别对应的堆栈指针,x可能为1、2、3),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。
  3. 异常发生在更低级别且在异常处理时使用AArch64模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(非AArch32模式)。 这也是比较常见的场景。
  4. 异常发生在更低级别且在异常处理时使用AArch32模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch32执行模式(非AArch64模式)。 这中场景基本未做处理。

代码分析

## 中断向量表

Linux内核中,中断向量表实现在entry.S文件中,代码如下:

   /** Exception vectors.*/.align  11/*el1代表内核态,el0代表用户态*/ENTRY(vectors)ventry  el1_sync_invalid        // Synchronous EL1t ventry  el1_irq_invalid         // IRQ EL1tventry   el1_fiq_invalid         // FIQ EL1t/*内核态System Error ,使用SP_EL0(用户态栈)*/ventry el1_error_invalid       // Error EL1tventry el1_sync            // Synchronous EL1hventry   el1_irq             // IRQ EL1hventry   el1_fiq_invalid         // FIQ EL1h/*内核态System Error ,使用SP_EL1(内核态栈)*/ventry el1_error_invalid       // Error EL1hventry el0_sync            // Synchronous 64-bit EL0ventry el0_irq             // IRQ 64-bit EL0ventry el0_fiq_invalid         // FIQ 64-bit EL0/*用户态System Error ,使用SP_EL1(内核态栈)*/ventry   el0_error_invalid       // Error 64-bit EL0#ifdef CONFIG_COMPATventry   el0_sync_compat         // Synchronous 32-bit EL0ventry el0_irq_compat          // IRQ 32-bit EL0ventry el0_fiq_invalid_compat      // FIQ 32-bit EL0ventry el0_error_invalid_compat    // Error 32-bit EL0#elseventry  el0_sync_invalid        // Synchronous 32-bit EL0ventry el0_irq_invalid         // IRQ 32-bit EL0ventry el0_fiq_invalid         // FIQ 32-bit EL0ventry el0_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函数*/
.macro  inv_entry, el, reason, regsize = 64
kernel_entry el, \regsize
/*传入bad_mode的三个参数*/
mov x0, sp
/*reason由上一级传入*/
mov x1, #\reason
/*esr_el1是EL1(内核态)级的ESR(异常状态寄存器),用于记录异常的详细信息,具体内容解析需要参考硬件手册*/
mrs x2, esr_el1
/*调用bad_mode函数*/
b   bad_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 0x%08x -- %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);}
}

IRQ中断处理

场景的场景中(不考虑EL2和EL3),IRQ处理分两种情况:用户态发生的中断和内核态发生的中断,相应的中断处理接口分别为:

el0_sync
el1_sync

相应代码如下:

el1_sync:

   .align  6
el1_irq:/*保存中断上下文*/kernel_entry 1enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGSbl  trace_hardirqs_off
#endif/*调用中断处理默认函数*/irq_handler/*如果支持抢占,处理稍复杂*/
#ifdef CONFIG_PREEMPTget_thread_info tskldr w24, [tsk, #TI_PREEMPT]     // get preempt countcbnz    w24, 1f             // preempt count != 0ldr   x0, [tsk, #TI_FLAGS]        // get flagstbz x0, #TIF_NEED_RESCHED, 1f   // needs rescheduling?bl    el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGSbl  trace_hardirqs_on
#endif/*恢复上下文*/kernel_exit 1
ENDPROC(el1_irq)

代码非常简单,主要就是调用了irq_handler()函数,不做深入解析了,有兴趣可以自己再看看代码。

el0_sync处理类似,主要区别在于:其涉及用户态和内核态的上下文切换和恢复。

   .align  6
el0_irq:kernel_entry 0
el0_irq_naked:enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGSbl  trace_hardirqs_off
#endif/*退出用户上下文*/ct_user_exitirq_handler#ifdef CONFIG_TRACE_IRQFLAGSbl  trace_hardirqs_on
#endif/*返回用户态*/b    ret_to_user
ENDPROC(el0_irq)

原文地址: https://happyseeker.github.io/kernel/2016/03/05/ARM-Cortex-A-programming-mannual-notes.html

 

ARM Cortex-A 编程手册学习笔记相关推荐

  1. 《Linux高性能服务器编程》学习笔记

    <Linux高性能服务器编程>学习笔记 Linux高性能服务器编程 TCP/IP协议族 TCP/IP协议族体系结构以及主要协议 数据链路层 网络层 传输层 应用层 封装 分用 测试网络 A ...

  2. Java编程思想学习笔记-第11章

    <?xml version="1.0" encoding="utf-8"?> Java编程思想学习笔记-第11章 Java编程思想学习笔记-第11章 ...

  3. 【C#编程基础学习笔记】6---变量的命名

    2013/7/24 技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社区:www.credream.com [C#编程基础学习笔记]6---变量的命名 ----- ...

  4. 【C#编程基础学习笔记】4---Convert类型转换

    2013/7/24 技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社区:www.credream.com [C#编程基础学习笔记]4---Convert类型转换 ...

  5. 内存模型 linux,内存模型 - STM32F4 编程手册学习_Linux编程_Linux公社-Linux系统门户网站...

    STM32F4编程手册学习2_内存模型 1. 内存映射 MCU将资源映射到一段固定的4GB可寻址内存上,如下图所示. 内存映射将内存分为几块区域,每一块区域都有一个定义的内存类型,一些区域还有一些附加 ...

  6. 《基于GPU加速的计算机视觉编程》学习笔记

    <基于GPU加速的计算机视觉编程>学习笔记(1) 最近打算 准备工作 CUDA开发环境(主要是查看N卡的信息) 在WIN10下安装CUDA工具包 最近打算 在训练模型的时候,感觉电脑非常吃 ...

  7. Linux C编程一站式学习笔记2

    Linux C编程一站式学习笔记 chap2 常量.变量和表达式 本书以C99为标准 一.继续hello world 加入更多注释的hello world 可以用ctrl+(shift)+v复制到vi ...

  8. 熊猫的python小课_朋友圈里那个可爱的小熊猫Python编程的学习笔记,学编程,不难!...

    Print( ) 详细请见公众号文章,里面有让人印象深刻,无法忘记的灵魂画手的图画解释.朋友圈里那个可爱的小熊猫Python编程的学习笔记,学编程,不难!​mp.weixin.qq.com 人类有人类 ...

  9. GO 编程模式学习笔记——GO GENERATION

    GO编程模式学习笔记系列为学习陈皓的GO编程模式系列文章记录与心得. 原文链接:GO 编程模式:GO GENERATION Go语言代码生成主要还是用来解决编程泛型的问题.Go语言的类型检查有两种技术 ...

最新文章

  1. 论文阅读:CNN-RNN: A Unified Framework for Multi-label Image Classification
  2. android 过滤格式,android Intent.setType() 过滤图片,返回所有的文件类型
  3. Linux的项目中积累的实际工作技巧
  4. mybatis教程--延迟加载详解
  5. 施华洛世奇的少女水晶梦还能继续吗?
  6. 利用中值滤波而不是均值滤波去除椒盐噪声(脉冲噪声)
  7. 通过ISA发布服务器(二)
  8. linux ping raw socket -(signal),***之旅――原始套接字(Raw Socket)透析(3)--用Raw Socket实现Ping...
  9. Cassandra安装和初次使用
  10. QCC512x/302x笔记(3)-- 只改一行代码,实现串口输出调试log
  11. 计算机设备故障,计算机常见硬件故障及其原因
  12. CodeSmith(1):使用和语法简介
  13. 在spss中实现变量标准化Z值
  14. 简述autocad在测绘工程中的应用_AutoCAD在工程测绘制图中的应用
  15. 为什么我不断收到Java错误:错误:可以找到或加载类Dint?
  16. 跨越信息沟通的障碍,构建企业高效应用平台
  17. 利用arcmap提取河流中心线
  18. 远程桌面 多人同时 使用谷歌浏览器
  19. 5G图传 5G单兵 5G视频终端 无人机4G图传
  20. 酯化反应固体酸催化剂

热门文章

  1. c++中override的应用
  2. stringstream用法总结
  3. 详解平衡二叉树(AVL),红黑树与平衡二叉树的区别
  4. 为什么资本主义生产的一般趋势是资本有机构成的提高?2017-12-26
  5. 专栏 | 基于 Jupyter 的特征工程手册:特征选择(三)
  6. android 小球效果,Android开发实现跟随手指的小球效果示例
  7. python展示_python展示ppt
  8. 北大中文期刊目录_最新版语言学C刊及北大核刊投稿方式全收录
  9. Python 中 function(#) (X)格式 和 (#)在Python3.*中的注意
  10. 对“js变量作用域的疑问”的解答