用了這個評估最佳化LiteOS映象利器,我有點飄...

farsun發表於2021-09-11
摘要:本文會給大家介紹下LiteOS Studio的映象分析工具,這可是一個評估、最佳化映象檔案RAM、ROM佔用大小的利器。

大家都知道嵌入式開發板由於受成本限制,晶片的RAM、Flash等硬體資源有限,比如有些低成本的開發板只有內建的64KB ROM、20KB RAM。在豐富功能特性程式設計時,一些看似無害的改變,都可能導致編譯出的映象膨脹,超出開發板的資源限制。對於硬體資源相對寬裕的開發板,合理的映象大小規劃,也會提升效能。本文會給大家介紹下LiteOS Studio的映象分析工具,這可是個評估、最佳化映象檔案RAM、ROM佔用大小的利器。

開發環境準備

前期的系列文章,我們掌握瞭如何安裝LiteOS Studio,如何新建STM32F769IDISCOVERY和Qemu realview-pbx-a9工程。這次我們以STM32F429IGTx開發板為例,建立工程的方式是一樣的,開發板選擇STM32F429IGTx即可。映象分析特性對任何LiteOS工程都是適用的,我們只是以該開發板為例。工程建立完畢後,執行編譯,輸出如下:

用了這個評估最佳化LiteOS映象利器,我有點飄...

終端控制檯輸出顯示編譯成功了,可執行檔案Huawei_LiteOS.elf對應的text段為74774 bytes,data段大小1444 bytes,bss段13080 bytes,dec表示前面三段大小的合計,為89268bytes。這些text、data、bss資料代表什麼?有什麼意義?我們繼續,後文中會詳細解釋。

LiteOS 映象分析特性

點選LiteOS Studio工具欄的調測工具Debug Tools按鈕,開啟除錯工具,選擇映象分析,這就是本文要給大家介紹的LiteOS Studio的映象分析工具。填寫可執行檔案路徑、Map檔案路徑等,如圖:

用了這個評估最佳化LiteOS映象利器,我有點飄...

點選確定按鈕,會自動開啟映象分析視窗。包含記憶體區域、詳細資訊、檔案大小、模組大小等4個選項卡。我們依次演示如何使用。

  • 記憶體區域

記憶體區域頁面評估分析LiteOS開發板工程對記憶體的細分使用情況。對於STM32F429IGTx開發板,顯示的記憶體區域region包含FLASH、RAM、CCMRAM,展示的資訊包含每個記憶體區域的名稱、起始記憶體地址、總大小、空閒大小、已用大小,使用比例。在這個記憶體區域頁面,除了數值展示分析,還提供餅圖可以宏觀的評估每個區域的使用、剩餘情況。如下圖所示,FLASH總大小 1024Kb,RAM總大小192Kb,對FLASH、RAM的使用率較低,剛剛超7%。對於CCMRAM更是沒有使用,CCM(Core Coupled Memory)是給STM32F4核心專用的全速64KB RAM。

用了這個評估最佳化LiteOS映象利器,我有點飄...

  • 詳細資訊

繼續點選詳細資訊選項卡開啟映象分析詳細資訊頁面,該頁面展示每個記憶體區域包含的記憶體段section,記憶體段包含的符號symbol的詳細資訊。 比如FLASH下面包含.isr_vector、.text、.rodata等記憶體段, 記憶體段又包含分配在該段的程式符號。每一行展示的資訊包含執行地址VMA(Virtual Memory Address)、裝載地址LMA(Load Memory Address)、記憶體段/符號的大小。其中,LMA表示程式裝載的記憶體地址;VMA表示程式執行時的記憶體地址。嵌入式系統中RAM記憶體空間有限,一般把程式放在FLASH中,LMA地址為Flash中的地址,等到程式執行時,再載入到RAM中的執行地址VMA。記憶體段.data、.vector_ram就屬於這種情況,VMA、LMA地址不一樣,並且在FLASH、RAM均出現。

在使用詳情頁面,支援排序和搜尋過濾,支援程式符號快速跳轉到原始碼行。我們可以很直觀的評估每個記憶體段、程式符號的使用大小情況,可以快速定位到潛在可最佳化的資源消耗大戶。
如圖:

用了這個評估最佳化LiteOS映象利器,我有點飄...

從映象分析圖表中,可以看出text、data段存放在Flash儲存,data、bss段資料存放在RAM儲存。那麼,連結器linker是怎麼知道如何把各個段的符號資料存放在ROM、RAM的?這就要靠連結指令碼。

連結指令碼和Text、Data、BSS Section段

連結指令碼包含一些規則來約束連結器如何把函式、變數放到ROM或RAM,STM32F429IGTx工程的連結指令碼位置在targetsCloud_STM32F429IGTx_FIREliteos.ld。我們分欄同時展示映象分析頁面和連結指令碼,如下圖,可以看出映象分析頁面展示的記憶體段使用情況和連結指令碼中所定義的是一致的,開發板的記憶體使用情況在LiteOS Studio 映象分析頁面得到非常直觀的展示。

記憶體段.ccmram在連結指令碼中沒有定義,已使用大小的也為0 byte。對於記憶體段.data、.vector_ram,連結指令碼中使用關鍵字 RAM AT> FLASH,來表示裝載地址、實際執行地址分別在FLASH、RAM。

用了這個評估最佳化LiteOS映象利器,我有點飄...

連結指令碼片段1如下,定義了中斷處理向量.isr_vector、程式.text存放在Flash儲存,使用的關鍵字為>FLASH,其中FLASH在上文的MEMORY{......}中定義。

  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

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

連結指令碼片段2如下,定義了.vector_ram、.data存放在Flash儲存,在程式啟動時,會從Flash複製到RAM。使用的關鍵字為 >RAM AT> FLASH,其中RAM在上文的MEMORY{}中定義。

 /* Initialized liteos vector sections goes into RAM, load LMA copy after code */
  .vector_ram :
  {
    . = ORIGIN(RAM);
    _s_liteos_vector = .;
    *(.data.vector)    /* liteos vector in ram */
    _e_liteos_vector = .;
  } > RAM AT> FLASH

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data ALIGN(0x1000):
  {
    __ram_data_start = _sdata;
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    KEEP(*( SORT (.liteos.table.*)));

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
    __ram_data_end = _edata;
  } >RAM AT> FLASH

連結指令碼片段3如下,定義了.bss、._user_heap_stack佔用RAM儲存。使用的關鍵字為 >RAM。

 .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_start = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
    __bss_end = _ebss;
  } >RAM

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

現在來總結一下,通常是這樣的:

  • Text段

Text儲存在只讀的ROM記憶體區域,包含向量表、程式程式碼、只讀常量資料。

  • Data段

表示初始化過的變數。儲存在FLASH、RAM。連結器分配data段資料在Flash區域,在startup啟動時,從ROM中複製到RAM。ROM中儲存的是隻讀的副本,開發板重啟也不會改變;RAM中儲存的是讀寫副本,在程式執行過程中,變數值可能會變化。

  • BSS段

表示未初始化的變數。RAM中未初始化的資料,在程式啟動時初始化為0。

  • 其他記憶體段

.user_heap_stack記憶體堆,malloc()申請記憶體使用此區域;.ARM.attributes、.debug_frame等儲存除錯資訊。

Map對映檔案、ASM反彙編檔案

在程式編譯連結時需要指定連結指令碼,編譯成功後會生成map對映檔案,儲存程式、資料的記憶體對映資訊。對映檔案是比較重要的輔助分析檔案,可以獲取程式中的函式、變數如何分配到RAM、ROM。在使用映象分析功能的時候,我們指定了Map對映檔案outCloud_STM32F429IGTx_FIREHuawei_LiteOS.map。反彙編檔案是另外一個比較重要的檔案,如果一個函式佔用了太多的text程式碼段、data資料段時,此時可以分析反彙編程式碼。
利用映象分析特性結合反彙編檔案,我們很方便評估函式是否可以進一步最佳化改進。以main函式為例,在對映檔案中的片段如下, main函式的存放地址為0x08000ea0,函式佔用空間大小0x38 bytes(=56 bytes)。

 .text.startup.main
                0x08000ea0       0x38 d:/LiteOS_master/out/Cloud_STM32F429IGTx_FIRE/liblibCloud_STM32F429IGTx_FIRE.a(main.o)
                0x08000ea0                main

這個資料在LiteOS Studio映象分析頁面也可以快速查閱到:

用了這個評估最佳化LiteOS映象利器,我有點飄...

下面是main()函式反彙編片段。可以看出,我們是C程式碼和反彙編混合展示的。第一列8000ea0是地址,第二列是彙編指令的機器碼、然後是彙編程式碼。
函式開始地址為0x8000ea0,結束地址為0x8000ed4,函式佔用空間大小=0x8000ed4-0x8000ea0+0x4=0x38 bytes。如果函式長度過長,結合分析反彙編程式碼行,進行定位最佳化。

08000ea0 <main>:
main():
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:45

INT32 main(VOID)
{
 8000ea0:    b598          push    {r3, r4, r7, lr}
 8000ea2:    af00          add    r7, sp, #0
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:46
    HardwareInit();
 8000ea4:    f7ff ffea     bl    8000e7c <HardwareInit>
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:48

    PRINT_RELEASE("n********Hello Huawei LiteOS********n"
 8000ea8:    4b07          ldr    r3, [pc, #28]    ; (8000ec8 <main+0x28>)
 8000eaa:    4a08          ldr    r2, [pc, #32]    ; (8000ecc <main+0x2c>)
 8000eac:    4908          ldr    r1, [pc, #32]    ; (8000ed0 <main+0x30>)
 8000eae:    4809          ldr    r0, [pc, #36]    ; (8000ed4 <main+0x34>)
 8000eb0:    f000 fb84     bl    80015bc <dprintf>
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:54
                  "nLiteOS Kernel Version : %sn"
                  "build data : %s %snn"
                  "**********************************n",
                  HW_LITEOS_KERNEL_VERSION_STRING, __DATE__, __TIME__);

    UINT32 ret = OsMain();
 8000eb4:    f003 fd18     bl    80048e8 <OsMain>
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:55
    if (ret != LOS_OK) {
 8000eb8:    b108          cbz    r0, 8000ebe <main+0x1e>
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:56
        return LOS_NOK;
 8000eba:    2001          movs    r0, #1
d:LiteOS_mastertargetsCloud_STM32F429IGTx_FIRE/Src/main.c:62
    }

    OsStart();

    return 0;
}
 8000ebc:    bd98          pop    {r3, r4, r7, pc}
 8000ebe:    4604          mov    r4, r0

動手試驗時間

我們動手編碼建立些不同型別的變數、函式,看看這些會分配到哪些記憶體段,實際分配是否符合我們已掌握的知識。增加下述程式碼片段到targetsCloud_STM32F429IGTx_FIRESrcuser_task.c檔案中的函式UINT32 app_init(VOID)的上方,在該UINT32 app_init(VOID)函式內首行增加對 ABC_FunName(0);的呼叫。

static UINT32 ABC_static_global_init = 1;
 UINT32 ABC_global_init = 1;
 UINT32 ABC_global_noInit;
 const UINT32 ABC_global_const = 1;

static VOID ABC_Static_FunName(VOID){
    printf("ABC_static_global_init is %d.n", ABC_static_global_init);
    printf("ABC_global_init is %d.n", ABC_global_init);
    printf("ABC_global_noInit is %d.n", ABC_global_noInit);
    printf("ABC_global_const is %d.n", ABC_global_const);
}

 UINT32 ABC_FunName(UINT32 ABC_num){
    CHAR *ABC_var_inFun = "1234567890";
    UINT32 ABC_var_inFuc_init = 1;
    static UINT32 ABC_static_InFuc_init = 1;
    static UINT32 ABC_static_InFuc_noinit;
 
    const UINT32 ABC_inFuc_const = 1;
    ABC_static_InFuc_noinit = 99;
    printf("ABC_var_inFuc_init is %d.n", ABC_var_inFuc_init);

    printf("ABC_static_InFuc_init is %d.n", ABC_static_InFuc_init);
    printf("ABC_static_InFuc_noinit is %d.n", ABC_static_InFuc_noinit);
    printf("ABC_inFuc_const is %d.n", ABC_inFuc_const);
 
    CHAR *buf = LOS_MemAlloc(m_aucSysMem0, 8);
    buf[0] = ABC_var_inFun[0];
    LOS_MemFree(m_aucSysMem0, buf);

    (VOID)ABC_Static_FunName();

    return 0;
 }

重新編譯,點選映象分析頁面的重新整理按鈕重新展示。我們新增的符號都以ABC_開頭,我們以這個關鍵字搜尋一下,可以看出來,ABC_FunName函式屬於.text程式碼段,全域性初始化的變數ABC_global_init屬於.data段,全域性未初始化變數ABC_global_noInit屬於.bss段。靜態函式ABC_Static_FunName、函式棧沒有在這個區域展示。這符合我們已掌握的知識,如下圖,大家也可以自行嘗試一下。

本文介紹了嵌入式開發中的記憶體佈局、連結指令碼,對映檔案,透過例項演示瞭如何利用LiteOS Studio的映象分析特性,如何結合反彙編檔案來評估函式空間佔用。 LiteOS Studio是我們LiteOS物聯網開發的利器!歡迎大家去使用這個特性,並分享使用心得,有任何問題、建議,都可以留言給我們。謝謝。

本文分享自華為雲社群《使用LiteOS Studio映象分析工具評估最佳化LiteOS映象 》,原文作者:zhushy 。

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4289/viewspace-2796276/,如需轉載,請註明出處,否則將追究法律責任。

相關文章