写点什么

用了这个评估优化 LiteOS 镜像利器,我有点飘...

发布于: 2020 年 12 月 23 日

摘要:本文会给大家介绍下 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, #0d:\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, #1d:\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 。

 

点击关注,第一时间了解华为云新鲜技术~


发布于: 2020 年 12 月 23 日阅读数: 35
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
用了这个评估优化LiteOS镜像利器,我有点飘...