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]);
}

此时完成界面切换,同时也可以正常刷新第二页的数据。

推荐文章

Ubuntu 22.04 使用apt安装python3.8

Ubuntu 22.04 使用APT安装Python3...

如何向Wordpress页面底部添加备案信息

我在网上查询了很多关于wordpress添加备案号的信...

评论区(暂无评论)

我要评论

昵称
邮箱
网址
0/200
没有评论
可按 ESC 键退出搜索

0 篇文章已搜寻到~