over 2 years ago

這篇文章將會簡單的說明Linker Script在嵌入式開發上,會寫些什麼內容,如何被使用。但我並不打算詳細的說明那些語法,因為那並不是我該做的事情。我會簡要的各個段落語法的用途與意義,讓你大致掌握Linker Script在做什麼事情。

我們將以一個運行在STM32F429 Discovery上的號誌燈專案的Linker Script做為例子。

進入點

stm32_flash.ld
/* Entry Point */
ENTRY(Reset_Handler)  

定義程式執行的進入點,吃一個symbol作為參數。要指定進入點有好幾種方式,ENTRY只是常見的一種。

配置runtime記憶體大小

/* Highest address of the user mode stack */
_estack = 0x20030000;    /* end of 192K RAM */

/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

指定runtime stack的記憶體位置,我們會事先指定一段heap和stack size,程式設計師必須自行保證使用的stack不會超過這範圍,如果配置太多在Link時就會炸掉

配置實體記憶體

/* 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。

/* 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區塊內。

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .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

  /* 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變數和字串。

  .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

  /* 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

  _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

  .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_heapstack

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(4);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(4);
  } >RAM

  /* MEMORY_bank1 section, code must be located here explicitly            */
  /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */
  .memory_b1_text :
  {
    *(.mb1text)        /* .mb1text sections (code) */
    *(.mb1text*)       /* .mb1text* sections (code)  */
    *(.mb1rodata)      /* read-only data (constants) */
    *(.mb1rodata*)
  } >MEMORY_B1

用於檢查我們需要的的heap和stack size是否會超過板子上的RAM的大小。如果超過的話該section會建不出來,就會噴錯。

/Discard/

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

/DISCARD/是個特別的section name,被放在這裡的input section不會被輸出成output section。

  .ARM.attributes 0 : { *(.ARM.attributes) }
}
← 不負責任論瑞士法郎暴升 今天我們談馬拉松 →
 
comments powered by Disqus