中国软件网

您现在的位置是:网站首页>软件百科

软件百科

Cortex-M3内核芯片进阶之启动过程详解

中国软件网2023-01-17软件百科voip软件
mac拼图软件,voip软件,past软件,Cortex-M3内核芯片进阶之启动过程详解,mac拼图软件,voip软件,past软件“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM中的bin映像(imag

Cortex-M3内核芯片进阶之启动过程详解

Cortex-M3内核芯片进阶之启动过程详解,

  mac拼图软件,voip软件,past软件“ARM 程序”是指在 ARM 系统中正在执行的程序,而非保存在 ROM 中的 bin 映像(image)文件。这一点清注意区别。

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

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

  __main 负责设置内存,而__rt_entry 负责设置运行时环境。 __main 执行代码和数据复制、解压缩以及 ZI 数据的零初始化。然后,它跳转到__rt_entry,设置堆栈和堆、初始化库函数和静态数据,并调用任何顶级 C++构造函数。然后, __rt_entry 跳转到应用程序的入口 main()。主应用程序结束执行后, __rt_entry 将库关闭,然后把控制权交还给调试器。函数标签 main()具有特殊含义。 main()函数的存在强制链接器链接到__main 和__rt_entry 中的初始化代码。如果没有标记为 main()的函数,则没有链接到初始化序列,因而部分标准 C 库功能得不到支持。

  使用 Keil for ARM( uVision4)的软件模拟,工程的硬件设定为 LPC1700 系列,不使用microlib。这里不使用 microlib,则系统自动加载标准 C Library,这样我们才能看到标准的 ARM芯片的标准启动过程。随后我们会对 microlib 进行探讨。测试代码如下:

  1.2.2跟踪启动代码开始调试之前,须将工程设置的 DEBUG 栏中取消掉 Run to main()的勾选,否则代码会直接运行到 main()函数,我们也就无法看到芯片的启动过程了。然后启动调试,最先进入 Reset_Handler,如下图所示。

  继续单步运行,程序跳入__main(C Library 的代码,并非用户代码),如下图所示。

  继续单步运行,经过一系列的代码(主要是 scatter load 过程)后,程序进入__rt_entry(同样是由 C Library 管理,并非用户代码)。

  继续单步运行,再次经过一系列的代码之后(主要是堆栈和堆的初始化以及 C Library 的初始化),程序进入 main()。如下图所示。

  以上是整个测试代码启动过程的跟踪调试的大致过程,这个过程对 ARM 系列的芯片来说都是相同的,不同的是里面具体的细节。

  1.2.3详细的启动过程使用的依然是上面的测试代码,详细启动过程如下图所示。

  上图显示的就是测试代码在 LPC17xx 上启动运行的全过程(详细过程),由此图可见LPC17xx 的启动过程与图 1.1 所示的启动过程是基本一致的,但是还有差别,可以说是图 1.1所示启动过程的简化版。注意:并非所有代码的启动过程全部相同,此启动过程与所使用的链接器、用户代码以及其所集成的C Library密切相关。此图为由“armlink”的链接器为程序清单1.2所示的测试代码产生的启动过程,其他情况可能会有一些差异(比如有的代码的启动过程就会运行一段RW段的解压代码等,而本例程中则没有)。

  若程序使用的是 C 或 C++语言编写的代码,那么 C/C++程序的入口是在 C Library 中的__main。库代码在此处执行以下操作。

  __rt_entry 符号是使用 ARM C 库的程序的起点。将所有分散加载区重定位到其执行地址后,会将控制权传递给__rt_entry。其有如下缺省实现:

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

  这是库初始化函数。它是紧靠__rt_stackheap_init()后面调用的,并传递一个要用作堆的初始内存块。此函数是标准 ARM 库初始化函数,不能重新实现此函数。

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

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

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

  所谓 ARM 映像文件就是指烧录到 ROM 中的 bin 文件,也成为 image 文件。以下用 Image文件来称呼它。 Image 文件包含了 RO 和 RW 数据(注意:不包含 ZI 数据)。之所以 Image 文件不包含 ZI 数据,是因为 ZI 数据都是 0,没必要包含,只要程序运行之前将 ZI 数据所在的区域(执行区域)一律清零即可。包含进去反而浪费存储空间。

  从以上两点可以知道,烧录到 ROM 中的 image 文件与实际运行时的 ARM 程序之间并不是完全一样的。因此就有必要了解 ARM 程序是如何从 ROM 中的 image 到达实际运行状态的。实际上, RO 中的指令(启动程序)至少应该有这样的功能:

  将 RW 从 ROM 中搬到 RAM 中,因为 RW 是变量,变量不能存在 ROM 中。

  将 ZI 所在的 RAM 区域全部清零,因为 ZI 区域并不在 Image 中,所以需要程序根据编译器给出的 ZI 地址及大小来将相应得 RAM 区域清零。 ZI 中也是变量,同理:变量不能存在 ROM 中。在程序运行的最初阶段, RO 中的指令(启动程序)完成了这两项工作后(也就是分散加载的过程) C 程序才能正常访问变量。否则只能运行不含变量的代码。说了这么多可能还是有些迷糊, RO, RW 和 ZI 到底是什么,下面我将给出几个例子,最直观的来说明 RO, RW, ZI在 C 语言中的含义。

  看下面两段程序,它们之间差了一条语句,这条语句就是声明一个字符常量。因此按照之前的内容,它们之间应该只会在 RO 数据中相差一个字节(字符常量为 1 字节)。

  以上两个程序编译出来后的信息可以看出: Prog1 和 Prog2 的 RO 包含了 Code 和 RO Data两类数据。他们的唯一区别就是 Prog2 的 RO Data 比 Prog1 多了 1 个字节。这正和之前的推测一致。如果增加的是一条指令而不是一个常量,则结果应该是 Code 数据大小有差别。

  同样再看两个程序,它们之间只相差一个“已初始化的变量”,按照之前所讲的,已初始化的变量应该是算在 RW 中的,所以两个程序之间应该是 RW 大小有区别。Prog3:

  以上两个程序编译出来后的信息可以看出: Prog1 和 Prog2 的 RO 包含了 Code 和 RO Data两类数据。他们的唯一区别就是 Prog2 的 RO Data 比 Prog1 多了 1 个字节。这正和之前的推测一致。如果增加的是一条指令而不是一个常量,则结果应该是 Code 数据大小有差别。

  再看两个程序,他们之间的差别是一个未初始化的变量“a”,从之前的了解中,应该可以推测,这两个程序之间应该只有 ZI 大小有差别。

  编译的结果完全符合推测,只有 ZI 数据相差了 1 个字节。这个字节正是未初始化的一个字符型变量“a”所引起的。

  注意: 如果一个变量被初始化为0,则该变量的处理方法与未初始化华变量一样放在ZI区域。即:ARM C程序中,所有的未初始化变量都会被自动初始化为0。以上代码是再ADS下编译的,keil环境下与之不同,比如在keil下生成ZI数据段就必须定义一个大于8字节的未初始化或初始化位0的变量,且必须在源代码中引用此变量才会在链接的描述文件中看到其生成的ZI文件。

  对于没有描述内存映射的映像,链接器根据缺省内存映射放置代码和数据。如下图所示。

  注意:这个内存映射并非对所有芯片都有效,不同的芯片的内存映射是不同的。在

  堆栈从内存区顶部向下增长。堆从内存区底部向上增长。这是缺省设置。由堆管理的内存从来不会缩减。不能将通过调用 free()释放的堆内存再次用于其他用途。

  一个内存区用于堆栈,另一个内存区用于堆。堆区大小可以是零。堆栈区可以位于分配的内存中,也可以从执行环境中继承。要使用双区模型而不是缺省的单区模型,请使用以下任一方法:

  如果使用双区内存模型,并且未提供任何堆内存,则无法调用 malloc()、使用 stdio 或获取main()的命令行参数。如果将堆区大小设置为 0,并且将__user_heap_extend()定义为可扩展堆的函数,则会在需要时创建堆。

很赞哦!