下面以ARM Cortex_M3裸核的启动代码为例,做一下简单的分析。首先,在启动文件中完成了三项工作:

    1、  堆栈以及堆的初始化

    2、  定位中断向量表

    3、  调用Reset Handler。

  在介绍之前,我们先了解一下ARM芯片启动文件中涉及到的一些汇编指令的用法。

  补充一下,其中DCD相当于C语言当中的&,定义地址。

1堆栈以及堆的初始化

1.1 堆栈的初始化

Startup_xxx.s中的堆栈初始化代码

  Stack_Size  EQU  0x00000400,这个语句相当于Stack_Size这个标号(标号:链接器的术语,下文中提到的所有“标号”,指的都是指的链接器中的标号)等于0x00000400相当于C语言中的#define  Stack_Size  0x00000400 ,也就是说此语句只是一个声明,并未分配地址。

  AREA    STACK, NOINIT, READWRITE, ALIGN=3,此语句定义了一个叫STACK的代码段,并指明8字节对齐(ALIGN = 3)。其中NOINIT表示未初始化,READWRITE表示可读可写,ALIGN = 3,即表示2^3 = 8,八字节对齐。

  Stack_Mem    SPACE   Stack_Size,为Stack_Mem分配Stack_Size大小的一块内存区域,注意这里分配的是RAM,即分配了大小为1KB的内存空间(0x00000400 = 1024)。

  __initial_sp ,紧跟着栈分配内存后,所以其为栈顶(满递减栈)。此标号有一层隐含的意思就是在M3中堆栈是满递减堆栈,因为它指定了堆栈指针位于堆栈的高地址(在Stack_Mem之后),具体如下图所示。

堆栈指针sp位置

  上图来自Cortex_M3的一个工程的xxx.map文件。可以看出栈的起始地址为0x20000c68,大小为1024字节(即0x00000400 = Stack_Size)。而堆栈指针的位置在0x20001068,其等于栈的起始地址0x2000c68+0x00000400,说明本系列的Cortex_M3微控制器的堆栈为满递减堆栈。

  所以__initial_sp为1KB空间栈的栈顶,栈主要用于局部变量和形参的调用过程的临时存储,属于编译器自动分配和释放的内存,所以这里需要注意如果你的函数所占的内存过大,那么这个空间应调整其大小但一定要小于内部SRAM的大小。堆是程序员空间是程序员进行分配和释放的,如果程序中未释放最后由系统回收。

1.2 堆的初始化

Startup_xxx.s中的堆初始化代码

  堆的初始化过程与堆栈的初始化相同。

2、中断向量表的初始化

中断向量表的初始化代码(部分)

PRESERVE8指定了以下的代码为8字节对齐,这是keil编译器的一个编程要求,对齐情况如下图所示:

xxx.list文件中的8字节对齐示意图

  THUMB指定了接下来的代码为THUMB指令集。

  AREA    RESET, DATA, READONLY,此语句声明RESET数据段。

  EXPORT  __Vectors,导出向量表标号,EXPORT作用类似于C语言中的extern。之后的代码就是为向量表分配存储区域。中断向量表从FLASH的0x00000000地址开始放置,以4个字节为一个单位,地址0存放的是栈顶指针(sp)的地址,0x00000004存放的是复位程序的地址,往后以此类推,这里我们只设置了一个Reset_Handler向量。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道C语言中的函数名就是一个地址。(由此我们知道,中断函数的函数名都已经知道了,我们在写对应的中断服务程序时,从对应的地址取服务例程的入口地址并跳入执行)。但是此处有一个要注意的,就是0号地址不是什么入口地址,而是给出的复位后的MSP的初值。

3、调用Reset Handler

调用Reset Handler的代码

  此段代码只完成了一个功能,引导程序进入__main。__main的具体行为在后面做具体描述。

  PROC与ENDP组合在汇编中定义了一段子函数。

用户堆栈的初始化

具体的堆栈以及堆的初始化行为

  这一部分也就是把初始化的堆栈地址赋值给单片机的对应寄存器以方便C程序进行分配释放使用。

4、其他代码

有一些芯片厂商对芯片的加密的加密级别的代码也会放在这里,芯片上电后会自动读取这一地址的值以确定芯片的加密方式。

5ARM芯片的启动过程详解

  接下来介绍__main函数的具体实现过程。

  首先在介绍__main函数之前,我们先了解一些关于ARM芯片在启动过程中的基本知识。

“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM中的.bin(.axf,.hex)映像(image)文件。

一个ARM程序包含3部分:RO,RW和ZI

RO 就是只读数据,是程序中指令和常量;

RW是可读写的数据,程序中已初始化变量;

ZI 是程序中未初始化的变量和初始化为0的变量。

简单理解就是:

RO就是readonly,RW就是read/write,ZI就是zero initial。

ARM芯片的启动过程详解

注意,以上的过程并非绝对的,不同的ARM架构或者是不同的代码以上的执行过程是不同的。

复位处理程序是在汇编器中编写的短模块,系统一启动就立即执行。复位处理程序最少要为应用程序的运行模式初始化堆栈指针。对于具有本地内存系统(如缓存、TCM、MMU和MPU)的处理器,某些配置必须在初始化过程的这一阶段完成。复位处理程序在执行之后,通常跳到__main以开始C库初始化序列。

__main中的__scatterload负责设置内存,而__rt_entry负责设置运行时的环境。__scatterload中负责把RO/RW(非零)输出段从装载域地址复制到运行域地址(执行代码和数据复制、解压缩),并完成ZI段运行域数据的0初始化工作。然后跳到__rt_entry设置堆栈和堆、初始化库函数和静态数据。然后,__rt_entry跳转到应用程序的入口main()。主应用程序结束执行后,__rt_entry将库关闭,然后把控制权交换给调试器。函数标签main()具有特殊含义。Main()函数的存在强制链接器链接到__main和__rt_entry中的代码。如果没有标记为main()的函数,则没有链接到初始化序列,因而部分标准C库功能得不到支持。

6、结合代码来看芯片启动过程

上电后硬件设置sp、pc,刚上电复位后,硬件会自动根据向量表地址找到向量表。

  在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:

    1、从地址 0x0000 0000 处取出 MSP 的初始值。

    2、从地址 0x0000 0004 处取出 PC 的初始值,这个值是复位向量, LSB 必须是 1。 然后从这个值所对应的地址处取指。

  硬件自动从0x0000 0000位置处读取数据赋给栈指针sp,然后从0x0000 0004位置处读取数据赋给pc指针,完成复位,结果为:

SP = 0x2000 1068

PC = 0x0000 011D

  这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是我们上面分析的Reset_Handler这个函数。

进入__main

  LDR   R0, =__main

  BX   R0

执行上两条指令,跳转到__main程序段运行,__main的地址是0x0000 0080,上一步指令pc = 0x0000 011D的地址没有对齐,硬件自动对齐到0x0000 011C,执行__main。

pc指针通过立即数寻址,跳转到0x0000 0081处执行,同上这里也会自动对齐到0x0000 0080处。

  在__scatterload函数中又会进入__scatterload_copy,在__scatterload_copy中进行代码搬运,主要是加载已经初始化的数据段和未初始化的数据段,同时还会初始化栈空间,即ZI段清零(其中搬运次数由代码中声明的变量类型和变量多少来决定)。

  然后会跳转到__rt_entry函数执行,__rt_entry是使用ARM C库的程序的起点。将所有分散加载区重新定位到其执行地址后,会将控制权传递给__rt_entry。如下图,在__rt_entry中主要实现如下几个功能:

  1、  设置用户的堆和堆栈

  2、  调用__rt_lib_init以初始化C库

  3、  调用main()

  4、  调用__rt_lib_shutdown以关闭C库

  5、  退出

  __rt_lib_init函数是库函数初始化函数,它与__rt_lib_shutdown配合使用。并且这个函数紧靠__rt_stackheap_init()后面调用,即紧跟堆和堆栈初始化后面调用,并且传递一个要用作堆的初始内存块。此函数是标准ARM库初始化函数,不能重新实现此函数。

  注意:最后两步是在程序退出main()函数的时候才会执行,而我们嵌入式程序一般都是死循环,所以基本上不会执行这两个过程。还有以上过程是针对使用标准C Library而言的,不包括使用MDK提供的microlib库的情况。

在__rt_entry_main中,用户程序就开始正式执行了(进入C的世界)。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。这也就是__main中做的事情。

7、最后关于microlib

Microlib 是缺省C库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行,因此microlib 进行了高度优化以使代码变得很小,当然它的功能相比缺省C库少,并且根本不具备某些ISO C特性。某些库函数的运行速度也比较慢,比如memcpy()。

Microlib与缺省C库之间的主要差异是:

  Microlib不符合ISO C 库标准。不支持,某些ISO特性,并且其他特性具有的功能也比较少;

  Microlib不符合IEEE754 二进制浮点算法标准;

  Microlib进行了高度优化以使代码变得很小;

  无法对区域设置进行配置。缺省C区域设置是唯一可用的区域设置;

  不能将main()声明为使用参数,并且不能返回内容;

  不支持stdio,但未缓冲的stdin、stdout和stderr除外;

  Microlib对C99函数提供有限的支持;

  Microlib不支持操作系统函数;

  Microlib不支持与位置无关的代码;

  Microlib不提供互斥锁来防止非线程安全的代码;

  Microlib不支持宽字符或多字节字符串;

  与stdlib不同,microlib不支持可选的单或双区内存模型。Microlib只提供双区内存模型,即单独的堆栈和堆区。

8、关于生成的xxx.map文件

想要更好的了解启动代码的运行机制,我们就有必要了解一下由Keil的链接器“armlink”生成的描述文件,即xxx.map文件。

目标文件的组成

上图即是armlink的链接器为测试代码生成的xxx.map文件中的一部分,其描述了镜像文件的组成信息,其中可以明显看到其由两部分构成:

User Code生成的目标文件

C Library生成的目标文件

可见我们在上文中所描述的启动过程中看到的__main、__rt_entry、__scartterload以及__rt_lib_init等,就是C library中的代码。

所以,我们每次烧录的可执行的ARM的bin文件中不仅有开发者编写的代码,还有C Library的代码。

上图为存放在RAM中的RW段。

以上就是CM3芯片的基本启动过程,有高手发现有低级错误,还望指正,谢谢!

个人博客更新地址:

https://blog.strongwong.top/posts/%E5%85%B3%E4%BA%8E-ARM-Cortex-M3-%E7%9A%84%E5%90%AF%E5%8A%A8%E6%96%87%E4%BB%B6%E5%88%86%E6%9E%90%E5%8F%8A%E5%88%86%E6%95%A3%E5%8A%A0%E8%BD%BD.html

关于ARM CM3的启动文件分析的更多相关文章

  1. STM32F4XX启动文件分析

    STM32F4XX启动文件分析 - STM32F4XX启动文件下载地址 导读:STM32F4XX启动文件的作用 初始化设置SP,即栈指针 初始化设置PC指针指向复位中断处理函数,即PC = Reset ...

  2. startup_LPC17XX.s 启动文件分析

    工程中startup_LPC17XX.s是M3的启动文件,启动文件由汇编语言写的,它的作用一般是下面这几个: 1)堆和栈的初始化 2)中断向量表定义 3)地址重映射及中断向量表的转移 4)设置系统时钟 ...

  3. 【AT91SAM3S】英蓓特EM-SAM3S开发板例子工程中的启动文件分析

    手上一块英倍特的EM-SAM3S开发板,拿到已经有一个月了.本来是做uLoong活动使用的板子,可当初由于不熟悉这个芯片,使用了STM32F4当作了替代.最近准备抽点时间折腾下这个板子. 这个板子的资 ...

  4. STM32启动过程--启动文件--分析

    一.概述 1.说明 每一款芯片的启动文件都值得去研究,因为它可是你的程序跑的最初一段路,不可以不知道.通过了解启动文件,我们可以体会到处理器的架构.指令集.中断向量安排等内容,是非常值得玩味的. ST ...

  5. Cortex-M3 .s启动文件分析

    1. 基本概念(CMSIS): Cortex Micro-controller Software Interface Standard,微控制器软件接口标准. 2. CMSIS标准的文件结构: a) ...

  6. Cortex-M系列内核 启动文件分析

    最近终于闲了下来了准备好好学习下Cortex-M3/M4系列处理器的架构,经过各种资料的折磨也没法对它的整个工作过程能有个完整的认知,最后看到一片博客打算从程序的运行过程开始探究,所以首先就找到了启动 ...

  7. keil MDK启动文件分析---基于LPC2100系列(其实都是相通的)

    转用MDK有一段时间了,越来越觉得MDK的强大,因为我之前都是用ADS1.2开发产品,所以更能体会到MDK的强大与易用性.MDK编译出来的代码与ADS1.2相比,代码量减少了很多,我的一个工程用ADS ...

  8. 关于启动文件分析的(MDK-ARM) 【转】

    ;******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** ;* File Name : star ...

  9. stm32f407启动文件分析

    ; Amount of memory (in bytes) allocated for Stack; Tailor this value to your application needs; < ...

随机推荐

  1. 基于阿里云 DNS API 实现的 DDNS 工具

    0.简要介绍 0.1 思路说明 AliDDNSNet 是基于 .NET Core 开发的动态 DNS 解析工具,借助于阿里云的 DNS API 来实现域名与动态 IP 的绑定功能.工具核心就是调用了阿 ...

  2. RabbitMQ访问控制

    Access Control (Authentication, Authorisation) in RabbitMQ 认证和授权这两个概念经常容易被混淆,甚至被互换使用.在RabbitMQ中这是错的, ...

  3. 一套能体现 RBAC 的表结构设计

    1.RBAC 概述 2.表结构设计 2.1.用户表 2.2.角色表 2.3.权限表 2.4.用户角色(关系)表 2.5.角色权限(关系)表 3.总结 1.RBAC 概述 RBAC(Role-Based ...

  4. MySQL/MariaDB表表达式(3):视图

    视图是表表达式的一种,所以它也是虚拟表.对视图操作的时候会通过语句动态的从表中临时获取数据. 1.创建.修改视图 CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED ...

  5. SpringBoot之打成war包部署到Tomcat

    正常情况下SpringBoot项目是以jar包的形式,正常情况下SpringBoot项目是以jar包的形式,并且SpringBoot是内嵌Tomcat服务器,所以每次重新启动都是用的新的Tomcat服 ...

  6. PPP中的PAP和CHAP的区别

    PAP PAP是简单认证,明文传送,客户端直接发送包含用户名/口令的认证请求,服务器端处理并回应. CHAP CHAP是加密认证,先由服务器端给客户端发送一个随机码 challenge,客户端根据 c ...

  7. hadoop小结

    测试小结:1.如果只需要对数据集进行过滤,筛选则只需要编写Mapper类,不需要Reduce类,此时要执行下面一条语句:job.setNumReduceTesk(0);2.如果需要对处理的数据进行分组 ...

  8. MySQL行转列

    IF(expr1,expr2,expr3) 如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 IF()的返回值为expr2; 否则 ...

  9. 【转载】ASP.NET工具类:文件夹目录Directory操作工具类

    在ASP.NET开发网站的过程中,有时候会涉及到文件夹相关操作,如判断文件夹目录是否存在.删除文件夹目录.创建文件.删除文件.复制文件夹等等.这一批有关文件目录的操作可以通过Directory类.Fi ...

  10. 12个敏捷过程的小提示Tips

    12个敏捷过程的小提示Tips 1. 可视化一切. 在团队里使用Scrum白板.同时走廊过道上也会挂上显示信息的白板,这些信息可以是公司战略.软件缺陷等等.可视化的好处是,员工经过这些白板时,能够了解 ...