LVGL开启页面切换同时清理内存引发的内存安全问题
LVGL开启页面切换同时清理内存引发的内存安全问题
一、前言
内存紧张情况下需要高度复用内存,页面切换时需要删除页面并释放内存,腾出空间给另一页面使用。
我为每个页面都设计了任务栏,有时间、SD卡、电量状态,这些状态需要周期刷新。结合我复用内存的操作会存在踩内存的可能。
另外你需要了解LVGL 线程不安全 。参考 函数可重入性(Reentrancy)概念详解,几乎所有涉及到LVGL的API使用前必须加锁,包括lv_task_handler(),但是lv_scr_act() 、lv_async_call 是不需要加锁的。
但加锁这个解决方案并不优雅,当UI设计比较复杂时加锁,任务时序会非常复杂难以调试,同时会导致锁竞争,即多个线程都需要使用LVGL来更新画面数据,造成非常大的性能开销。
此外我使用segger systemview分析操作系统任务时序,加锁后事件会非常多,造成事件Over flow。
二、两种方案
这两种方案不使用锁,同时也不使用事件通知等功能,我使用全局变量在线程与LVGL间通讯,即线程向全局变量写入数据,LVGL轮询线程读取全局变量统一更新界面。
1. 不使用锁,页面切换时不删除页面和回收内存
这种方案简单,易于实现,但不能删除页面和回收内存,因此对系统内存提出了一定要求;因为不删除页面回收内存,任何时候lvgl线程都可以访问修改任意页面的数据,这也不存在踩内存问题。
其他线程不调用LVGL API,把需要周期刷新时间、SD卡状态、电量的API直接扔到lv_task_handler();所在的线程内,这样lv_task_handler()函数退出后可以安全执行其他lvgl API。
实测效果非常稳定,没有出现系统异常问题
2. 不使用锁,页面切换时删除页面同时回收内存
在这种情况下使用第一种方法出现了踩内存问题 内存泄漏 & 内存溢出 & 踩内存 & malloc底层实现原理_踩内存和内存泄漏-CSDN博客
当页面切换时存在 准备切换→删除页面回收内存→完成切换 的过程,问题就出在中间删除页面回收内存的阶段,由于由于我使用的轮询方法会周期访问页面控件,当进入删除页面回收内存的阶段后LVGL轮询线程并不知道那些页面&控件被删除了,此时再尝试更新控件内容就会出现问题。
当然我也尝试了解决方案,在LVGL轮询线程更新控件之前检查LVGL当前页,只操作当前页内的控件,结果仍然会出现内存问题
解决方案
使用LVGL 内部 Timer 来替代LVGL轮询线程,将刷新任务放到Timer回调函数中执行,我的程序中创建了两个页面,这里直接创建两个周期Timer,一个Timer用来刷新第一页的控件,另一个Timer用来刷新第二页的控件。
当进入首页时先启动第一个Timer,停止第二个Timer
// Lvgl Handler
void lvglTaskEntry(void *argument)
{
global_param.lvgl_timer_refresh[0] = lv_timer_create(lvgl_refresh_main, 500, lv_scr_act());
lv_timer_set_repeat_count(global_param.lvgl_timer_refresh[0],-1);
lv_timer_ready(global_param.lvgl_timer_refresh[0]);
global_param.lvgl_timer_refresh[1] = lv_timer_create(lvgl_refresh_setting, 500, lv_scr_act());
lv_timer_set_repeat_count(global_param.lvgl_timer_refresh[1],-1);
lv_timer_ready(global_param.lvgl_timer_refresh[1]);
lv_timer_pause(global_param.lvgl_timer_refresh[1]);
for(;;)
{
/*------------------------------1.LVGL-Handle----------------------------------------*/
osDelayUntil(5);
#ifndef LVGL_LOCK
// lvgl 线程不安全 所有lv开头函数必须加锁
if(osMutexAcquire(global_param.os_lock_lvgl,100)==osOK)
{
lv_task_handler();
osMutexRelease(global_param.os_lock_lvgl);
}
#else
lv_task_handler();
#endif
}
}
当切换页面进入 准备切换阶段 时停止第一个Timer
/// @brief 主界面 设置按钮 弹起
/// 初始化前期回调
void btn_main_set_clicked_pre_cb(void)
{
if(global_param.init_cam)
{
HAL_DCMI_Stop(&hdcmi); // 停止摄像头数据传输
OV5640_Power(3); // 摄像头省电模式
}
// 截图提示窗口提示隐藏
lv_obj_add_flag(guider_ui.Main_label_snapshot_msg, LV_OBJ_FLAG_HIDDEN);
// 暂停主界面数据刷新定时器
lv_timer_pause(global_param.lvgl_timer_refresh[0]);
}
当切换页面进入 完成切换阶段 时开启第二个Timer
/// @brief 主界面 设置按钮 弹起
/// 初始化后期回调
void btn_main_set_clicked_post_cb(void)
{
// 开启设置界面数据刷新定时器
lv_timer_resume(global_param.lvgl_timer_refresh[1]);
}
此时完成界面切换,同时也可以正常刷新第二页的数据。
文章作者:四文鱼Max
本文链接:https://blog.awolon.fun/archives/lvgl-page-switch-clean-memory-cause-memery-problem.html
许可协议:CC BY-SA 4.0