在DSP复位后,执行程序之前,需要将程序放入目标设备的内存中,这就是程序的加载。加载过程初始化了设备内存、程序代码和数据,完成程序执行的准备工作。程序加载完成后,CPU会跳转到相应的入口地址继续执行程序。
加载程序可能是设备上的另一个程序,一个外部代理(例如,一个调试器),或者设备可能在开机后初始化自己,这称为引导加载(BootLoader)。
加载程序负责在程序启动之前在内存中构建加载镜像。加载镜像是程序执行前内存中的代码和数据。加载程序的组成部分取决于环境,例如操作系统是否存在。在使用COFF RAM模型时,加载程序负责解析.cinit部分,并在加载时执行.cinit代码的初始化。
程序可以通过以下方式加载:
加载地址是镜像文件中给数据对象分配的存储地址,运行地址是数据对象在程序执行期间存在的地址。(数据对象是一块内存,它表示一个Sector、段、函数或变量等数据)。
程序代码和只读数据的加载地址和运行地址是相同的,如.econst Sector和.text Sector。在这种情况下,程序可以直接从加载地址读取数据。
可写数据(如:变量)的加载地址和运行地址是不同的,如:.data Sector,.data Sector的起始内容加载到ROM中,运行时复制到RAM中。复制通常发生在程序启动期间(_c_int00启动函数自动完成,如:变量的初始化),但也可能是在启动后的某个时刻由用户显示实现(如:某个函数在FLASH中加载,在RAM中运行)。
没有初始值的Sector,如.ebss Sector没有加载地址,或者说加载地址和运行地址相同。如果为未初始化的数据指定不同的加载地址和运行地址,链接器将提供警告并忽略加载地址。
汇编代码和目标文件中的符号几乎总是指向运行地址。当查看程序中的地址时,几乎总是在查看运行地址。除了初始化之外,很少使用加载地址。
一个Sector的加载地址和运行地址由链接命令文件(CMD文件)控制,并记录在目标文件中。加载地址决定加载程序将该Sector的原始数据存放在何处。运行地址决定了程序运行时该Sector的地址。对该Sector的任何引用(例如对其中的变量的引用)都指向它的运行地址。如果在CMD文件中为Sector指定了不同的的运行地址和加载地址,在调用该Sector中的数据之前,用户程序必须将该Sector从其加载地址复制到其运行地址。复制不会自动发生,所以需要在用户程序中显式地完成复制过程。
程序中的全局变量是可写的,所以全局变量必须位于可写内存中,通常是RAM中。然而,RAM是不稳定的,这意味着当断电时,它将失去数据。如果该数据有初始值,那么该初始值必须存储在非易失性内存中,如FLASH。在使用初始值之前,必须将初始值从非易失性ROM(如:FLASH)复制到RAM中。
在设备复位时,首先会从BOOT ROM中获取复位中断向量,然后根据复位中断向量运行BOOT ROM中的程序,该程序会通过判断BOOT引脚的状态来确定从FLASH启动还是RAM启动,或者是执行外设引导加载程序。BOOT ROM中有多个外设引导加载程序,通过配置BOOT引脚可以选择复位后执行哪个外设引导加载程序。
在不同设备之间,引导加载(bootloader)程序的细节差别很大。不是每台设备都支持每一种引导加载模式,使用引导加载程序是可选的。本节讨论各种引导加载模式,以帮助您理解它们的工作方式。请参考您的设备的数据表,以了解哪些引导加载方案是可用的,以及如何使用它们。
程序入口地址是程序开始执行的地址,是启动程序的地址。启动程序负责完成初始化并调用程序的其余部分。对于一个C/C++程序,启动程序通常命名为c_int00。程序加载完成后,入口地址c_int00的值被放置在PC寄存器中,CPU从PC寄存器中获取入口地址_c_int00并开始执行,实现C/C++环境的初始化。
目标文件有一个入口地址字段。对于C/C++程序,链接器默认将c_int00填充到入口地址中。用户也可以选择一个自定义入口地址,如在某些程序中入口地址是code_start,然后在code_start中跳转到c_int00。
MEMORY
{
PAGE 0 :
START : origin = 0x080000, length = 0x000002
}
SECTIONS
{
codestart : > BEGIN PAGE = 0, ALIGN(4)
}
以TMS320F28075为例,在上述CMD文件中创建一个名为codestart的Sector,并将该Sector放入START内存区域,START内存是FLASH的起始地址。默认情况下,TMS320F28075复位后先执行BOOT ROM中的程序,然后从FLASH开始启动,即从FLASH的起始地址START开始执行代码。
在F2807x_CodeStartBranch.asm文件中,使用.sect指令创建了一个名为codestart的Sector,该Sector中的代码在连接后会放入START中。设备复位后,从START处开始执行codestart中的代码,并且程序会从这里跳转到c_int00,然后在c_int00中调用主函数Main()并执行用户程序。
***********************F2807x_CodeStartBranch.asm**********************
WD_DISABLE .set 0 ;set to 1 to disable WD, else set to 0
.ref _c_int00
.global code_start
***********************************************************************
* Function: codestart section
*
* Description: Branch to code starting point
***********************************************************************
.sect "codestart"
code_start:
.if WD_DISABLE == 1
LB wd_disable ;Branch to watchdog disable code
.else
LB _c_int00 ;Branch to start of boot._asm in RTS library
.endif
;end codestart section
程序加载完成后就可以运行了,下面描述C/C++程序的初始化。汇编程序可能不需要执行这些步骤的全部。
函数_c_int00是C/C++程序的启动程序,它执行C/C++程序初始化所需的所有步骤。
函数名称c_int00表示它是中断号为0的中断处理程序,用于复位,并配置C环境。该复位中断处理函数的名称不是一定要取名为c_int00,但是链接器默认将c_int00设置为C程序的入口地址。编译器的运行时支持库提供c_int00的默认实现。
启动程序c_int00负责执行以下操作:
ROM模型在启动程序c_int00期间执行更多的工作。RAM模型在加载应用程序时执行更多的工作。
如果您的应用程序需要频繁地复位,或者是一个独立的应用程序,那么ROM模型可能是更好的选择,因为启动程序c_int00将拥有初始化RAM变量所需的所有数据。但是,对于带有操作系统的设备,最好使用RAM模型。
在COFF RAM模型中,加载程序首先负责处理.cinit Sector。此时.cinit Sector是一个NOLOAD部分,这意味着链接器不会为它分配内存。相反,加载器负责解析.cinit Sector,并在加载时执行其中代码的初始化。
在COFF ROM和EABI ROM模型中,C启动程序c_int00将数据从.cinit Sector复制到变量的运行时位置。
在EABI RAM模型中,在启动时不会生成.cinit记录。
在运行时自动初始化变量是自动初始化的默认方法。要使用此方法,请使用——rom_model选项调用链接器。
ROM模型允许初始化数据存储在慢速非易失性内存(如:FLASH)中,并在每次程序复位时复制到快速内存(如:RAM)中。
对于带有EABI的ROM模型,.cinit Sector与所有其他初始化Sector一起加载到内存中。链接器定义了一个特殊的符号,称为ti_cinit_base,它指向内存中初始化表的开头。当程序开始运行时,C启动程序c_int00将表(由.cinit指向)中的数据复制到变量的运行时位置。
对于带有COFF的ROM模型,.cinit Sector与所有其他初始化Sector一起加载到内存中。链接器定义了一个称为cinit的特殊符号,它指向内存中初始化表的开头。当程序开始运行时,C启动程序c_int00从初始化表(由.cinit指向)中复制数据,并将数据赋值给.ebss Sector或用户自定义Sector中的指定变量。
RAM模型在加载时初始化变量。要使用此方法,请使用——ram_model选项调用链接器。此模型可以减少启动时间并节省初始化表使用的内存。
当使用——ram_model链接器选项时,链接器会在.cinit Sector的标题中设置STYP_COPY位。这告诉加载程序不要将.cinit Sector加载到内存中(.cinit Sector在内存映射中不占空间)。
对于COFF,链接器还将cinit符号设置为-1(通常,cinit指向初始化表的开头)。这C向启动程序c_int00表明,初始化表不存在于内存中,因此,在启动时不执行运行时初始化。
对于EABI,链接器将_ ti_cinit_base设置为_ ti_cinit_limit,以表明不存在.cinit记录。
COFF加载程序必须能够执行以下任务,以便在加载时进行初始化:
对于EABI,加载程序直接从.data Sector中复制数据到内存中。
下面的列表概述了使用-ram_model或-rom_model选项调用链接器时发生的情况。
符号_c_int00被定义为程序入口地址。_c_int00符号是boot. C .obj中的C启动程序的开始。引用_c_int00可以确保从适当的运行时支持库中将boot.c.obj自动链接进来。
对于COFF, .cinit输出Sector有一个终止记录,以告诉C启动程序c_int00(运行时自动初始化)或加载程序(加载时初始化)何时停止读取初始化表。
当使用RAM模型【加载时初始化】(-ram_model选项):
当使用ROM模型【运行时自动初始化】(- rom_model选项):
注意:加载程序不是TMS320C28x C/C++编译器工具的一部分。请使用Code Composer Studio集成开发环境或其他工具作为加载器。
用户可能希望将代码加载到内存的一个区域,并在运行之前将其移动到另一个区域。例如,在FLASH中有性能关键的代码,这些代码加载到FLASH中,但是它需要RAM中运行以获取更好的性能。
链接器提供了一种处理方法。使用SECTIONS指令,用户可以有选择地指导链接器为Sector分配两个地址:第一个地址是Sector的加载地址,第二个地址是Sector的运行地址。对加载地址使用load关键字,对运行地址使用run关键字。
如果一个Sector在链接时被分配了两个地址,那么该Sector中定义的所有标签都将被重定向到运行地址。
ramfuncs1 : LOAD = FLASHD,
RUN = RAM0,
LOAD_START(_RamFuncs1LoadStart),
LOAD_END(_RamFuncs1LoadEnd),
RUN_START(_RamFuncs1RunStart),
PAGE = 0, ALIGN(4)
上述代码表示Sector ramfuncs1的加载地址是FLASHD,运行地址是RAM0。
如果仅为Sector分配一个地址,则该Sector在相同的地址加载和运行。未初始化的Sector(如.ebss或.bss)没有加载地址,因此惟一有效的地址是运行地址。链接器对未初始化的Sector只分配一个地址,如果用户同时指定了运行地址和加载地址,链接器会警告并忽略加载地址。
.econst : > FLASHA PAGE = 0, ALIGN(4)
上述代码表示Sector .econst的加载地址和运行地址都是FLASHA。
[1] TMS320C28x Assembly Language Tools