0 背景

        前面笔者在先导篇中介绍过嵌入式系统的7层架构【1】,并且介绍了第一阶段的引导启动【2】。本文笔者将深入到第一阶段的启动中,这部分的启动主要是对cpu的初始化,所需要的最核心的技术就是arm架构,arm架构是7层架构中第一个软件层次,向下它决定了芯片的设计,向上它为其他的层次提供软件编程的基础平台,在整个架构中扮演着举足轻重的角色。笔者从工程实践入手,结合自己的经验,从不同的视角对整个的arm体系结构进行总结。本文是文件视角下的arm体系结构的开篇-指令集,在文件视角下,arm体系端到端的文件笔者归结为指令集、汇编文件、elf文件和运行时文件4个不同形态,笔者将一一解读。

1 不同视角下的arm体系结构

        笔者在进行arm体系结构的主题调研时,发现当前对arm体系结构的介绍都是比较零散的,没有一个系统的总结,而且大多数都是知识的堆叠,没有从工程实践的角度,对我们日常开发中每一层可能用到的arm知识系统的进行归类。比如在某些问题场景(如踩内存)下需要进行反汇编分析,在某些性能优化的场景下,需要对编译选项进行优化,在遗留系统的切换到新的体系结构时需要进行整改,整改时需要哪些基本的知识等等,这些都是跟arm体系结构紧密相关的。 为此笔者对其进行了梳理,从不同的视角对arm体系结构进行总结,抽离出演进中的最基础的概念,抽象出最基本的知识,力图让这部分总结永不过时,在后续每一代的arm演进的时候,都能回过头来有所收获。

         如上图所示,是不同视角下的arm体系结构,存储的视角下,arm涉及到寄存器、总线和内存三个不同的角色,这是软件存储和执行的基础。文件视角下,在不同的阶段,arm会以不同的文件形式存在,arm最基本的构成元素是指令,这些指令通过不同的组合方式构成汇编文件,汇编文件经过编译生成elf文件,cpu就可以加载elf文件在寄存器中运行。arm工具链是将arm文件编译成arm核可以识别的文件的一套工具,在我们日常开发写makefile和build文件时接触到的arm最多的部分,其中涉及到编译工具链、编译选项、字节序等等。工程应用视角下系统的第一阶段的启动,引导初始化cpu,在问题定位时的反汇编定位法,在日常调试中的函数未定义问题的定位,这些都需要arm的基本知识做基础。在嵌入式七层架构的视角,笔者将从芯片、uboot、操作系统、中间件到最终的边缘计算,分别去总结其中涉及到的跟arm体系结构相关的知识,特别是边缘计算作为一种新的计算方式,现有的arm体系架构能否支撑,今年即将正式发布的armv9指令集将对边缘计算带来哪些机会和挑战,将是笔者重点探讨的方向。

2 文件视角下的arm指令集

        这一节,笔者重点探讨一下文件视角下的指令集,如下图所示,是指令集在这个主题下所处的位置,作为后续文件的基础,指令集是构成整个arm体系结构的核心,笔者将这里边包含的知识点以模块的方式分割,并按照上下的依赖关系整理如下。图中最上层是arm指令集版本信息,我们知道当前arm指令集已经演进到armv8,armv9的指令集也将在今年正式发布,无论后续如何演进,最基本的模块还是底下的这几个,arm指令流水线和指令、RISC、寄存器、体系结构。

        每个版本的arm指令基本都在上一个版本的基础上新增或修改,但是最基本的那几类指令是始终不变的,指令的格式也基本保持不变;指令的设计也始终延续RISC(精简指令集)的特点:程序和数据分离、指令简短、格式整齐划一,尽量少访问内存,中间结果和操作数都会预先存储在寄存器,提高指令的执行效率;寄存器作为这部分功能的核心实体部件,在指令中也会大量被使用;最后的体系结构,是程序员在为特定的处理器编制程序时,所能看到的程序中使用的资源及相互的关系,当前主要有CISC和RISC两大类体系结构,CISC即复杂指令集通常采用一个复杂的数据通路和一个微程序控制器,一般使用微程序来实现复杂指令集,比较有代表性的,如x86体系结构。

3 arm指令解读

        在嵌入式系统的设计和开发中,我们使用最多的是RISC,而arm指令集是以RISC为基础设计的,在整个的嵌入式系统中占的比重比较大,所以arm几乎是RISC的代名词。为何要基于这种精简指令集的设计模式,笔者理解主要是嵌入式设备的资源受限,采用RISC的方式可以使cpu核在芯片上占用更小的面积,芯片的尺寸和功耗也会变得更小,开发成本更低。

        如下图所示是arm指令和处理器之间的关系图,左半部分是arm指令,右半部分是处理器,处理器包含寄存器和处理器工作模式。arm指令在寄存器中执行,可以切换arm处理器到不同的工作模式,而每种模式又反过来访问控制每个寄存器。arm处理器的工作模式包括7种,进一步可分为三类:即中断、权限和异常。与中断有关的FIQ和RIQ,在系统启动第一阶段的引导中经常会看到其身影;与权限有关的USR、SYS和SVC,在操作系统内核态和用户态中经常会用到对其的理解;最后异常情况(数据访问异常或者是未定义的指令)下的工作模式,ABT和UND。这些模式可以通过软中断的方式相互间进行切换,在不同的工作模式下,对寄存器的访问也是受控的。

        从指令的格式上看arm指令集车处理器关系,每条指令的基本构成包含操作码和操作数,其中操作码来自于arm指令集中的各种指令,如加减乘除、逻辑运算等等;操作数则主要由寄存器构成,寄存器的类型分为通用寄存器和状态寄存器,几乎所有的指令都是按照这样的规律组织,因此当我们看到一段arm指令时,可以通过其操作码和寄存器类型,识别出指令完成的功能。当然还有一类指令并没有显式的给出寄存器,比如立即数,这个部分跟寻址方式有关。因此围绕指令格式,读者要关注寻址方式、寄存器和操作指令码三个关键构件,寻址方式决定了操作数(数据或者地址)的获取方式,寄存器决定了操作数的存储位置,操作指令码则决定了指令实现的功能。

        图中列举了一条最简单的指令ADD R0,R1,R2,实现的功能将R1和R2两个寄存器求和,然后将结果保存在寄存器R0中。指令在执行过程中,按照从右往左的顺序依次取值,并将最终的计算结果存放在最左边的寄存器上,这样的执行过程也是适用于大多数的指令执行过程。再进一步深探这条指令,就需要知道指令中每个操作码或者操作数的长度,这部分属于arm指令集中的工作状态部分,有arm和thumb两种工作状态,对应的指令长度分别是32/64位和16/32位,相互之间可以进行切换,我们日常编码中经常会听到要4字节对齐或者8字节对齐,可以提升代码性能,就是来源于该工作状态,除此以外在arm更新换代中,往往要兼容新老设备,也要注意32位和64位系统在编码、编译上的差异。从指令存储格式上看,当前主要有大端序和小端序两种存储格式,所谓大端序即高位存在低地址的地方,低位存在高地址的地方,小端序则是低位存在低地址的地方,高位存在高地址的地方,字节序的转换也是网络应用开发中经常会遇到的,就是跟指令的这个大小端的存储有关。至此这样一条arm指令我们就解读完成,笔者将上面一条指令中所包含的关键构件放到上图的左边,即arm指令中,其中包含指令类型、寻址方式、指令格式、存储格式和工作状态5个基本组件。下图进一步将这5个基本组件的内部构成细分,勾勒出一张相对完整的arm指令地图,无论指令集的版本如何演进,该图中的基本概念是不变的,这张图将会伴随笔者后续解读汇编文件。

4 小结

        本文选择一个全新的视角去审视arm体系结构,即从文件视角下,对arm体系进行解读。由一条指令入手,以小见大,抽离出构成一条arm指令的基本组件。后续笔者将从指令集构成的下一类文件-汇编文件入手,以uboot的初始化cpu的文件为例,继续解读arm体系结构。敬请期待!

参考文献:

【1】(17条消息) 嵌入式系统那些事-先导篇_linus_ben的博客-CSDN博客

【2】(17条消息) 嵌入式系统那些事-第一阶段引导启动_linus_ben的博客-CSDN博客

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: