乐趣区

关于开发:用了这个评估优化LiteOS镜像利器我有点飘

摘要: 本文会给大家介绍下 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 工程的链接脚本地位在 targetsCloud_STM32F429IGTx_FIREliteos.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 映射文件 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 镜像剖析页面也能够疾速查阅到:

上面是 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(“nHello Huawei LiteOSn” 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 物联网开发的利器!欢送大家去应用这个个性,并分享应用心得,有任何问题、倡议,都能够留言给咱们 https://gitee.com/LiteOS/Lite…。谢谢。

本文分享自华为云社区《应用 LiteOS Studio 镜像剖析工具评估优化 LiteOS 镜像》,原文作者:zhushy。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版