ARM GCC中的Link File
所谓的Link File是指在GCC链接阶段用到的一个用于表示MCU内存分布的文件,这个文件是GCC连接器的一个输入文件,不同的链接文件会有不同的结果,比如我们常见的下载到RAM或者Flash中,这两种不同的地址就对应不同的连接文件。本篇文章我们就对链接文件做一个简单的描述。
连接文件连接文件的基本概念
在GCC中,连接文件扩展名一般是.ld
, 是编译器链接阶段的输入文件,他主要用来表示我们编译的程序在目标系统里面的存储方式。在解释这个概念之前我们有必要在回顾一下程序编译的具体过程。
很早之前我们就知道程序编译要经过编译和链接两个阶段,但是现在各种GUI工具的盛行,很多时候我们都是直接点一个按钮就搞定了,工具实际在后台做了什么操作我们并不是特别清楚。实际上编译器的编译主要经过的两个阶段主要做的事情如下:
-
预处理阶段:这个阶段编译器会对我们的代码进行预处理,主要就是进行宏的替换和注释的删除,这样我们就会得到比较干脆的代码,很多编译器不在单独列出这个预处理阶段,而是直接和编译过程综合在一起。
-
编译阶段:这个阶段主要将我们的C语言编译成机器语言,生成的目标文件是
.o
的object文件和.lst
的连接文件,这个连接文件和我们今天要讲的连接文件有区别,他类似于机器语言,但是里面的地址信息都是符号和相对地址,而不是真正的地址。编译阶段是针对源代码中的C文件的,每个C文件都会单独编译,所以编译器也没法获得绝对地址。 -
连接阶段:这个阶段生成最终的可执行程序,这个阶段的输入文件主要有我们今天要讲的
ld
链接文件和编译阶段生成的所有.o
文件,连接过程因为得到了所有的程序信息,所以它会根据ld
文件中的地址信息安排代码在处理器中的存放方式,比如代码放到什么位置,变量放到什么位置等等。
连接文件中比较重要的几个部分
连接文件中内存的分布是按照region
来分布的:
MEMORY
{
VECTORS (rwx) : ORIGIN = 0x0, LENGTH = 0x0410
ITCM (rwx) : ORIGIN = 0x00000410, LENGTH = 128K- 0x410
DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
ENTRY(_reset_init)
Heap_Size = 4K;
Stack_Size =41K;
SECTIONS
{
/* The startup code goes first into INTERNAL_FLASH */
.isr_vector :
{
__vector_table = .;
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > VECTORS
.text :
{
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.srodata .srodata.*)
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
*(.init)
*(.fini)
} > ITCM
.data :
{
. = ALIGN(4);
__DATA_RAM = .;
__data_start__ = .; /* create a global symbol at data start */
__etext = .;
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.sdata .sdata.*)
*(.heapsram*) /* This is only for the pulpino official test code. */
__noncachedata_start__ = .; /* create a global symbol at ncache data start */
*(NonCacheable)
__noncachedata_end__ = .; /* define a global symbol at ncache data end */
KEEP(*(.jcr*))
. = ALIGN(4);
__data_end__ = .; /* define a global symbol at data end */
} > DTCM
.bss :
{
__bss_start__ = .;
*(.bss*)
*(COMMON)
__bss_end__ = .;
} > DTCM
.heap :
{
__end__ = .;
end = __end__;
__heap_start = .;
. = + Heap_Size;
. = ALIGN(4);
__heap_end = .;
} > DTCM
/* Set stack top to end of RAM */
__StackTop = ORIGIN(DTCM) + LENGTH(DTCM) - 8;
__StackLimit = __StackTop - Stack_Size;
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __heap_end, "region RAM overflowed with stack")
}
上面是一个简单的RAM中分布的连接文件,可以看到我们主要将内存分成了VECTORS
, ITCM
, DTCM
三个region
,这三个部分中分别存放了不同的内容,比如*(.text*)
就是代码段, *(.data*)
就是内存中的变量,这些变量一般是需要初始化的全局变量,如果程序不是直接下载到RAM中而是Flash中,我们还需要通过AT
关键字进行地址重定义。*(.bss*)
是为初始化的全局变量,这些我们一般会在程序初始化的时候后将他们初始化为全零。
堆和栈的定义是直接采用定义地址偏移和定义符号来实现的,我们直接在用到的DATA段和BSS段之后指定了Heap_Size
大小的空间作为堆空间,这里可以通过ALIGN
关键字进行地址对齐。栈则是直接将后面的剩余空间直接利用,最后会有一个判断,用来计算最终地址是否溢出。
地址重定义
连接文件中还有一个比较关键的AT
关键字这里没有体现,这个关键字主要是实现一个地址映射的功能,我们知道变量我们一般是放到RAM中去的,对于一些有初始值的变量我们不能简单的放到RAM中,因为对于MCU而言,我们不可能每次都去下载程序之后在运行,当程序从flash直接启动的时候,RAM数据并不会自动初始化,常用的方法是将这些RAM变量的初始值放到flash中去,在程序启动的时候手动将这些值初始化到RAM中,AT关键字就是实现这个功能的,它的作用就是告诉连接器某个段连接的时候放到哪个地址,运行的时候在哪个地址运行。
好了就酱吧,该去吃早饭了。