摘要:本文會給大家介紹下LiteOS Studio的鏡像分析工具,這可是一個評估、優化鏡像文件RAM、ROM占用大小的利器。
大家都知道嵌入式開發板由於受成本限制,芯片的RAM、Flash等硬件資源有限,比如有些低成本的開發板只有內置的64KB ROM、20KB RAM。在豐富功能特性編程時,一些看似無害的改變,都可能導致編譯出的鏡像膨脹,超出開發板的資源限制。對於硬件資源相對寬裕的開發板,合理的鏡像大小規划,也會提升性能。本文會給大家介紹下LiteOS Studio的鏡像分析工具,這可是個評估、優化鏡像文件RAM、ROM占用大小的利器。
開發環境准備
前期的系列文章,我們掌握了如何安裝LiteOS Studio,如何新建STM32F769IDISCOVERY和Qemu realview-pbx-a9工程。這次我們以STM32F429IGTx開發板為例,創建工程的方式是一樣的,開發板選擇STM32F429IGTx即可。鏡像分析特性對任何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文件路徑等,如圖:
點擊確定按鈕,會自動打開鏡像分析窗口。包含內存區域、詳細信息、文件大小、模塊大小等4個選項卡。我們依次演示如何使用。
- 內存區域
內存區域頁面評估分析LiteOS開發板工程對內存的細分使用情況。對於STM32F429IGTx開發板,顯示的內存區域region包含FLASH、RAM、CCMRAM,展示的信息包含每個內存區域的名稱、起始內存地址、總大小、空閑大小、已用大小,使用比例。在這個內存區域頁面,除了數值展示分析,還提供餅圖可以宏觀的評估每個區域的使用、剩余情況。如下圖所示,FLASH總大小 1024Kb,RAM總大小192Kb,對FLASH、RAM的使用率較低,剛剛超7%。對於CCMRAM更是沒有使用,CCM(Core Coupled Memory)是給STM32F4內核專用的全速64KB RAM。
- 詳細信息
繼續點擊詳細信息選項卡打開鏡像分析詳細信息頁面,該頁面展示每個內存區域包含的內存段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均出現。
在使用詳情頁面,支持排序和搜索過濾,支持程序符號快速跳轉到源代碼行。我們可以很直觀的評估每個內存段、程序符號的使用大小情況,可以快速定位到潛在可優化的資源消耗大戶。
如圖:
從鏡像分析圖表中,可以看出text、data段存放在Flash存儲,data、bss段數據存放在RAM存儲。那么,鏈接器linker是怎么知道如何把各個段的符號數據存放在ROM、RAM的?這就要靠鏈接腳本。
鏈接腳本和Text、Data、BSS Section段
鏈接腳本包含一些規則來約束鏈接器如何把函數、變量放到ROM或RAM,STM32F429IGTx工程的鏈接腳本位置在targets\Cloud_STM32F429IGTx_FIRE\liteos.ld。我們分欄同時展示鏡像分析頁面和鏈接腳本,如下圖,可以看出鏡像分析頁面展示的內存段使用情況和鏈接腳本中所定義的是一致的,開發板的內存使用情況在LiteOS Studio 鏡像分析頁面得到非常直觀的展示。
內存段.ccmram在鏈接腳本中沒有定義,已使用大小的也為0 byte。對於內存段.data、.vector_ram,鏈接腳本中使用關鍵字 RAM AT> FLASH,來表示裝載地址、實際運行地址分別在FLASH、RAM。
鏈接腳本片段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映射文件out\Cloud_STM32F429IGTx_FIRE\Huawei_LiteOS.map。反匯編文件是另外一個比較重要的文件,如果一個函數占用了太多的text代碼段、data數據段時,此時可以分析反匯編代碼。
利用鏡像分析特性結合反匯編文件,我們很方便評估函數是否可以進一步優化改進。以main函數為例,在映射文件中的片段如下, main函數的存放地址為0x08000ea0,函數占用空間大小0x38 bytes(=56 bytes)。
.text.startup.main 0x08000ea0 0x38 d:/LiteOS_master/out/Cloud_STM32F429IGTx_FIRE/lib\libCloud_STM32F429IGTx_FIRE.a(main.o) 0x08000ea0 main
這個數據在LiteOS Studio鏡像分析頁面也可以快速查閱到:
下面是main()函數反匯編片段。可以看出,我們是C代碼和反匯編混合展示的。第一列8000ea0是地址,第二列是匯編指令的機器碼、然后是匯編代碼。
函數開始地址為0x8000ea0,結束地址為0x8000ed4,函數占用空間大小=0x8000ed4-0x8000ea0+0x4=0x38 bytes。如果函數長度過長,結合分析反匯編代碼行,進行定位優化。
08000ea0 <main>: main(): d:\LiteOS_master\targets\Cloud_STM32F429IGTx_FIRE/Src/main.c:45 INT32 main(VOID) { 8000ea0: b598 push {r3, r4, r7, lr} 8000ea2: af00 add r7, sp, #0 d:\LiteOS_master\targets\Cloud_STM32F429IGTx_FIRE/Src/main.c:46 HardwareInit(); 8000ea4: f7ff ffea bl 8000e7c <HardwareInit> d:\LiteOS_master\targets\Cloud_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_master\targets\Cloud_STM32F429IGTx_FIRE/Src/main.c:54 "\nLiteOS Kernel Version : %s\n" "build data : %s %s\n\n" "**********************************\n", HW_LITEOS_KERNEL_VERSION_STRING, __DATE__, __TIME__); UINT32 ret = OsMain(); 8000eb4: f003 fd18 bl 80048e8 <OsMain> d:\LiteOS_master\targets\Cloud_STM32F429IGTx_FIRE/Src/main.c:55 if (ret != LOS_OK) { 8000eb8: b108 cbz r0, 8000ebe <main+0x1e> d:\LiteOS_master\targets\Cloud_STM32F429IGTx_FIRE/Src/main.c:56 return LOS_NOK; 8000eba: 2001 movs r0, #1 d:\LiteOS_master\targets\Cloud_STM32F429IGTx_FIRE/Src/main.c:62 } OsStart(); return 0; } 8000ebc: bd98 pop {r3, r4, r7, pc} 8000ebe: 4604 mov r4, r0
動手試驗時間
我們動手編碼創建些不同類型的變量、函數,看看這些會分配到哪些內存段,實際分配是否符合我們已掌握的知識。增加下述代碼片段到targets\Cloud_STM32F429IGTx_FIRE\Src\user_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物聯網開發的利器!歡迎大家去使用這個特性,並分享使用心得,有任何問題、建議,都可以留言給我們https://gitee.com/LiteOS/LiteOS_Studio/issues。謝謝。
本文分享自華為雲社區《使用LiteOS Studio鏡像分析工具評估優化LiteOS鏡像 》,原文作者:zhushy 。