ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HoloCubic 中 LVGL Stress Demo 压力测试解析:在 PC 模拟器上排查内存泄漏与内存损坏

HoloCubic 中 LVGL Stress Demo 压力测试解析:在 PC 模拟器上排查内存泄漏与内存损坏
  • 嵌入式
  • 物联网
  • 智能硬件
  • 嵌入式GUI
  • 硬件开发

【免费下载链接】HoloCubic

带网络功能的伪全息透明显示桌面站

项目地址:https://gitcode.com/gh_mirrors/ho/HoloCubic
点击查看免费下载

LVGL 的 Stress Demo(lv_demo_stress)是一个专门为 GUI 系统设计的压力测试例程,它通过高频地创建、删除控件、驱动动画与切换样式,持续对内存管理器施加负载,用于发现内存泄漏、内存损坏与碎片化问题。本篇文章以 HoloCubic 仓库中自带的 LvglSimulator(VS2019 + SDL2 的 PC 端模拟器)为运行载体,完整讲解 Stress Demo 的启用配置、启动流程、状态机工作原理与内存监测机制,并结合lv_demo_stress.c与lv_mem.c源码,帮你掌握一套可在 PC 上离线复现、可量化观测的 LVGL 稳定性验证方法。

一、Stress Demo 是什么:定位与适用场景

在 HoloCubic 仓库的模拟器目录下,官方lv_examples的 README 将lv_demo_stress描述为:

A stress test for LVGL. It contains a lot of object creation, deletion, animations, styles usage, and so on. It can be used if there is any memory corruption during heavy usage or any memory leaks.

也就是说,它是一个面向 LVGL 的压力测试(stress test),而不是功能演示。它刻意制造大量高频的控件创建与删除、动画播放、样式应用与布局切换,从而把以下几类问题放大出来:

  • 内存泄漏(memory leak):对象创建后若未被正确释放,空闲内存会单调递减;
  • 内存损坏(memory corruption):越界写、悬垂指针等在正常使用中难以暴露,但在高频创建/删除的竞争场景下容易触发内存完整性校验失败;
  • 内存碎片化(fragmentation):大量小对象反复分配/释放后,空闲块被切碎,最大连续空闲块缩小,frag_pct指标持续升高。

由于 HoloCubic 的固件 GUI(lv_cubic_gui.c)与模拟器(lvgl_similator.cpp)共用同一套 LVGL 环境,在 PC 模拟器上先跑通 Stress Demo,再移植到 ESP32 等资源受限的 MCU 上,是一种低成本、可重复的稳定性验证路径。

二、运行前置条件:两个配置文件必须改对

原文档给出的启用方式是三步,其中前两步都是配置开关,缺一不可:

2.1 在lv_ex_conf.h中启用 Demo

在模拟器工程根目录的 lv_ex_conf.h 中,将LV_USE_DEMO_STRESS置为 1:

/*Stress test for LVGL*/ #define LV_USE_DEMO_STRESS 1

仓库当前配置已经默认开启(lv_ex_conf.h 中LV_USE_DEMO_STRESS 1)。作为对照,模板文件 lv_ex_conf_template.h 中的默认值是0,这提醒我们:在复制工程时如果发现编译出的二进制没有 Stress Demo,首先检查这个宏。

该宏的作用在源码中体现为两处:

  • lv_examples.h 中无条件#include "src/lv_demo_stress/lv_demo_stress.h",声明了入口函数lv_demo_stress(void);
  • lv_demo_stress.c 中整个实现体被#if LV_USE_DEMO_STRESS ... #endif包裹,宏为 0 时所有压力逻辑都会被裁剪掉。

2.2 在lv_conf.h中开启控件与动画

原文档强调必须在 lv_conf.h 中开启所有用到的控件(如LV_USE_BTN 1)以及动画(LV_USE_ANIMATION 1)。因为 Stress Demo 的状态机几乎遍历了 LVGL v7 的所有核心控件,任何一个被裁剪的控件都会导致链接失败或运行时崩溃。

仓库中的关键配置值如下(均位于 lv_conf.h):

配置宏当前值作用
LV_USE_ANIMATION1(L149)动画系统开关,Stress Demo 中大量lv_anim_*调用依赖它
LV_USE_BTN1(L582)按钮控件,lv_demo_stress中最基础的创建对象
LV_MEM_CUSTOM0(L80)使用 LVGL 内置内存管理器(lv_mem_alloc/lv_mem_free),这是 Stress Demo 能检测内存问题的前提
LV_MEM_SIZE32 * 1024字节(L83)内置堆大小,压力测试的负载会显著占用该堆
LV_HOR_RES_MAX/LV_VER_RES_MAX240/240(L23-L24)模拟器逻辑分辨率上限
LV_DPI130(L56)决定默认控件尺寸与滚动步长

补充一点:如果LV_MEM_CUSTOM被置为 1(改用malloc/free),那么lv_mem_monitor()与lv_mem_test()内部对内存块链表的大段逻辑会被#if LV_MEM_CUSTOM == 0跳过(见 lv_mem.c),泄漏检测将退化为对free_size的粗略观测。因此要做严谨的压力测试,建议保持内置内存管理器。

三、启动流程:在模拟器 main 中调用入口函数

原文档的第三步是:在lv_init()完成、驱动(display / indev)初始化之后,调用lv_demo_stress()。

在 HoloCubic 的模拟器工程 lvgl_similator.cpp 中,main()的调用顺序为:

  1. lv_init()—— LVGL 核心初始化;
  2. hal_init()—— 初始化 SDL 窗口显示器、鼠标/键盘输入设备,并创建 tick 线程(每 5ms 调用一次lv_tick_inc(5));
  3. 从 Demo 列表中选择一个执行(当前默认执行lv_demo_keypad_encoder(),lv_demo_stress()位于 第 66 行 的注释中);
  4. 进入while(1)主循环,周期性调用lv_task_handler(),每次Sleep(10)。
//lv_demo_widgets(); //lv_demo_benchmark(); lv_demo_keypad_encoder(); //lv_demo_printer(); //lv_demo_stress(); // <-- 取消注释即可运行 Stress Demo //lv_ex_get_started_1();

需要提醒:同一时刻只能取消注释一个 Demo 入口,否则会互相抢占屏幕与任务队列。启用 Stress Demo 时,建议把lv_demo_keypad_encoder()一并注释掉。

此外,工程文件 lvgl_similator.vcxproj 与 lvgl_similator.vcxproj.filters 已经将lv_demo_stress.c和lv_demo_stress.h纳入编译与过滤项,Visual Studio 2019 打开解决方案后无需额外手动添加文件。

lv_demo_stress()本身并不复杂(lv_demo_stress.c),它只做三件事:

void lv_demo_stress(void) { lv_task_create(obj_test_task_cb, TIME_STEP, LV_TASK_PRIO_MID, NULL); lv_style_init(&style_slow_trans); lv_style_set_bg_opa(&style_slow_trans, LV_STATE_DEFAULT, LV_OPA_COVER); lv_style_set_bg_color(&style_slow_trans, LV_STATE_DISABLED, LV_COLOR_SILVER); lv_style_set_transition_prop_1(&style_slow_trans, LV_STATE_DEFAULT, LV_STYLE_BG_COLOR); lv_style_set_transition_time(&style_slow_trans, LV_STATE_DEFAULT, TIME_STEP * 10); lv_mem_monitor_t mon; lv_mem_monitor(&mon); mem_free_start = mon.free_size; }
  • 创建一个周期任务obj_test_task_cb,周期为TIME_STEP(即 50ms),优先级为LV_TASK_PRIO_MID;
  • 初始化一个"慢速过渡"样式style_slow_trans,过渡时间为TIME_STEP * 10(500ms),用于后面的按钮禁用态颜色渐变测试;
  • 通过lv_mem_monitor()记录启动时刻的空闲内存mem_free_start,作为后续泄漏检测的基准线。

四、状态机原理:一个 50ms 一拍的控件"过山车"

Stress Demo 的核心是任务回调 obj_test_task_cb:每 50ms 被 LVGL 任务调度器调用一次,内部用静态变量state记录当前步进,每次调用末尾state++,形成一个从-1开始的有限状态机。以下按源码逐个阶段梳理(行号均指向 lv_demo_stress.c):

state主要操作源码行覆盖的压力点
-1执行lv_mem_test()完整性校验 +lv_mem_monitor()泄漏/碎片统计L71-L80内存健康基线
0创建lv_page容器main_page(半屏宽),内部创建按钮与多行符号 labelL82-L94页面容器 + 按钮 + 符号字体
1创建lv_tabview(4 个 tab),内含lv_cpicker与lv_led,带动画切换 tab;auto_del延迟删除L96-L115多标签容器 + 颜色选择器 + LED + 切换动画
2按钮应用慢速过渡样式进入LV_STATE_DISABLED;无限循环宽度动画(100↔200);格式化 label;lv_page_focusL117-L141样式过渡 + 无限动画 + 焦点滚动
3创建lv_textarea,设置占位文本L143-L147文本输入控件
4按钮通过 style 设置本地文本;lv_obj_del_async()异步删除;带 pattern 图标的按钮L150-L162异步删除 + 样式绘制模式
5lv_textarea切换为单行模式L164-L165控件模式切换
6page 布局切换为LV_LAYOUT_COLUMN_LEFTL167-L169布局算法
7lv_bar(范围 -1000~2000)、lv_slider(动画到越界值 5000)、两个lv_switch(开/关动画)L171-L191数值控件 + 越界值 + 开关动画
8lv_win窗口(标题 + 两个按钮)+lv_calendarL193-L203窗口容器 + 日历
9textarea 填入超长文本触发滚动L204-L207文本重排与滚动
10lv_keyboard从屏幕底部滑入动画后延迟删除L208-L219虚拟键盘 + 位移动画
11lv_dropdown展开并选中第 3 项L221-L227下拉列表展开动画
12lv_roller无限滚动模式,动画选中第 3 项L229-L235滚动选择器 + 循环滚动
13lv_arc(end angle 180→400 动画)+ 两个lv_linemeter(角度偏移动画)L237-L263弧形控件 + 指针仪表动画
14lv_msgbox消息框(双按钮、自动关闭)L265-L272模态对话框 + 自动关闭
15textarea 切回多行模式L274-L276模式切换
16lv_tileview磁贴视图(3 个有效位置、动画跳转)L278-L299磁贴布局 + 页面切换动画
17lv_gauge双指针仪表(红/蓝针)L301-L313指针仪表多指针绘制
18垂直列表(7 个按钮逐个延迟删除)+ 水平列表(LV_LAYOUT_ROW_MID)L314-L342列表构建 + 焦点 + 混合布局
19textarea 光标定位并插入含符号的文本L344-L347光标与文本插入
20page 布局恢复LV_LAYOUT_PRETTY_MIDL349-L351布局来回切换
21textarea 连续插入 16 个字符 + 5 个符号文本L353-L376高频文本写入
22lv_spinbox多步长递增(10/100/1)+ 页面垂直滚动L378-L396数字步进器 + 滚动
23lv_chart单系列 10 个数据点 + 页面滚动L398-L417图表数据刷新
24三个lv_checkbox(普通/选中/禁用)逐个延迟删除L419-L433复选框状态机
25textarea 光标定位并连续删除 64 个字符L435-L443批量文本删除
26textarea 再切单行模式L445-L446模式切换
29lv_page_clean(main_page)清空页面 + 删除 textareaL448-L452批量清理对象
31lv_obj_clean(lv_scr_act())清空整个屏幕,state重置为-2L453-L457全屏清理后进入下一轮循环

state从-1走到32后归零,压力场景便循环往复,直到人工关闭程序。可以看到:每一轮循环都会让几乎全部核心控件经历"创建 → 操作 → 动画 → 延迟删除"的完整生命周期,这正是制造内存压力的关键设计。

五、auto_del:藏在动画里的延迟删除器

源码中大量出现的auto_del(obj, delay)是一个静态辅助函数(lv_demo_stress.c),它的实现很巧妙——借用动画系统实现延时删除:

static void auto_del(lv_obj_t * obj, uint32_t delay) { lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, obj); lv_anim_set_time(&a, 0); lv_anim_set_delay(&a, delay); lv_anim_set_ready_cb(&a, lv_obj_del_anim_ready_cb); lv_anim_start(&a); }

它创建一个时间为 0、仅带delay延迟的动画,并把动画"结束回调"设置为lv_obj_del_anim_ready_cb。于是延迟时间一到,动画自然结束,lv_obj_del()便自动执行。这保证了:对象在被删除之前有充足时间经历动画/交互逻辑,且删除动作交由 LVGL 动画与任务系统调度,避免在回调栈里直接释放造成悬垂指针。

对比之下,state 4还使用了lv_obj_del_async()(L154)——对象不会立即删除,而是等下一次lv_task_handler()再删。两种删除路径并存,正是为了同时覆盖"同步调度删除"与"异步安全删除"两条代码分支。

六、内存观测机制:三个指标看懂压力结果

Stress Demo 每次回到state -1时都会打印一行统计(lv_demo_stress.c):

lv_res_t res = lv_mem_test(); if(res != LV_RES_OK) LV_LOG_ERROR("Memory integrity error"); lv_mem_monitor_t mon; lv_mem_monitor(&mon); volatile int32_t diff = mem_free_start - mon.free_size; LV_LOG_USER("mem leak since start: %d, frag: %3d %%", diff, mon.frag_pct);

6.1 内存完整性校验:lv_mem_test()

lv_mem_test()的实现在 lv_mem.c:它遍历内置堆的所有内存块头,检查两个越界条件:

  • 单个块大小d_size > LV_MEM_SIZE;
  • 块地址e8 + d_size超出work_mem + LV_MEM_SIZE的堆边界。

任何一条不满足都会返回LV_RES_INV,触发LV_LOG_ERROR("Memory integrity error")。这是检测内存损坏(越界写、双释放)最直接的信号。

6.2 泄漏检测:mem leak since start

mem_free_start在lv_demo_stress()入口记录启动时的空闲内存。此后每个周期计算diff = mem_free_start - mon.free_size,即"自启动以来流失的空闲内存"。如果一轮轮跑下去diff持续、无界地增长,就说明有对象在删除后未归还内存——内存泄漏。由于每轮结束时都会lv_obj_clean(lv_scr_act())清屏,理论上每轮结束内存应回到近似基准,diff应围绕某个小值波动;一旦它单调爬升,即可锁定泄漏点。

6.3 碎片率:frag: xx %

frag_pct由 lv_mem.c 计算得出:

mon_p->frag_pct = (uint32_t)mon_p->free_biggest_size * 100U / mon_p->free_size; mon_p->frag_pct = 100 - mon_p->frag_pct;

即100% - 最大连续空闲块占空闲总量的比例。碎片率越高,说明空闲内存被切得越碎,未来大块分配(如全屏图片 buffer)越容易失败。lv_mem_monitor_t的完整字段定义在 lv_mem.h,除frag_pct外还包括total_size、free_size、free_biggest_size、used_pct、max_used等,可用于更细粒度的观测。

从源码结构看,若需要更完整的内存曲线,可以在主循环中周期性调用lv_mem_monitor()并把free_size/max_used记录下来绘图——仓库中的 lv_test_style.c 也采用了"起点监控 + 终点对比"的同类做法,可作为扩展参考。

七、在 HoloCubic 场景中的使用建议

HoloCubic 的模拟器工程(LvglSimulator)为开发者在 PC 上验证 LVGL 行为提供了与固件一致的运行环境,因此 Stress Demo 的典型用法是:

  1. 固件改动前的回归验证:在 lvgl_similator.cpp 中切换到lv_demo_stress(),跑 10~30 分钟观察日志,确认mem leak since start不持续增长、frag稳定、无Memory integrity error;
  2. 控件裁剪后的完整性确认:修改lv_conf.h后再次运行 Stress Demo,若裁剪不当,编译期或运行期会立即暴露缺失的控件引用;
  3. 内存预算评估:结合LV_MEM_SIZE(当前为 32KB)与max_used字段,可以估算真实固件在 HoloCubic-fw 中应预留的堆大小;
  4. 与固件侧联调:固件中的 GUI 代码(lv_cubic_gui.c、setup_scr_home.c)与模拟器共用 LVGL API 体系,模拟器上通过的压力场景可以作为固件移植的参考依据。

需要说明的适用前提:Stress Demo 面向 LVGL v7.6.1(见 lv_ex_conf.h 的版本注释)以及模拟器工程自身的 240×240 逻辑分辨率配置;若在更高分辨率或不同 LVGL 版本上运行,控件布局与内存压力会随之变化,建议以当前仓库的配置为基准进行对比。

八、小结

lv_demo_stress不是展示功能的花架子,而是一台"GUI 内存压力机":它用 50ms 一拍的状态机轮番压测 page、tabview、bar、slider、switch、window、keyboard、dropdown、roller、arc、gauge、chart、list、msgbox、tileview 等全部核心控件,配合lv_mem_test()/lv_mem_monitor()/auto_del三件套,把内存损坏、内存泄漏与碎片化问题显性化。结合 HoloCubic 自带的 VS2019 + SDL2 模拟器工程,开发者可以在 PC 上以最小的成本完成 LVGL 稳定性的回归验证,再放心地将其固化到伪全息桌面站的嵌入式固件中。

关键文件索引:

  • 入口实现:lv_demo_stress.c
  • 头文件声明:lv_demo_stress.h
  • Demo 总开关:lv_ex_conf.h
  • 控件/内存配置:lv_conf.h
  • 模拟器入口与调用位置:lvgl_similator.cpp
  • 内存监控实现:lv_mem.c、lv_mem.h
  • 嵌入式
  • 物联网
  • 智能硬件
  • 嵌入式GUI
  • 硬件开发

【免费下载链接】HoloCubic

带网络功能的伪全息透明显示桌面站

项目地址:https://gitcode.com/gh_mirrors/ho/HoloCubic
点击查看免费下载

相关推荐

上一篇:SiYuan v3.5.5 版本技术解读:mmap 落盘、未引用数据库清理与 I/O 性能优化全解析
下一篇:ZeroTermux 内置命令手册解读:file 命令——探测文件类型的三个检查过程与实战用法

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进