這篇文章將會簡單的說明Linker Script在嵌入式開發上,會寫些什麼內容,如何被使用。但我並不打算詳細的說明那些語法,因為那並不是我該做的事情。我會簡要的各個段落語法的用途與意義,讓你大致掌握Linker Script在做什麼事情。
我們將以一個運行在STM32F429 Discovery上的號誌燈專案的Linker Script做為例子。
進入點
1 2 |
/* Entry Point */ ENTRY(Reset_Handler) |
定義程式執行的進入點,吃一個symbol作為參數。要指定進入點有好幾種方式,ENTRY只是常見的一種。
配置runtime記憶體大小
1 2 3 4 5 |
/* Highest address of the user mode stack <em>/ _estack = 0x20030000; /</em> end of 192K RAM */ /* Generate a link error if heap and stack don't fit into RAM <em>/ _Min_Heap_Size = 0; /</em> required amount of heap <em>/ _Min_Stack_Size = 0x400; /</em> required amount of stack */ |
指定runtime stack的記憶體位置,我們會事先指定一段heap和stack size,程式設計師必須自行保證使用的stack不會超過這範圍,如果配置太多在Link時就會炸掉
配置實體記憶體
1 2 3 4 5 6 7 8 |
/* Specify the memory areas */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K } |
這裡的記憶體大小必須要按照板子實際上的狀況來配置,可以看到不同的記憶體有不同的起始位置和大小。常見的有FLASH和RAM。一般來說FLASH是燒程式的地方,RAM是跑程式的地方,至於CCMRAM是幹啥的呢?CCMRAM全名是(Core Coupled Memory),他是一種SRAM,特性是無法被DMA存取,這塊記憶體一定要由CPU自己主動存取,用途是是當一般記憶體被DMA時是CPU是動不了的,此時CPU可以使用CCRAM來提昇效能。
你可能會有疑問,為什麼配置的ORIGIN並不是從0開始。這是因為板子本身的FLASH就不是從0開始,這麼做的原因是為了保留0起頭的一段記憶體位置,因為剛boot時板子一定會從0開始讀,透過Memory Alias讓板子可以從不同的記憶體區塊boot,詳情請查閱板子的記憶體配置文件。
定義section
接下來的區塊將開始定義執行檔的sections,我們可以知道執行檔是由多個object file所組合而成的,每個object file中都有自己的section,存放不同的程式內容。接下來我們將透過Linker Script告訴Linker要怎麼把大家的section組合成執行檔中的section。
1 2 3 4 5 6 7 8 9 10 |
/* Define output sections */ SECTIONS { /* The startup code goes first into FLASH */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH |
我們首先建立一個被稱為.isr_vector的section,關於這個section內容要放置的東西會寫在大括號內。其實.isr_vector就是一開始的中斷向量表。
.
是指location counter,代表目前的記憶體位址,ALIGN是對齊的意思,代表請對齊到最接近的4的倍數,相同或更大的記憶體位址。
KEEP意指請找出並保留(不管存不存在)所有object files內被稱為.isr_vector的section,並把這些section統統放進來
最後請把該section存到我們一開始定義的FLASH區塊內。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* The program code and other data goes into FLASH */ .text : { . = ALIGN(4); *(.text) /* .text sections (code) */ *(.text</em>) /* .text* sections (code) */ *(.glue_7) /* glue arm to thumb code */ *(.glue_7t) /* glue thumb to arm code */ *(.eh_frame) KEEP (*(.init)) KEEP (*(.fini)) . = ALIGN(4); _etext = .; /* define a global symbols at end of code */ } >FLASH |
接下來我們要建立.text section,這裡通常是存放實際執行的程式碼,我們會蒐集所有object file中的.text, .text*, .glue_7, .glue_7t, ...放進這個區段內,並另外建立並保留init和fini區段。
.rodata
1 2 3 4 5 6 7 8 |
/* Constant data goes into FLASH */ .rodata : { . = ALIGN(4); *(.rodata) /* .rodata sections (constants, strings, etc.) */ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ . = ALIGN(4); } >FLASH |
該section存放唯讀data, 一般來說是const變數和字串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
.ARM.extab : { *(.ARM.extab .gnu.linkonce.armextab.*) } >FLASH .ARM : { __exidx_start = .; *(.ARM.exidx*) _exidx_end = .; } >FLASH .preinit_array : { PROVIDE_HIDDEN (__preinit_array_start = .); KEEP (*(.preinit_array*)) PROVIDE_HIDDEN (__preinit_array_end = .); } >FLASH .init_array : { PROVIDE_HIDDEN (__init_array_start = .); KEEP (*(SORT(.init_array.*))) KEEP (*(.init_array*)) PROVIDE_HIDDEN (init_array_end = .); } >FLASH .fini_array : { PROVIDE_HIDDEN (__fini_array_start = .); KEEP (*(SORT(.fini_array.*))) KEEP (*(.fini_array*)) PROVIDE_HIDDEN (__fini_array_end = .); } >FLASH /* used by the startup to initialize data */ _sidata = LOADADDR(.data); |
.data
1 2 3 4 5 6 7 8 9 10 |
/* Initialized data sections goes into RAM, load LMA copy after code */ .data : { . = ALIGN(4); _sdata = .; / create a global symbol at data start */ *(.data) / .data sections / *(.data) /* .data* sections */ . = ALIGN(4); _edata = .; /* define a global symbol at data end */ } >RAM AT> FLASH |
保存已經初始化的全域變數和static變數
CCRAM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
_siccmram = LOADADDR(.ccmram); /* CCM-RAM section * * IMPORTANT NOTE! * If initialized variables will be placed in this section, * the startup code needs to be modified to copy the init-values. / .ccmram : { . = ALIGN(4); _sccmram = .; / create a global symbol at ccmram start / *(.ccmram) *(.ccmram) . = ALIGN(4); _eccmram = .; /* create a global symbol at ccmram end */ } >CCMRAM AT> FLASH /* Uninitialized data section */ . = ALIGN(4); |
bss
1 2 3 4 5 6 7 8 9 10 11 12 |
.bss : { /* This is used by the startup in order to initialize the .bss secion / _sbss = .; / define a global symbol at bss start / bss_start = _sbss; *(.bss) *(.bss) *(COMMON) . = ALIGN(4); _ebss = .; /* define a global symbol at bss end */ __bss_end__ = _ebss; } >RAM |
.bss section,通常是蒐集與存放未初始化的全域變數和static變數。
.user_heap_stack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* Userheapstack section, used to check that there is enough RAM left */ .userheapstack : { . = ALIGN(4); PROVIDE ( end = . ); PROVIDE ( _end = . ); . = . + _MinHeapSize; . = . + _MinStack_Size; . = ALIGN(4); } >RAM /* MEMORYbank1 section, code must be located here explicitly */ /* Example: extern int foo(void) attribute ((section (".mb1text"))); */ .memoryb1text : { *(.mb1text) /* .mb1text sections (code) */ *(.mb1text*) /* .mb1text* sections (code) */ *(.mb1rodata) /* read-only data (constants) */ *(.mb1rodata*) } >MEMORYB1 |
用於檢查我們需要的的heap和stack size是否會超過板子上的RAM的大小。如果超過的話該section會建不出來,就會噴錯。
/Discard/
1 2 3 4 5 6 7 |
/* Remove information from the standard libraries */ /DISCARD/ : { libc.a ( * ) libm.a ( * ) libgcc.a ( * ) } |
/DISCARD/是個特別的section name,被放在這裡的input section不會被輸出成output section。
1 2 |
.ARM.attributes 0 : { *(.ARM.attributes) } } |