用了这个评估优化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工程的链接脚本位置在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 。
用了这个评估优化LiteOS镜像利器,我有点飘...的更多相关文章
- Docker容器技术-优化Docker镜像
一.优化Docker镜像 1.降低部署时间 一个大的Docker应用是如何影响在新Docker宿主机上的部署时间. (1)编写Dockerfile创建一个大Docker镜像 [root@bogon ~ ...
- Dockerfile镜像优化,减小镜像
前言镜像的优化注意几条: 选择最精简的基础镜像减少镜像的层数清理镜像构建的中间产物注意优化网络请求尽量去用构建缓存使用多阶段构建镜像接下来我们以rhel7镜像构建容器,并在容器中安装nginx的源码包 ...
- 优化 Docker 镜像大小常见方法
平时我们构建的 Docker 镜像通常比较大,占用大量的磁盘空间,随着容器的大规模部署,同样也会浪费宝贵的带宽资源.本文将介绍几种常用的方法来优化 Docker 镜像大小,这里我们使用 Docker ...
- 使用line_profiler对python代码性能进行评估优化
性能测试的意义 在做完一个python项目之后,我们经常要考虑对软件的性能进行优化.那么我们需要一个软件优化的思路,首先我们需要明确软件本身代码以及函数的瓶颈,最理想的情况就是有这样一个工具,能够将一 ...
- [图文] Fedora 28 使用 Virt-Manager 制作并优化QCOW2镜像——Windows 10 1709
实验说明: 云计算的发展使得桌面上云,windows 10就必不可少,这一章就如何制作QCOW2镜像文件并优化进行说明. 实验环境: 宿主机系统 :Fedora 28 WorkStation 虚拟 ...
- Android优化——UI检视利器:Hierarchy Viewer
在Android的SDK工具包中,有很多十分有用的工具,可以帮助程序员开发和测试Android应用程序,大大提高其工作效率.其中的一款叫 Hierachy Viewer的可视化调试工具,可以很方便地在 ...
- Docker镜像优化
前言 上篇博文说到使用Visual Studio Tools for Docker帮助我们生成Dockerfile,现在我们讨论下生成的Dockerfile的优劣. 一.以往Dockerfile构建模 ...
- 使用 Docker 部署 Node 应用 - 镜像文件尺寸的优化
前面 使用 Docker 部署 Node 应用 一文中完成了镜像的创建和运行,不过生成的镜像还有些粗糙,需要进一步优化. 镜像的优化 通过 docker images 看到简单的一个 node 服务端 ...
- NodeJS 服务 Docker 镜像极致优化指北
这段时间在开发一个腾讯文档全品类通用的 HTML 动态服务,为了方便各品类接入的生成与部署,也顺应上云的趋势,考虑使用 Docker 的方式来固定服务内容,统一进行制品版本的管理.本篇文章就将我在服务 ...
- Hadoop YARN:调度性能优化实践(转)
https://tech.meituan.com/2019/08/01/hadoop-yarn-scheduling-performance-optimization-practice.html 文章 ...
随机推荐
- 02-RAID技术 学习心得
RAID 术语 扇区:是磁盘中最小的存储单元,向磁盘读写数据时是以扇区为最小单元进行存储 block:block,是由N个扇区组成一个块: 在磁盘相同偏移处横向逻辑分割,就形成了stripee: 一个 ...
- 『STAOI』G - Round 1 半个游记
很刺激. 挂个链接
- OpenWrt主题在菜单中不显示
问题: 路径中有对应的主题,但是make menuconfig中不显示 原因: 需要建立软连接 1. 在路径 SDK-DR232-20221220/package/feeds/luci 中运行 ls ...
- springboot项目在docker中运行
前端时间需要把项目打包到docker中运行,于是就让组员去探索,最后整个过程是这样的. 首先我们做java开发,一般都是使用springboot开发,开发完成,我们需要把springboot项目打包成 ...
- [C++]线段树 区间查询 单点修改
线段树 区间查询 单点修改 算法思想 这个算法是用于数组的查询和修改 可以高效的进行查询修改 但是会增加内存的使用 本质上是一种 空间换时间 的算法 这个算法把一串数组无限二分 直到分的只剩下一个数据 ...
- JavaScript 简介与引用
作者:WangMin 格言:努力做好自己喜欢的每一件事 我们通常写好的HTML网页是处于一个静态的效果,在用户体验这一方面就不是很好,给人一种死板的感觉.这里我们就可以用到JavaScript来为网页 ...
- Java 基础学习第二弹
1. HashMap和HashT able的区别 HashMap和Hashtable是两种常见的哈希表数据结构,它们在实现上有一些区别. 线程安全性:Hashtable是线程安全的,而HashMap不 ...
- Flask解决跨域问题
什么是跨域问题 跨域问题指的是浏览器限制了从一个源(协议.域名.端口)访问另一个源的资源的行为,这个限制是浏览器的一个安全机制.如果一个网页从一个源加载了另一种类型的资源(例如 HTML.CSS.脚本 ...
- 题解 P4819
前言: 看到目前的题解当中没有并查集做法,于是写一篇水水. 题目描述: 给定一张图,一个图中有黑白两种颜色,已知黑色的点有且只有一个,且每个点是黑色的概率相等,然后点 \(u\) 与点 \(v\) 之 ...
- 在PowerShell脚本中获取程序集文件属性的指定元数据特性的方法——AssemblyMetadataAttribute
在PowerShell脚本中获取程序集文件属性的指定元数据特性的方法--AssemblyMetadataAttribute <# .SYNOPSIS 获取程序集文件属性的指定元数据特性 .DES ...