所謂的Link File是指在GCC鏈接階段用到的一個(gè)用于表示MCU內(nèi)存分布的文件,這個(gè)文件是GCC連接器的一個(gè)輸入文件,不同的鏈接文件會(huì)有不同的結(jié)果,比如我們常見(jiàn)的下載到RAM或者Flash中,這兩種不同的地址就對(duì)應(yīng)不同的連接文件。本篇文章我們就對(duì)鏈接文件做一個(gè)簡(jiǎn)單的描述。

連接文件的基本概念
在GCC中,連接文件擴(kuò)展名一般是.ld, 是編譯器鏈接階段的輸入文件,他主要用來(lái)表示我們編譯的程序在目標(biāo)系統(tǒng)里面的存儲(chǔ)方式。在解釋這個(gè)概念之前我們有必要在回顧一下程序編譯的具體過(guò)程。
很早之前我們就知道程序編譯要經(jīng)過(guò)編譯和鏈接兩個(gè)階段,但是現(xiàn)在各種GUI工具的盛行,很多時(shí)候我們都是直接點(diǎn)一個(gè)按鈕就搞定了,工具實(shí)際在后臺(tái)做了什么操作我們并不是特別清楚。實(shí)際上編譯器的編譯主要經(jīng)過(guò)的兩個(gè)階段主要做的事情如下:
預(yù)處理階段:這個(gè)階段編譯器會(huì)對(duì)我們的代碼進(jìn)行預(yù)處理,主要就是進(jìn)行宏的替換和注釋的刪除,這樣我們就會(huì)得到比較干脆的代碼,很多編譯器不在單獨(dú)列出這個(gè)預(yù)處理階段,而是直接和編譯過(guò)程綜合在一起。
編譯階段:這個(gè)階段主要將我們的C語(yǔ)言編譯成機(jī)器語(yǔ)言,生成的目標(biāo)文件是
.o的object文件和.lst的連接文件,這個(gè)連接文件和我們今天要講的連接文件有區(qū)別,他類(lèi)似于機(jī)器語(yǔ)言,但是里面的地址信息都是符號(hào)和相對(duì)地址,而不是真正的地址。編譯階段是針對(duì)源代碼中的C文件的,每個(gè)C文件都會(huì)單獨(dú)編譯,所以編譯器也沒(méi)法獲得絕對(duì)地址。連接階段:這個(gè)階段生成最終的可執(zhí)行程序,這個(gè)階段的輸入文件主要有我們今天要講的
ld鏈接文件和編譯階段生成的所有.o文件,連接過(guò)程因?yàn)榈玫搅怂械某绦蛐畔?,所以它?huì)根據(jù)ld文件中的地址信息安排代碼在處理器中的存放方式,比如代碼放到什么位置,變量放到什么位置等等。
連接文件中比較重要的幾個(gè)部分
連接文件中內(nèi)存的分布是按照region來(lái)分布的:
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")
}
上面是一個(gè)簡(jiǎn)單的RAM中分布的連接文件,可以看到我們主要將內(nèi)存分成了VECTORS, ITCM, DTCM三個(gè)region,這三個(gè)部分中分別存放了不同的內(nèi)容,比如*(.text*)就是代碼段, *(.data*)就是內(nèi)存中的變量,這些變量一般是需要初始化的全局變量,如果程序不是直接下載到RAM中而是Flash中,我們還需要通過(guò)AT關(guān)鍵字進(jìn)行地址重定義。*(.bss*)是為初始化的全局變量,這些我們一般會(huì)在程序初始化的時(shí)候后將他們初始化為全零。
堆和棧的定義是直接采用定義地址偏移和定義符號(hào)來(lái)實(shí)現(xiàn)的,我們直接在用到的DATA段和BSS段之后指定了Heap_Size大小的空間作為堆空間,這里可以通過(guò)ALIGN關(guān)鍵字進(jìn)行地址對(duì)齊。棧則是直接將后面的剩余空間直接利用,最后會(huì)有一個(gè)判斷,用來(lái)計(jì)算最終地址是否溢出。
地址重定義
連接文件中還有一個(gè)比較關(guān)鍵的AT關(guān)鍵字這里沒(méi)有體現(xiàn),這個(gè)關(guān)鍵字主要是實(shí)現(xiàn)一個(gè)地址映射的功能,我們知道變量我們一般是放到RAM中去的,對(duì)于一些有初始值的變量我們不能簡(jiǎn)單的放到RAM中,因?yàn)閷?duì)于MCU而言,我們不可能每次都去下載程序之后在運(yùn)行,當(dāng)程序從flash直接啟動(dòng)的時(shí)候,RAM數(shù)據(jù)并不會(huì)自動(dòng)初始化,常用的方法是將這些RAM變量的初始值放到flash中去,在程序啟動(dòng)的時(shí)候手動(dòng)將這些值初始化到RAM中,AT關(guān)鍵字就是實(shí)現(xiàn)這個(gè)功能的,它的作用就是告訴連接器某個(gè)段連接的時(shí)候放到哪個(gè)地址,運(yùn)行的時(shí)候在哪個(gè)地址運(yùn)行。
好了就醬吧,該去吃早飯了。