2021SC@SDUSC TencentOS Tiny源码分析(十三)动态内存管理机制模块二

公孙巴英
2023-12-01

2021SC@SDUSC


上周我们学习了TencentOS Tiny中的第一种 动态内存的管理机制–TLSF算法,并且看了其在TencentOS Tiny中的两个简单API的实现,本周我们就分析 第二种动态内存的管理机制–静态内存池管理算法

一. 静态内存池管理算法

静态内存池就是将一块内存划分为n个大小相等的块,用户可以动态的申请、释放一个块,表面上的体现就是在使用动态内存

内存池(Memory Pool)是一种用于分配大量大小相同的小对象的技术。它可以极大加快内存分配/释放的速度。

内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表,和TLSF中的空闲块链表类似)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。

内核是如何管理内存池的呢?当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列。

内核负责给内存池分配内存池对象控制块,它同时也接收用户线程的分配内存块申请,当获得这些信息后,内核就可以从内存池中为内存池分配内存。内存池一旦初始化完成,内部的内存块大小将不能再做调整。

这个算法的基本思想还是比起TLSF更显简单,但是其仍然具有广泛的应用和重要性,下面我们直接来看TencentOS Tiny中是如何实现静态内存池管理算法的:

二. TencentOS Tiny中静态内存池管理算法的实现

在TencentOS Tiny中关于静态内存池管理,一共有四个基本的API,分别是内存池的创建、销毁,从内存池中申请、放回一个空闲块;以及两个更高级的API,分别是内存池的动态创建和销毁,这里我们只分析四个基础API的实现

(一)创建内存池

TencentOS-tiny中静态内存池管理算法的实现在tos_mmblk.htos_mmblk.c中。我们首先来看内存池的创建

__API__ k_err_t tos_mmblk_pool_create(k_mmblk_pool_t *mbp, void *pool_start, size_t blk_num, size_t blk_size)
{
    uint32_t    i;
    void       *blk_curr, *blk_next;

    TOS_IN_IRQ_CHECK();
    TOS_PTR_SANITY_CHECK(pool_start);

    if (((cpu_addr_t)pool_start & K_MMBLK_ALIGN_MASK) != 0u) {
        return K_ERR_MMBLK_INVALID_POOL_ADDR;
    }

    if ((blk_size & K_MMBLK_ALIGN_MASK) != 0u) {
        return K_ERR_MMBLK_INVALID_BLK_SIZE;
    }

    blk_curr = pool_start;
    blk_next = K_MMBLK_NEXT_BLK(blk_curr, blk_size);

    for (i = 0; i < blk_num - 1u; ++i) {
        *(void **)blk_curr  = blk_next;
        blk_curr            = blk_next;
        blk_next            = K_MMBLK_NEXT_BLK(blk_next, blk_size);
    }
    *(void **)blk_curr = K_NULL;

    mbp->pool_start = pool_start;
    mbp->free_list  = pool_start;
    mbp->blk_free   = blk_num;
    mbp->blk_max    = blk_num;
    mbp->blk_size   = blk_size;

    TOS_OBJ_INIT(mbp, KNL_OBJ_TYPE_MMBLK_POOL);
    
    knl_object_alloc_set_static(&mbp->knl_obj);

    return K_ERR_NONE;
}

这个创建内存池的函数通过传入一个用来指向这个即将被创建的内存池的指针mbp和内存池的起始地址pool_start,内存池中内存块的数量blk_num,单个内存块的大小blk_size来创建内存池,系统可以自动的根据传入的这些参数来进行计算后给内存池分配空间,而不需要使用参数指定空间的具体大小,这样可以省掉一个参数。

然后来看一下实际的创建过程,整个创建过程关键部分是利用pool_startfree_listblk_freeblk_maxblk_size等各参数在初始化mbp指针所指向的内存池对象,最后再调用knl_object_alloc_set_static函数给内存池分配内存空间

(二)销毁内存池

__API__ k_err_t tos_mmblk_pool_destroy(k_mmblk_pool_t *mbp)
{
    TOS_PTR_SANITY_CHECK(mbp);
    TOS_OBJ_VERIFY(mbp, KNL_OBJ_TYPE_MMBLK_POOL);
    
#if TOS_CFG_MMHEAP_EN > 0u
    if (!knl_object_alloc_is_static(&mbp->knl_obj)) {
        return K_ERR_OBJ_INVALID_ALLOC_TYPE;
    }
#endif

    mbp->pool_start = K_NULL;
    mbp->free_list  = K_NULL;
    mbp->blk_free   = 0;
    mbp->blk_max    = 0;
    mbp->blk_size   = 0;

    TOS_OBJ_DEINIT(mbp);

    return K_ERR_NONE;
}

销毁内存池就比较简单了,首先利用mbp指针或者叫做句柄来设置所指向的内存池对象的参数列表信息,然后调用TOS_OBJ_DEINIT释放资源即可

(三)从内存池中获取内存块

__API__ k_err_t tos_mmblk_alloc(k_mmblk_pool_t *mbp, void **blk)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(mbp);
    TOS_OBJ_VERIFY(mbp, KNL_OBJ_TYPE_MMBLK_POOL);

    TOS_CPU_INT_DISABLE();
    if (mbp->blk_free == 0) {
        TOS_CPU_INT_ENABLE();
        *blk = K_NULL;
        return K_ERR_MMBLK_POOL_EMPTY;
    }
    *blk            = mbp->free_list;
    mbp->free_list  = *(void **)mbp->free_list;
    --mbp->blk_free;
    TOS_CPU_INT_ENABLE();
    return K_ERR_NONE;
}

从内存池中获取内存块时,首先调用TOS_CPU_INT_DISABLE保留CPU寄存器中的状态字然后关中断,使CPU执行下面的语句,如果内存池句柄mpb指向的内存池剩余空间参数为0,表示目前内存池无可分配的空闲内存块,那么就开中断,然后返回错误信息K_ERR_MMBLK_POOL_EMPTY表示内存池已空,如果内存池有剩余可分配空闲内存块,则首先将内存池中的内存块通过为参数blk指针赋值来分配出去然后通过mbp设置内存池对象的各项参数,完成之后就开中断返回成功执行信息

(四)将内存块资源释放回内存池

__API__ k_err_t tos_mmblk_free(k_mmblk_pool_t *mbp, void *blk)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(mbp);
    TOS_PTR_SANITY_CHECK(blk);
    TOS_OBJ_VERIFY(mbp, KNL_OBJ_TYPE_MMBLK_POOL);

    TOS_CPU_INT_DISABLE();
    if (mbp->blk_free >= mbp->blk_max) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_MMBLK_POOL_FULL;
    }

    *(void **)blk   = mbp->free_list;
    mbp->free_list  = blk;
    ++mbp->blk_free;
    TOS_CPU_INT_ENABLE();
    return K_ERR_NONE;
}

将内存块资源放回到内存池中和从内存池中取内存块的具体实现大同小异,首先也是通过调用TOS_CPU_INT_DISABLE保留CPU寄存器中的状态字然后关中断,使CPU执行下面的语句,不同的便是下面这里,首先要通过判断mbp->blk_free >= mbp->blk_max是否成立,如果成立则表示目前内存池没有被使用的空闲块,所有内存块均空闲,不再接收内存块资源的放入,然后就开中断,返回内存池已满的信息K_ERR_MMBLK_POOL_FULL如果内存池仍有资源未释放回来,则通过blk和mbp指针将资源释放回来,完成以后就开中断,返回成功执行信息

三. 以TLSF为代表的动态内存堆管理机制VS静态内存池管理机制

  • 内存堆(mmheap)管理机制,每次可以申请和释放**「不定大小的内存」**,分配时间虽然不快,但是能保证已知
  • 静态内存池(mmblk)管理机制,每次只能申请和释放一个块(「固定大小的内存」),分配时间最快,没有碎片

四. 整体课程总结

以上就是本周的所有工作内容了,关于TencentOS Tiny的源码分析工作也到此结束,在本次课程内容中,我一共写了13篇相关博客,分别对TencentOS tiny 的进程调度、调度器、队列、消息队列、内存管理等模块进行了知识学习和源码分析。下面简要描述一下对于我来说一篇博客的成长历程

  • 首先通过CSDN,百度,github,官方API等查询学习了针对于所分析模块的相关算法和知识,对自己对这块领域的知识背景做了一些了解
  • 之后深入到源码中去看TencentOS Tiny是如何将功能实现的,并且学习代码编辑技巧
    • 在分析源码的过程中,由于TencentOS Tiny是绝大部分用C语言完成的,我第一时间发现自己之前对于C语言的认识是如此的浅,在这里我见到了一些C语言的巧妙用法和高级技巧,这些是很难在平时的C语言基础学习中了解到的,而且TencentOS Tiny无疑为我提供了一个真正去体会这些技巧是如何在实际生产中应用的场景,比如说:do…while(0)的妙用,条件编译等一系列高级用法,这是我在本次源码学习中的第一收获
    • 第二点就是我对这个领域有了进一步的了解,之前我对于实时操作系统是一无所知,更不了解嵌入式操作系统,但是经过本次的学习,我了解了这些领域的一些知识,明白内核是如何在工作,同时对内核的接触也让我真正看到了平时所说的底层究竟是什么样的
    • 最后就是我认为这次源码学习很关键的一个作用就是让我想起来了之前学习的理论课程上的一些理论知识,让我得到了知识的贯通融合经验,深刻体会到了计算机学科是一个整体,不是分割的一块一块,而是相互协调来共同为计算机用户去提供服务的,让我有了整体的概念

最后,很感谢学校能安排本次源码分析课程,让我有了很大的提升!

 类似资料: