本系列文章链接


本文主要介绍C6000 Compiler的链接脚本相关的内容,参考文档为:

  目标文件的链接也是通过cl6x这个工具实现的,它就是C6000 Compiler的主体,在编译器安装路径下的bin文件夹中能够找到,实际上的编译链接工作都是通过命令行完成的,只是CCS做成了图形化界面。链接命令:

1
cl6x --run_linker filename.cmd

  这个命令调用链接器,同时根据cmd文件中的命令选项进行相应的链接。
  应用二进制接口(Application Binary Interface, ABI),在SPRU187U的2.16节和6.11节有ABI的具体定义,大概就是输出文件的格式,不同的ABI不能兼容!我们在创建工程的时候都需要输出文件格式进行选择,有两种,COFF和ELF。
  COFF是Common Object File Format的缩写,是一种传统的格式,它采用的就是原本的ABI,一些老的编译器只能支持这种格式,现在还能用,但是不在支持一些新的特性了。
  ELF是Executable and linking format的缩写,采用的是Embedded ABI(EABI),它能够支持一些新的C++中的特性,比如模版实例化和内联函数。
  我现在用的工程都是COFF输出格式的,但可能用ELF对C++的支持好一些。
  cmd文件中的MEMORY语法挺好理解的,就是具体的存储器空间,这个根据C6455的数据手册就可以知道,存储器的几段地址空间的基地址和长度都记录在这里。而且用户可以根据功能进一步对存储器进行细分。
  然后我觉得对于最初看到这个cmd文件的人,最想知道的应该是这里的不同的section是什么含义。
  首先是这个section在程序中定义的方式,也就是这些section是怎么来的:
  在C源文件中可以用Pragma命令来实现,参考SPRU187U的6.9节,有CODE_SECTION、DATA_SECTION、SET_CODE_SECTION和SET_DATA_SECTION的详细用法

1
2
3
4
#pragma CODE_SECTION (symbol , "section name ")
#pragma DATA_SECTION (symbol , " section name ")
#pragma SET_CODE_SECTION ("section name")
#pragma SET_DATA_SECTION ("section name")

  大概就是前面带“SET”的会直接把这条指令后面的代码都放到指定的section里,而不带“SET”的则可以通过symbol指定具体对象。
  在汇编代码中,直接通过“.sect”指定段,比如:

1
.sect ".csl_vect"

  就直接指定了后面的代码放到csl_vect段中。
  区分了这些段之后,就可以把指定的代码或者数据放到指定的存储空间。
  SPRU187U的5.3.5、7.1.1和SPRA999A1的2.1.2对几种不同名称的段的含义有介绍。section主要分为两部分,初始化的和未初始化的。初始化的包括可执行代码和一些初始化的数据。而未初始化就比如是临时变量、堆栈空间等等。
  可执行代码大家应该都知道,就是“.text”段,打开菜单栏中的“View --> Memory Allocation”可以看到编译后的各个存储空间的段分配情况。那么初始化的数据呢?主要是“.const”和“.cinit”。可以简单做一个实验,创建一个全局变量而不专门指定段,那么“.cinit”和“.bss”的大小就会增加。
  关于这个数据,还有一个自动初始化的问题。自动初始化的方式有两种,在SPRU187U的5.3.4和7.8.1中有介绍。

  上图是在链接时加上“–rom_model”,简写“-c”,是默认选项,选项的效果是“.bss”段中的数据在程序运行时才会被初始化。

  而这个图是“–ram_model”(简写“-cr”)选项的效果,程序在加载的时候就初始化了“.bss”段中的数据。
  有些时候不知道具体哪个段是初始化的还是未初始化的,除了查阅文档来确定以外,还有一个简单的方法。就是在链接用的cmd文件中,加上“–map_file=filename”或者“-m filename”,就可以看到不同的段在存储器的分配情况,而且未初始化的段会有标注“UNINITIALIZED”。
  在大概了解每个段的功能后,我们可以按照自己的需要写cmd文件了。关于cmd文件的系统的学习可以参考SPRU186W的7.4和7.5节。
  7.4节主要介绍了支持的选项。除了刚刚讲的那个自动初始化的方式的选项和输出map文件的选项,别的常用的还有修改堆栈大小、指定输出目标文件名称。这些选项在工程选项中也能设置,如果在cmd文件中写入了相关的命令,工程选项中的配置就无效了。
  7.5节主要介绍了链接用的cmd文件,其中包括MEMORY的语法和SECTION的语法。这里就不详细介绍了,值得注意7.5.5 “Specifying a Section’s Run-Time Address”给出了load address和run address的区分,SPRA999A1中的1.2节“COFF Section Placement”也介绍了相关的内容。一般情况下,代码先是存在外部ROM中,然后再搬到RAM中运行的。
  如果不指定run address,即只用一个“>”,或者只有“load = xxx”,那么run adrress和load address是相同的。如果指定了run address,那么代码在链接的时候,目标文件中的symbol就会以run address为参考。那么什么时候要这样额外指定run address呢?这个主要看cpu访问这部分代码的频率,如果访问频率高,自然就是放到内部缓存中运行更好;如果只访问一次,比如.cinit,那就只有load address就够了。
  但是这个区分load address和run address并不会自动完成,在7.5节有两段原话是这样的:

The application must copy the section from its load address to its run address;this does not happen automatically when you specify a separate run address.
Specifically, the code that copies a section from its load address to its run address must have access to the load address

  就是说这个代码不会自动从load address搬到run address,而且如果要搬运,CPU也一定要能够有权限访问这部分存在外部存储器的代码。在我们用仿真器调试的时候,不管什么段,都放到L2缓存里就行了,加载完成后,代码就会从C程序入口“_c_int00”开始执行,先建立起C的运行环境,再执行main函数中的程序……所以我们不用特意去关心这个load address和run address。
  那么如果我们想要将程序固化,让代码存在Flash里,系统上电后自动将Flash里的程序加载到内存中在运行呢?是不是就非常符合上面说的区分load address和run address的应用场景?确实是这样,对于带BIOS系统的开发,用户好像可以通过DSP/BIOS memory manager对存储器的分配用图形化界面管理,非常方便。而且对于官方的DSK,好像有专门的Flash烧写工具,这里区分load address和run address之后,是不是用那个烧写工具就非常方便了?(没用过不知道)。而且linker还可以根据这一设置生成对应的copy table,在7.8.10中给出了一个用于搬运代码的C程序。
  总结起来,其实最关键的还是run address,因为链接的时候是以run address为参考的。而load address其实不是很重要,因为我的代码即使一开始存在外部的flash里,它也是需要我主动想办法把它搬进来,指定load address并没有给我提供多大的方便。所以不如就还是把所有段都放在L2里,然后我用我写的bootloader程序把它们都搬到L2上。具体的程序固化到Flash的方式将在后面的文章中介绍。
  目前我刚开始使用这个C6455,我感觉现在把所有的段都放在L2上是最方便的。后面如果想要把代码放到DDR2上运行,那我再另外写一篇文章。之所以说方便,是因为如果要用仿真器调试这部分代码,也可以直接运行;而如果要固化这部分程序,也不需要对cmd文件进行更改,只需要加上一部分bootloader的程序即可,而且这部分bootloader程序也不会影响仿真调试。