一、 嵌入式系統(tǒng)中的section
在講linker文件之前,痞子衡必須先跟大家理清一個嵌入式系統(tǒng)中很重要的概念-section。那么什么是section?我們寫的C或者匯編source文件里都是各種應(yīng)用代碼,這些代碼按功能可以分為很多種類,比如常量、變量、函數(shù)、堆棧等,而相同類型的代碼的集合便是一個section,鏈接器在鏈接時組織數(shù)據(jù)的基本單元便是section。那么一個典型的嵌入式系統(tǒng)中到底有多少種section呢?下面列出了IAR里默認的所有section,那些常見section在后續(xù)介紹linker文件里會被提到。
//常見Section
.bss // Holds zero-initialized static and global variables.
CSTACK // Holds the stack used by C or C++ programs.
.data // Holds static and global initialized variables.
.data_init // Holds initial values for .data sections when the linker directive initialize is used.
HEAP // Holds the heap used for dynamically allocated data.
.intvec // Holds the reset vector table
.noinit // Holds __no_init static and global variables.
.rodata // Holds constant data.
.text // Holds the program code.
.textrw // Holds __ramfunc declared program code.
.textrw_init // Holds initializers for the .textrw declared section.
//較冷僻Section
.exc.text // Holds exception-related code.
__iar_tls.$$DATA // Holds initial values for TLS variables.
.iar.dynexit // Holds the atexit table.
.init_array // Holds a table of dynamic initialization functions.
IRQ_STACK // Holds the stack for interrupt requests, IRQ, and exceptions.
.preinit_array // Holds a table of dynamic initialization functions.
.prepreinit_array // Holds a table of dynamic initialization functions.
Veneer$$CMSE // Holds secure gateway veneers.
//更冷僻Section
.debug // Contains debug information in the DWARF format
.iar.debug // Contains supplemental debug information in an IAR format
.comment // Contains the tools and command lines used for building the file
.rel or .rela // Contains ELF relocation information
.symtab // Contains the symbol table for a file
.strtab // Contains the names of the symbol in the symbol table
.shstrtab // Contains the names of the sections.
Note:上述section的詳細解釋請查閱IAR軟件安裝目錄下IAR SystemsEmbedded Workbench xxxarmdocEWARM_DevelopmentGuide.ENU.pdf文檔里的Section reference一節(jié)。
二、解析linker文件
知道了section概念,那便可開始深入了解linker文件,什么是linker文件?linker文件是按IDE規(guī)定的語法寫成的用于指示鏈接器分配各section在嵌入式系統(tǒng)存儲器中存放位置的文件。大家都知道嵌入式系統(tǒng)存儲器主要分為兩類:ROM(非易失性),RAM(易失性),所以相應(yīng)的這些section根據(jù)存放的存儲器位置不同也分為兩類屬性:readonly, readwrite。實際上linker文件的工作就是將readonly section放進ROM,readwrite section放進RAM。
那么到底該如何編寫工程的linker文件呢?正如前面所言,linker文件也是有語法的,而且這語法是由IDE指定的,所以必須要先掌握IDE制定的語法規(guī)則,linker文件語法規(guī)則相對簡單,最常用的關(guān)鍵字就是如下8個:
// 動詞類關(guān)鍵字
define // 定義各種空間范圍、長度
initialize // 設(shè)置section初始化方法
place in // 放置section于某region中(具體地址由鏈接器分配)
place at // 放置section于某絕對地址處
// 名詞類關(guān)鍵字
symbol // 各種空間范圍、長度的標(biāo)識
memory // 整個ARM內(nèi)存空間的標(biāo)識
region // 在整個ARM內(nèi)存空間中劃分某region空間的標(biāo)識
block // 多個section的集合塊的標(biāo)識
Note:上述linker語法的詳細解釋請查閱IAR軟件安裝目錄下IAR SystemsEmbedded Workbench xxxarmdocEWARM_DevelopmentGuide.ENU.pdf文檔里的The linker configuration file一節(jié)。
到這里我們已經(jīng)可以開始愉快地寫linker文件了,是不是有點按捺不住了?來吧,只需要三步走,Let's do it。
此處假設(shè)MCU物理空間為:ROM(0x0 - 0x1ffff)、RAM(0x10000000 - 0x1000ffff),痞子衡要寫的linker要求如下:
- 中斷向量表必須放置于ROM起始地址0x0,且必須256字節(jié)對齊
- STACK大小為8KB,HEAP大小為1KB,且必須8字節(jié)對齊
- SATCK必須放置在RAM起始地址0x10000000
- 其余section放置在正確的region里,具體空間由鏈接器自動分配
2.1 定義物理空間
第一步我們先定義3塊互不重疊的空間ROM_region、RAM_region、STACK_region,其中ROM_region對應(yīng)的是真實的ROM空間,RAM_region和STACK_region組合成真實的RAM空間。
// 定義物理空間邊界
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__ = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);
define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;
define symbol __ICFEDIT_region_RAM_end__ = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);
define symbol __ICFEDIT_intvec_start__ = __ICFEDIT_region_ROM_start__;
// 定義堆棧長度
define symbol __ICFEDIT_size_cstack__ = (8*1024);
define symbol __ICFEDIT_size_heap__ = (1*1024);
// 定義各region具體空間范圍
define memory mem with size = 4G;
define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region STACK_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ - 1];
define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ to __ICFEDIT_region_RAM_end__];
2.2 定義section集合
第二步是自定義section集合塊,細心的朋友可以看到右邊花括號里包含的都是上一節(jié)介紹的系統(tǒng)默認section,我們會把具有相同屬性的section集合成到一個block里,方便下一步的放置工作。
// 定義堆棧塊及其屬性
define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
// 定義section集合塊
define block Vectors with alignment=256 { readonly section .intvec };
define block CodeRelocate { section .textrw_init };
define block CodeRelocateRam { section .textrw };
define block ApplicationFlash { readonly, block CodeRelocate };
define block ApplicationRam { readwrite, block CodeRelocateRam, block HEAP };
有朋友可能會疑問,為何要定義CodeRelocate、CodeRelocateRam這兩個block?按道理說這兩個block對應(yīng)的section可以分別放進ApplicationFlash和ApplicationRam,那為何多此一舉?仔細上過痞子衡前一節(jié)課source文件的朋友肯定就知道答案了,在那節(jié)課里介紹的startup.c文件里有一個叫init_data_bss()的函數(shù),這個函數(shù)會完成初始化CodeRelocateRam塊的功能,它找尋的就是CodeRelocate段名字,這個名字比系統(tǒng)默認的textrw名字看起來更清晰易懂。
2.3 安置section集合
第三步便是處理放置那些section集合塊了,在放置集合塊之前還有initialize manually語句,為什么會有這些語句?還是得結(jié)合前面提及的startup.c文件里的init_data_bss()函數(shù)來說,這個函數(shù)是開發(fā)者自己實現(xiàn)的data,bss段的初始化,所以此處需要通知IDE,你不需要再幫我做初始化工作了。
// 設(shè)置初始化方法
initialize manually { readwrite };
initialize manually { section .data};
initialize manually { section .textrw };
do not initialize { section .noinit };
// 放置section集合塊
place at start of ROM_region { block Vectors };
//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };
place in ROM_region { block ApplicationFlash };
place in RAM_region { block ApplicationRam };
place in STACK_region { block CSTACK };
當(dāng)然如果你希望IDE幫你自動初始化data,bss,textrw段,那么可以用下面語句替換initialize manually語句。
initialize by copy { readwrite, section .textrw };
設(shè)置好初始化方法后,便是放置section集合塊了,放置方法主要有兩種,place in和place at,前者用于指定空間塊放置(不指定具體地址),后者是指定具體地址放置。
至此一個基本的linker文件便大功告成了,是不是so easy?
番外一、自定義section
有耐心看到這里的朋友,痞子衡必須得放個大招獎勵一下,前面講的都是怎么處理系統(tǒng)默認段,那么有沒有可能在代碼里自定義段呢?想象一下你有這樣的需求,你需要在你的應(yīng)用里開辟一塊1KB的可更新的數(shù)據(jù)區(qū),你想把這個數(shù)據(jù)區(qū)指定到地址0x18000 - 0x183ff的范圍內(nèi),你需要在應(yīng)用里定義4 Byte的只讀config block常量指向這個可更新數(shù)據(jù)區(qū)首地址(這段config block只會被外部debugger或者bootloader更新),如何做到?
// C文件中
/////////////////////////////////////////////////////
// 用@操作符指定變量myConfigBlock[4]放進自定義.myBuffer section
const uint8_t myConfigBlock[4] @ ".myBuffer" = {0x00, 0x01, 0x02, 0x03};
// Linker文件中
/////////////////////////////////////////////////////
// 自定義指定的mySection_region,并把.myBuffer放到這個region
define region mySection_region = mem:[from 0x0x18000 to 0x183ff];
place at start of mySection_region { readonly section .myBuffer };
上面做到了將代碼中的常量放入自定義段?,那么怎么將代碼中的函數(shù)也放進自定義段呢?繼續(xù)看下去
// C文件中
/////////////////////////////////////////////////////
// 用#pragma location指定函數(shù)myFunction()放進自定義.myTask section
#pragma location = ".myTask"
void myFunction(void)
{
__NOP();
}
// Linker文件中
/////////////////////////////////////////////////////
// 把.myTask放到mySection_region
place in mySection_region { readonly section .myTask };
看起來大功告成了,最后還有一個注意事項,如果myConfigBlock在代碼中并未被引用,IDE在鏈接的時候可能會忽略這個變量(IDE認為它沒用,所以優(yōu)化了),那么怎么讓IDE強制鏈接myConfigBlock呢?IAR留了個后門,在options->Linker->Input選項卡中的Keep symbols輸入框里填入你想強制鏈接的對象名(注意是代碼中的對象名,而非linker文件中的自定義段名)即可。
-
存儲器
+關(guān)注
關(guān)注
38文章
7492瀏覽量
163833 -
嵌入式系統(tǒng)
+關(guān)注
關(guān)注
41文章
3593瀏覽量
129472 -
RAM
+關(guān)注
關(guān)注
8文章
1368瀏覽量
114689 -
IAR
+關(guān)注
關(guān)注
5文章
352瀏覽量
36681 -
Cortex-M
+關(guān)注
關(guān)注
2文章
229瀏覽量
29763
發(fā)布評論請先 登錄
相關(guān)推薦
評論