如果从事 TI DSP 处理器开发,CMD 文件一定是不可忽略的重要文件。当然,CMD 文件也并不是 DSP 处理器的专利,开发 TI ARM(不运行 HLOS 高阶操作系统),MCU 等处理器也都会遇到 CMD 文件。本文就以C6000 DSP CMD 文件为例,给大家说说 CMD 文件的那些事。
首先,需要知道 CMD 文件到底是什么文件?
CMD 即Linker Command Files 命令链接文件。CMD 发挥作用的阶段是在我们程序开发的链接阶段。即下图 Linker 阶段。
需要注意的是英文 LinkerCommand Files 中的 Files 是复数,也就是说在一个 DSP 项目中,CMD 文件可以不止一个。不论是否运行实时操作系统,都可以有多个 CMD 文件,但是不同的 CMD 文件中的内容不能有冲突,否则编译工具链会报链接错误!
既然 CMD 文件在链接阶段发挥作用,那么就需要知道编译工具链在链接阶段究竟做了些什么?
n 分配段到目标系统可配置内存区域
n 重新定位符号和段并指派最终地址
n 解析不同文件中未定义的外部引用
n 分配段到特定的内存区域
n 合并目标文件段
n 定义或重定义链接时全局符号
说了这么多,简而言之链接过程主要是一些对符号和程序段内存分配的操作。而 CMD 文件最主要的功能就是内存分配。
使用 CMD 文件的文件结构
CMD 文件主要有三部分内容
1、链接选项
可以在 CMD 文件修改链接选项,比如
-heap 0x1000
-stack 0x1000
这两个参数是指定堆和栈的大小,这里需要注意的是堆栈,虽然一般都放在一起描述,但它们可不是一回事。
-l../../../Library/Codec/h264hpvdec_ti.le66
还可以链接静态库(-l 小写英文字母 L),这里使用的是相对路径。
2、MEMORY 指令 - 目标处理器内存区域描述
这一部分主要是描述目标处理器中内存区域,只要是可访问的内存区域都可以在这里描述,当然需要用到的内存空间要描述,不用到的可以不用描述。需要注意的是,这个内存描述仅在 CMD 文件中有效,不会影响其它文件,也不可以在其它文件中引用。
语法
MEMORY
{
name 1 [( attr )] : origin = expression , length = expression [, fill = constant]
.
.
name n [( attr )] : origin = expression , length = expression [, fill = constant]
}
name 命名一段内存区域,长度 1 - 64 个字符,可以使用 A - Z,a - z,$,.以及 _。
attr 为这段内存区域指定 1 - 4 个属性。可选参数。属性限制对于段的分配。如果,不指定该参数即代表不限制该内存段属性。有效的属性有
R 内存区域可读
W 内存区域可写
X 内存区域包含可执行代码
I 内存区域可被初始化
origin :指定内存区域起始地址,也可以写作 origin,org 或 o。地址以字节为单位的 32 位常量表达式,可以是十六进制、十进制或者八进制。
length: 指定内存区域长度,也可以写作 length,len 或 l。可以是十六进制、十进制或者八进制。
fill :使用指定字符填充内存区域,也可以写作fill 或 f。可选参数。填充字符为一个整数常量,可以是十六进制、十进制或者八进制。 fill 用于填充一段不用来分配段的内存区域。
地址操作可以使用的表达式,表达式的规则与标准 C语言一致。
单目运算符 - ~ !
双目运算符 * / % + -<<>> == = <<= >>= & | && ||
其它 START SIZE END 这三个关键字分别用于获取引用内存区域的起始地址、大小及结束地址。
使用表达式描述地址的范例。
/********************************************************/
/* 范例*/
/********************************************************/
file1.obj file2.obj /* 输入文件 */
--output_file=prog.out /* 选项 */
MEMORY
{
FAST_MEM (RX): origin =ORIGIN + CACHE length = 0x00001000 + BUFFER
SLOW_MEM (RW): origin = end(FAST_MEM)length = 0x00001800 - size(FAST_MEM)
EXT_MEM (RX): origin = 0x10000000 length = size(FAST_MEM) - CACHE
}
3、SECTIONS 指令 - 分配程序段到内存
描述输入段如何合并到输出段;
定义可执行文件中的输出段;
指定输出段放置到的内存区域;
允许重命名输出段。
SECTIONS
{
name : [property [, property] [, property] … ]
name : [property [, property] [, property] … ]
name : [property [, property] [, property] … ]
}
加载分配:定义段被加载到的内存区域
语法: load = 区域 或 >区域
运行分配 :定义段运行的内存区域
语法: run = 区域 或 run > 区域
输入段:定义用于组成输出段的输入段(目标文件)
语法: { 输入段 }
段类型:定义特定段标志
语法: type = COPY 或 type = DSECT 或 type = NOLOAD
填充值:定义用来填充未初始化区域(Hole)值
语法: fill = 值 或 名称: [属性= 值]
SECTIONS 指令范例
/**************************************************/
/**************************************************/
file1.obj file2.obj
--output_file=prog.out
SECTIONS
{
.text: load =EXT_MEM, run = 0x00000800
.const: load =FAST_MEM
.bss: load =SLOW_MEM
.vectors: load =0x00000000
{
t1.obj(.intvec1)
t2.obj(.intvec2)
endvec = .;
}
.data:alpha: align =16
.data:beta: align =16
}
结合前面的范例,最终的内存分配如下图所示
此外,有部分段由编译器及程序设计语言(C++ / C / 汇编)定义,当然开发人员也可以自行定义段。
编译工具创建的已初始化的程序段(EABI)
编译工具创建的未初始化的程序段(COFFABI 及 EABI)
自行定义段名称不能跟这些名称冲突。