MTK:内存管理机制简单分析

MTK内存管理机制简单分析

 

1:内存:

  内存,在手机里面,是个较为紧缺的资源,特别是在功能机上面。经常在功能机上面产生的内存不足,申请失败的地方比比皆是,
更是屡见不鲜,经常会为了节省内存,会进行代码的优化,节省出一些空间出来。往往申请较大的比如700k之上的,总是需要考虑
谨慎,处理申请失败的情况。内存泄露,对于长期不关机的手机来说,更是一个潜在的威胁,对于设计人员的能力,则有较大的考
验。比如保证内存的申请与释放匹配,否则长期的累计,会使得内存可用一直在降低,最后导致没有可用内存可用,导致死机发生。
  内存碎片,也是一个需要注意的地方,比如设计不合理的内存管理机制,对于内存碎片未作较好的设计,会导致出现剩余空间很大,
但是连续的可用空间很小,导致无法申请较大的内存,使得功能受限,直接导致性能的衰弱。因此一般手机内存的管理,都会分块处理
,规划出一些空间,专门来为小内存的申请和释放,再规划一些空间,来进行大内存的分配管理,这样即可有效的规避掉内存碎片的
发生,MTk既是如此来处理的,当然其他的手机平台亦是如此考虑的。
2:开始来分析MTK内存的管理方案:
  首先,我们这边需要一个简化,便是将互斥操作直接忽略掉,直接关注核心处理。
  下面我们来说第一种分配的接口以及使用,一般是用在较大内存的申请,偏向驱动底层的内存分配,一般MMI不建议使用
  在med_mem.h存在如下的代码片段:
typedef union
{
    /* default size */
    kal_uint8 MED_DUFAULT_EXT_SIZE[MED_MEM_EXT_DEFAULT_SIZE];
    ......
    kal_uint8 MED_EXT_CAM_CAPTURE_W_FS_SORT_W_AUD[MED_MEM_CONC3_SIZE(MAX_MED_CAM_CAP_EXT_MEM_SIZE,MAX_FS_SORT_MEM_SIZE,AUD_EXT_MEM_SIZE)];
    ......

} med_ext_mem_union;
 #define MED_EXT_MEM_SIZE sizeof(med_ext_mem_union)  

  这个联合里面包含着各个需要从此内存池获取内存,各自需要的最大数值, sizeof(med_ext_mem_union) 得到的是联合里面最大的结构所占用的空间大小

  随后在med_main.c里面有如下代码:
          #pragma arm section zidata = "LARGEPOOL_ZI"
        __align(4)     kal_uint8 med_ext_mem[MED_EXT_MEM_SIZE];
        #pragma arm section zidata
  这个数组便是在运行时作为内存分配空间使用的。这里来看下几个语法 
#pragma arm section zidata = "LARGEPOOL_ZI"
****
#pragma arm section zidata
这个代表的意思为中间的        __align(4)     kal_uint8 med_ext_mem[MED_EXT_MEM_SIZE];最终编译链接的位置,是属于段LARGEPOOL_ZI里面的,
从而链接到的位置会依赖以链接脚本的LARGEPOOL_ZI段的位置。zidata的意思为未初始化的变量,与zidata相关的便是ro,只读,rw可读可写,zi和
rw的区别便是zi不占用rom空间,因为不需要保存初始值,会在运行时初始化为0,而rw数据,是有初始数据的,因此会在rom和ram都会占用空间,来保存初始值
以及运行时的修改。
 __align(4)这个代表的是2的4次方对齐,来保证内存的起点在16的整数倍上,这样才能使得内存管理机制较好的处理和管理。
现在已经知道了内存分配的来源,下来来看看内存是如何管理以及分配的。
3:内存管理的初始化: 
  调用关系
  med_create--med_init--med_utility_init
  med_create里面存储的是一个结构,包含了5个信息,task的初始化函数,主要的消息循环函数,配置函数,重启函数,以及退出函数。
  med_init便是初始化函数。
  med_utility_init函数里面通过调用med_set_ext_memory_pool来将med_ext_mem数组和分配的管理结构关联起来,具体如下:
 
  void med_set_ext_memory_pool(address_t memory, size_type size)
  传递的参数为med_ext_mem,以及MED_EXT_MEM_SIZE,此时便会将这个数组的信息传递到管理结构里面,具体的为:

      g_med_ext_mem_cntx.ext_mem_pool_id = kal_adm_create(
                                            memory,
                                            size,
                                            NULL,                                          
                                            KAL_FALSE
                                            );
    g_med_ext_mem_cntx.ext_mem_alloc_count = 0;
    g_med_ext_mem_cntx.ext_mem_left_size = size;
    
    g_med_ext_mem_cntx.temp_mem_list = NULL;
    g_med_ext_mem_cntx.is_debug_info_on = KAL_FALSE;
  
   kal_adm_create函数将med_ext_mem数组进行构造,返回出一个id,随后的申请都会围绕着这个id来进行。
   ext_mem_alloc_count ,申请计数
   ext_mem_left_size 大小(已使用的大小为: MED_EXT_MEM_SIZE-ext_mem_left_size)
4:内存的申请以及释放:
   med_alloc_ext_mem_ext这个函数是来处理申请的一个核心接口,其他上层的使用宏来进行调用,本质还是调用在此函数上面。
   此函数的接口参数为:
   申请大小,类别,文件,行号
   其中类别为:
   typedef enum
{
    MED_INT_MEMORY_TYPE,
    MED_EXT_MEMORY_TYPE_NONCACHEABLE,
    MED_EXT_MEMORY_TYPE_CACHEABLE,
    MED_EXT_MEMORY_TYPE_FRAMEBUFFER,
    MED_EXT_MEMORY_TYPE_TOPPEST_NONCACHEABLE,
    MED_EXT_MEMORY_TYPE_TOPPEST_CACHEABLE,
    MED_EXT_MEMORY_TYPE_AUDIO_NONCACHEABLE,
    MED_EXT_MEMORY_TYPE_AUDIO_CACHEABLE,
    MED_EXT_MEMORY_TYPE_COUNT
} med_memory_type_enum;
  med_alloc_ext_mem_ext函数的关键处理过程:

    kal_take_mutex(med_mem_mutex); //获取互斥操作信号量
    p = (void*)med_ext_smalloc_ext((size_type) size, location, file_p, line_p);
    kal_give_mutex(med_mem_mutex);//释放互斥操作信号量
   具体来看看med_ext_smalloc_ext这个函数吧:
    这个函数会来依据med_memory_type_enum来进行处理,具体的由于没有源码,不好说明,但是基本可以描述下:  
    有一个标示着为MED_MEMORY_WITH_TEMPORARY_FLAG,有这个标示的话,申请时先查询是否已经释放,没有的话先释放再申请。
    一般是从低地址进行申请,如果类型为MED_EXT_MEMORY_TYPE_TOPPEST_CACHEABLE或者MED_EXT_MEMORY_TYPE_TOPPEST_NONCACHEABLE
     则会从高地址来进行获取。
    cacheable 和noncacheable的区别应该为申请的地方不同,具体的不可得知。
    通过这个方式来管理的还有与声音audio有关的申请,声音单独使用kal_adm_create重新构造一个内存区域来进行管理,应该是为了规避其他task的申请
    影响到声音相关关键申请,而导致品质下降或者严重的死机产生。
    以上便是MTK内存管理的一个方式,下来我们来说一般的小的内存申请的方式,一般限制在2k大小的很小的一个结构申请,一般的管理方案是由一组数组
    组成,比如:
    g_buffer[]=
    {
//多少字节 总共数目 数组地址
    {50,1000,g_50_buffer},
    {100,1000,g_100_buffer},
    }
    这里只是一个例子,不代表MTK就是如此数据,随后申请时比如申请20字节,那么调用接口OslMalloc(对应的释放为OslMfree)进行申请空间。
    函数会在g_buffer里面查找,看当前50字节是否有剩余,有则返回g_50_buffer里面空闲的一项地址,然后进行修正剩余空闲数目,以备其他申请或者查询使用。
    如果50字节的1000个都被申请完了,那么便会进入100字节里面查询,如果有空闲,返回地址,没有继续查询,直到数组结束,数组结束也没有分配成功,则
    会产生断言,死机重启。
3:app的申请内存管理方案分析:
   ASM =application shared Memory ,这种机制是为了AP直接共享内存而产生的,比之Med第一种的分配管理,这个便多了更多的一些细节,可以更加友好的实现
   在内存不足时,提示用户是否愿意释放一些空间,来满足当前ap的运行使用。
   首先,我们先来看看内存分配数组的结构:
   app_mem_config.h里面:
    typedef union
{
    kal_uint8 SUB_POOL[sizeof(app_asm_pool_sub_union) + GDI_ASM_MEM_SIZE];
    kal_uint8 DUFAULT_POOL[APPLIB_MEM_AP_POOL_SIZE_CONFIG(APPLIB_MEM_AP_POOL_DEFAULT_SIZE)];
    .....
    kal_uint8 APP_UNIT_TEST[APPLIB_MEM_AP_POOL_SIZE_CONFIG(APPMEM_TEST_POOL_SIZE)];
    ......

} app_asm_pool_union;  
 #define APPLIB_MEM_AP_POOL_SIZE sizeof(app_asm_pool_union)
       是不是和Med第一种的分配管理基本保持一致呢?其实此时就是保持一致的。下来看下内存分配的数组的位置:
       MemoryRes.c里面:
       ALIGN(ASM_ALIGN_SIZE)  U8 g_applib_mem_ap_pool[APPLIB_MEM_AP_POOL_SIZE + 10240];
       这个就不需要解释了吧。
    我们再看看初始化这个数组的地方吧:
    MMI_Init--mmi_frm_appmem_stage1_init--applib_mem_ap_init
    MMI_Init在TaskInit.c里面的mmi_handler_info数组里面。
    我们现在来细看下applib_mem_ap_init函数:
     参数为 stop_finish_callback_by_MMI,g_applib_mem_ap_pool,APPLIB_MEM_AP_POOL_SIZE 这里需要说明stop_finished_handler函数的意义
    这里这个函数的目的是当一个应用申请失败时,会弹出是否需要释放其他应用来满足此应用的运行,(这里是否弹出需要释放其他应用内存,也是由用户来配置的,不需要的话
    便跟一般的申请内存一致了),当点击释放时,会调用注册的释放函数来完成释放,释放完毕后,会通知应用释放完毕,来进行再次尝试启动。
    applib_mem_ap_init这个函数内部的实现为:
        g_applib_mem_cntx.app_pool_id = kal_adm_create(
                                        g_applib_mem_ap_pool,
                                        APPLIB_MEM_AP_POOL_SIZE,
                                        (kal_uint32*) g_applib_mem_pool_chunk_size,
                                        KAL_FALSE);
    
    ASM_ASSERT(stop_finish_callback_by_MMI != NULL);
    g_applib_mem_cntx.app_stop_finish_callback = stop_finish_callback_by_MMI;
    g_applib_mem_cntx.app_pool_inited = KAL_TRUE;
    g_applib_mem_cntx.app_pool      = g_applib_mem_ap_pool;
    g_applib_mem_cntx.app_pool_size = APPLIB_MEM_AP_POOL_SIZE;
    //这块便是和Med第一种的分配管理基本一致了
    initialize_UI_demo--mmi_frm_appmem_init这个结构里面,便是赋值默认的管理申请内存失败,停止回调以及默认释放函数,以及预检测内存函数,预检测
    内存函数可以先进行检测是否能够申请得到内存。
4:ASM的管理数据结构:
   static applib_mem_cntx_struct g_applib_mem_cntx;
   这个结构保存着所有的相关信息,具体结构的信息为:

   typedef struct
{
    /**************************************************************************
     * app *
    **************************************************************************/

    /* ADM pool ID of app-based ASM. */
    KAL_ADM_ID app_pool_id;  //创建时保存的id,随后申请内存时需要

    /* memory buffer pool */
    void *app_pool;  // 创建的内存分配地址

    /* buffer pool size */
    kal_uint32 app_pool_size; // 创建的内存分配大小

    /* Count of allocated app-based blocks */
    kal_int32 app_alloc_count; //申请的计数

    /* Callback handler to process the result of stopping application in MMI task */
    app_stop_finish_callback_type app_stop_finish_callback; //停止应用的回调函数

    /* allocation fail handler */
    applib_mem_ap_allocation_fail_handler_type app_allocation_fail_handler;//申请失败的函数

    /* default stop callback */
    applib_mem_stop_handler_type app_stop_callback_default;//默认的停止函数

    /* callback before allocate memory */
    applib_mem_ap_pre_alloc_hdlr app_pre_alloc_handler; //预申请的函数

    /* callback after memory is freed */
    applib_mem_freed_handler mem_freed_handler;//释放后的函数

    /* Head node of app-based chunk */
    applib_mem_header_struct app_head;//记录着使用情况

    /* Initialized */
    kal_bool app_pool_inited; //是否已初始化

   
    /* app stop info */
    applib_mem_app_stop_info_struct app_stop_info[APPLIB_MEM_APP_STOP_MAX];//记录enum的stop的信息

    /* application info */    
    applib_mem_app_info_struct app_info[APPLIB_MEM_APP_COUNT];// 记录asm申请时的ap id信息

    /* Multi-thread mutex */
    mmi_frm_recursive_mutex_struct app_asm_mutex;//互斥信号
    ....去除掉了screen的一组数据,管理是和app这个是一致的。

} applib_mem_cntx_struct;
 现在我们来看看一个关键的定义APPLIB_MEM_APP_COUNT,这个数值。
  在app_mem.h里面有这个一个枚举:applib_mem_ap_id_enum
   内容为:
   typedef enum {

    APPLIB_MEM_AP_ID_DUMMY,
    APPLIB_MEM_AP_ID_QQ,
    APPLIB_MEM_AP_ID_IVEX,
    APPLIB_MEM_AP_ID_EM,
    .....
    APPLIB_MEM_AP_ID_VENUS,
    ......
    APPLIB_MEM_AP_ID_QQ_MOVIE,
    APPLIB_MEM_AP_ID_MARK_SEVERAL,
    APPLIB_MEM_AP_ID_TOTAL
} applib_mem_ap_id_enum;
  APPLIB_MEM_APP_COUNT =APPLIB_MEM_AP_ID_TOTAL-APPLIB_MEM_AP_ID_DUMMY
  这个枚举便是随后你申请空间时的一个关键依赖值,下面来看看这个空间的申请方式:
  关键函数1:applib_mem_ap_register
  参数applib_mem_ap_id_enum值,弹出框时的字串信息,弹出框时的图标,停止函数(当用户点击停止时会调用,释放内存,通知asm管理来处理数据)
  关键函数2:applib_mem_ap_alloc申请空间函数:
  参数applib_mem_ap_id_enum值,申请大小
  下来便是asm和其他的区别了:
   如果申请失败,可以调用 mmi_frm_appmem_prompt_to_release_mem函数来进行释放当前申请空间的一些应用,这个函数的参数为:
   pplib_mem_ap_id_enum值,图标,所需要的内存大小,释放成功后的函数(来完成释放后重新启动的动作)
   函数接口:
    applib_mem_ap_get_total_left_size 获取总共剩余的空间
    applib_mem_ap_get_max_alloc_size 获取最大可以申请的空间
    applib_mem_ap_get_pool_size 获取总数
    applib_mem_ap_alloc_full_pool_int 这个为申请pool上的所有空间,这个接口会占完全部的空间,如果不释放时,
    再去申请的话,会失败。
5:ASM里面的screen的内存管理:
    屏幕 screen的内存管理,结构上和APP Asm的管理完全一致,仅是接口的不同而已。

   总结:
     内存管理算是操作系统中设计关键部分之一,在手机的平台上,内存管理的算法设计考虑,若不能有效的来规避内存碎片,则会导致性能指数下降,
     直接影响到系统的存活周期,因此往往都会在内存的管理方案上下足成本,当然当前成熟的设计思路也是有效避免内存碎片,分割的无法使用的有效策略。
     看过几个其他的平台(mstar和展讯),内存申请无外乎这些考虑,但是相比而言,mstar的封装很好,不需要关注到底申请到哪里的空间内存,上层接口
     保持一致,如此便保证了用户层的统一,不至有所迷惑,一致不知所措。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值