嵌入式系统高效内存池的实现方法转让专利

申请号 : CN201010515419.2

文献号 : CN101968772B

文献日 :

基本信息:

PDF:

法律信息:

相似专利:

发明人 : 马红斌张峰付华楷李宁胡小波

申请人 : 烽火通信科技股份有限公司

摘要 :

本发明公开了一种嵌入式系统高效内存池的实现方法,内存块中非易变成员只在第一次分配或者在主、副内存池之间借用内存块时由内存池调用mp_init进行一次初始化,内存块释放到内存池时不执行非易变成员的销毁,非易变成员只是在其释放到一级配置器时执行销毁或者在主、副内存池之间借用内存块时调用mp_fini对非易变成员销毁。在内存块释放到内存池,通过调用内存块析构函数mp_dtor,将内存块中非易变成员保持为针对特定目而初始化的状态,插入到内存池中缓存链表中,后续的内存块分配不需要执行初始化函数。这样在每次使用内存块时,这些非易变成员就不需要销毁和重新创建,从而大大提高了内存池的分配和释放内存块的效率。

权利要求 :

1.嵌入式系统高效内存池的实现方法,该内存池中的每个内存块的生命周期包括分配、使用和释放三个阶段,其特征在于:内存块的分配阶段包括以下步骤:

步骤101、判断内存池中是否存在已经初始化了的缓存内存块,如果存在则执行步骤

102,否则执行步骤103;

步骤102、从上述缓存内存块中分配内存块供非易变成员使用,然后转至步骤105;

步骤103、从内存池未初始化内存块链表中分配一个未初始化的内存块;

步骤104、初始化该内存块供非易变成员使用;

步骤105、构造内存块,并初始化供易变成员使用;

内存块的释放阶段包括以下步骤:

步骤107、使非易变成员占用的内存块的回归到初始化状态;

步骤108、将内存块释放到内存池中。

2.如权利要求1所述的嵌入式系统高效内存池的实现方法,其特征在于内存池的数据结构中保存了内存池创建函数的四个回调函数指针,非易变成员初始化函数mp_init、非易变成员终结函数mp_fini、构造函数mp_ctor和析构函数mp_dtor指针,非易变成员的初始化通过调用mp_init实现,其销毁通过调用mp_fini实现;易变成员初始化通过调用mp_ctor实现,其销毁通过调用mp_dtor实现。

3.如权利要求1所述的嵌入式系统高效内存池的实现方法,其特征在于内存池的数据结构中具有锁和指向该锁的锁指针,在分配和释放内存块前若锁指针为非空,对内存池进行加锁;在完成分配和释放内存块后若锁指针为非空,对内存池进行解锁。

4.如权利要求1所述的嵌入式系统高效内存池的实现方法,其特征在于创建内存池的函数具有max_items和prealloc_items两个参数,其中参数max_items表示最大可分配块数,参数prealloc_items表示预分配块数,在创建内存池时从系统中预分配prealloc_items个内存块,该内存块不能回收;max_items减去prealloc_items的剩余数目为动态分配的数目,该内存块可以回收。

5.如权利要求1所述的嵌入式系统高效内存池的实现方法,其特征在于内存池的数据结构中具有引用计数数组,用于在创建内存池时分配预分配的内存块,数组大小是预分配内存块的个数,用于存放每一个预分配内存块的引用计数;动态分配的内存块,在分配内存时,末尾额外多分配四个字节的内存,用于引用计数;在查找一个内存块的引用计数时,如果其地址落在预分配的内存地址范围内,则根据其相对于预分配内存地址偏移得到引用计数数组的索引号,从而得到该内存块的引用计数;如果内存块是动态分配的,其引用计数就是该内存块末尾多分配的四个字节。

6.如权利要求1至5项任一项权利要求所述的嵌入式系统高效内存池的实现方法,其特征在于内存池包括主、副内存池,二者互为参考内存池,主、副内存池具有相同的数据结构且通过参考内存池指针相互指引。

7.如权利要求6所述的嵌入式系统高效内存池的实现方法,其特征在于内存块的分配次序为,首先从内存池的缓存内存块分配,其次从未初始化内存块链表中分配,接下来从一级内存配置器中分配,最后从参考内存池的缓存内存块中分配。

说明书 :

嵌入式系统高效内存池的实现方法

技术领域

[0001] 本发明涉及嵌入式系统的内存管理机制,具体涉及嵌入式系统高效内存池的实现方法。

背景技术

[0002] 嵌入式系统的内存管理机制必须满足实时、高效、高可靠性的要求。具体体现在:
[0003] (1)快速,嵌入式系统由于要保证实时性,因此要求内存分配过程尽可能地快。
[0004] (2)高效,内存分配要尽可能减少占用开销。
[0005] (3)可靠,内存分配必须得到满足,如果分配失败可能会带来灾难性的后果。
[0006] 目前,嵌入式系统常用的内存管理方式有以下3种:
[0007] (1)固定式分配,也叫静态分配或预分配。适用于那些不能够承担内存耗尽风险,或者实时性要求特别高的应用程序。在程序初始化的时候,预先分配固定数量的内存,用于存储所需要的对象和数据结构,比如使用数组。
[0008] (2)动态分配。根据需要从内存堆(heap)(在C/C++中,堆是指一般由程序员分配和释放,若程序员不释放,程序结束时可能由OS回收。其分配和释放函数一般是new、malloc、delete、free等)中分配内存。内存堆动态分配内存的一些分配算法会花费不可预期的时间。申请的区块越小,额外负担所占用的比例就越大。在这种方式下,还必须准备好处理内存耗尽的情况,例如某个系统X有两个模块协作处理一个业务,处理业务时各个模块都需要为每条业务分配一个不同的对象,如果每个模块都采用动态分配内存方案,在业务高峰时,可能第一个处理模块就将系统内存分配光了,因此,第二个模块处理时无法分配内存,导致业务流程处理中断,从而造成所有业务都没有得到处理。鉴于以上各种原因,嵌入式系统很少直接采用动态分配技术。
[0009] (3)内存池式分配。内存池一般用来分配某种固定尺寸的反复多次分配和释放的特定内存块。嵌入式系统内存分配的常用手段原则上采取二级配置:第1级配置直接采用操作系统提供的手段(比如通过语句malloc()和free())分配好所需要的内存;第2级配置采用内存池管理方式,内存池维护着空闲内存链表。预先分配一个内存池,循环使用其中未被使用的内存块,此时应注意预先分配足够的内存,以便能够在程序启动时容纳大量的数据结构,通过建立自由链表,可以避免内存块头部额外字节的消耗,从而可以改变动态内存分配的内存管理器中额外开销。这种二级分配方式还尽量减少了内存碎片,使得能够在一定的内存中存储更多的数据结构,减少系统的整体内存需求,降低系统成本。在前面例子系统X中,如果所有模块都采用内存池式分配方案,在初始化时就为每个模块预分配了一定数量A的对象;在业务高峰时,系统中已经没有内存可以分配,第一个处理模块只会处理A条业务,超过A的业务遭到丢弃;第二个模块也可以从内存池中分配A个对象,也可以处理A条业务。所以采用内存池实现方案,至少可以保证一定业务。
[0010] 目前在内存池或者缓冲区实现的相关专利文献中或者侧重于提高内存管理模块的效率,或者侧重于最大化内存的利用率(最小化管理开销占用的内存),或者侧重于减少内存碎片,或者侧重于实现内存池的动态扩展和回收,或者侧重于增加内存的可调试性,提供检测内存越界、内存泄漏的功能。以上这些方法侧重于优化内存池每次申请和释放内存块时对内存块的快速搜索定位方法,从而提高内存块分配和释放的速度。但很少有专利结合内存块的初始化与销毁以及提供避免数据无用拷贝机制来提高内存块分配速度。
[0011] 内存池作为内存块的一个容器,存放空闲的内存块和内存池管理需要的数据。内存池提供给使用者使用的通常是内存块。一般内存池使用方法如下,首先创建内存池,从内存池中分配内存块,使用内存块,释放内存块回内存池,在内存池不再使用时,销毁内存池。一个内存块的生命周期包括分配、使用和释放。在程序运行期间,一个内存池的创建一般只执行一次,通常在程序初始化时执行,其销毁一般也只执行一次,或者根本不执行(在程序终结或者系统终结时,让系统自动销毁分配的资源)。然而在程序运行期间,一个内存块的生命周期要重复无数次。所以为了提高效率,总是着眼于提高内存块的每次分配和释放速度。
[0012] 上面讲到的内存池中内存块的生命周期分为分配、使用和释放三个阶段,又可以细分为以下五个阶段,其包括:
[0013] 1)从内存池中分配内存块;
[0014] 2)初始化和构造内存块;
[0015] 3)使用内存块;
[0016] 4)析构和销毁内存块;
[0017] 5)将内存块释放到内存池中。
[0018] 内存块通常被使用者作为数据结构指针使用,其被分配后一般都立即强制转换成该数据结构指针。许多频繁复杂的数据结构包含一个或者多个成员变量。其中一些变量的初始化和销毁会消耗大量资源,像锁、信号量以及其它可以当作初始化状态的变量(像嵌套的内存指针(随后立即需要分配内存)等),我们将这些成员变量叫做非易变成员变量;除此以外的简单成员变量叫做易变成员变量。例如在下面的数据结构中,stream是一个流的控制结构,其中data_ptr是指向数据缓冲区的指针;startp和endp是数据相对于data_ptr的偏移量;tpye是流的类型;buf_size是数据缓冲区的大小;ext_buf是额外分配的缓冲区;databuf_embed是长度为256字节的数据缓冲区,内嵌在流的控制结构中。databuf_embed中可以存放小于等于256字节的数据,data_ptr指向databuf_embed,buf_size是256字节;如果流的数据大于256字节或者数据缓冲区需要独立于流控制结构单独在不同模块传递,就无法在databuf_embed中存放,就需要额外分配一块内存作为数据缓冲区,ext_buf、data_ptr指向该数据缓冲区,buf_size就是额外分配的大小;流控制结构的type也就有两种,一种是数据缓冲区内嵌,一种是数据缓冲区额外分配。其中,变量type、startp、endp、buf_size、data_ptr、databuf_embed就是数据结构的易变成员变量;
锁lock、信号量sem、ext_buf是数据结构的非易变成员变量。
[0019] struct stream
[0020] {
[0021] MUTEX*lock;
[0022] SEMPHORE*sem;
[0023] Int type; /*type of stream*/
[0024] Int startp; /*first data position*/
[0025] Int endp; /*last valid data position*/[0026] Int buf size; /*size of data buf*/
[0027] unsigned char*data_ptr;/*pointer of data buf*/
[0028] unsigned char*ext_buf ;/*pointer of extend buf*/
[0029] char databuf_embed[64];
[0030] };
[0031] 在程序运行期间,内存块会无数次被分配和释放,无数次重复上述生命周期的5个阶段。如果每次分配后都要对数据结构中的非易变成员变量进行初始化,在每次释放前又将这些数据结构的非易变成员变量销毁,则某些复杂数据结构中非易变成员变量的初始化和销毁所需的时间大大超过了从内存池中对其进行分配和释放回内存池所需的时间,甚至具有数量级的差异,导致系统效率低下甚至不可用。
[0032] 内存池中内存块的分配和释放通常是基于链表的操作或者其它快速定位算法,效率非常高,分配和释放时用于互斥的PV操作成了一种重要开销。但在某些情况下,不需要互斥(像单任务环境下,或者在某种已经存在互斥的环境下),所以内存池实现需要提供一种可选项,内存池使用者可以自行决定分配和释放是否使用互斥。
[0033] 在某些应用中,为了提高性能,需尽量避免数据拷贝,因此,多个使用者只读使用一个内存块数据时,一般共享该内存块数据,只有在写时才分配新的内存块将该内存块的数据拷贝到新的内存块里。这样节省了内存占用,减少了数据拷贝。为了达到此目的,广泛使用引用计数。引用计数用来识别内存块是否正在使用,一般来说,每个内存块对应一个引用计数,内存块的引用计数初始化为0,当使用者使用一个内存块时,引用计数加1,使用完后首先引用计数减1,然后检查应用计数,当引用计数为0,将内存块释放到内存池中;当引用计数不为0,表示有其他使用者正在使用该内存块,不释放内存块到内存池。
[0034] 在前面的流控制结构的例子中,其数据缓冲区既可以嵌入在控制结构中,也可以和控制结构分离。因此流控制结构的类型有两种,一种是数据缓冲区内嵌,我们将这种流控制结构称作STREAM_DATA_EMBED;一种是数据缓冲区是额外分配的内存,我们将这种流控制结构称作STREAM_DATA_EXTEND。两种流控制结构都使用前面相同的数据结构stream,但其存放数据的数据缓冲区位置不同,使用的目的也不同;数据缓冲区的分配、释放也不同。针对这些不同,我们可以简单地使用两个不同的内存池来缓存这两种不同类型的流控制结构,对数据缓冲区的分配、释放也作不同的处理。但这两种类型的流控制结构都使用相同的数据结构,并且其包含的变量除了ext_buf外,其他都是类似的。并且在使用者使用过程中,要求两者可以互相转化。如果将其它模块的数据缓冲区直接附加到一个STREAM_DATA_EMBED中,其就变成了一个STREAM_DATA_EXTEND流控制结构,在使用完后,就可以将其释放到缓存STREAM_DATA_EXTEND的内存池中;如果要将STREAM_DATA_EXTEND的中的数据缓冲区剥离后单独传递给其他模块,其就变成了STREAM_DATA_EMBED的流控制结构,在使用完后,就可以将其释放到缓存STREAM_DATA_EMBED的内存池中。数据缓冲区独立于控制结构可以避免数据的拷贝。简单的使用两个内存池割裂了两者的共性并且无法解决其互相转换的问题。
[0035] 内存池分配也有从系统中预分配内存和从系统中动态分配内存两种方案。从系统中预分配内存,就是创建内存池时从系统中预先分配一大块内存,并分割成块,然后添加到内存池的未初始化链表中,这样以后每次分配内存块时,不用从系统中分配内存,内存块的分配时间存在确定性,不会出现在系统中分配不到内存的情况,内存块的分配有保证;但其缺点是,难以扩展和回收。从系统中动态分配内存就是创建内存池时,没有从系统中预分配内存,每次分配内存块时,如果内存池中没有可用的内存块,就从系统中分配一个内存块;这样的内存池可以扩展和回收;缺点是内存块分配会花费不可预期的时间,无法保证实时性,此外还必须面对无法从系统分配内存的情况。
[0036] 综上所述,现有的嵌入式系统内存池管理没有考虑在内存块的初始化和释放过程中,对非易变成员变量的初始化和销毁所占用资源大大超过简单分配和释放所占用的资源,并且没有提供一些机制来避免数据无效拷贝,无法很好地满足系统实时、高效和高可靠性的要求。

发明内容

[0037] 本发明所要解决的技术问题是解决嵌入式系统内存池管理无法很好地满足系统实时、高效和高可靠性要求的问题。
[0038] 为了解决上述技术问题,本发明所采用的技术方案是提供一种嵌入式系统高效内存池的实现方法,该内存池中的每个内存块的生命周期包括分配、使用和释放三个阶段,内存块的分配阶段包括以下步骤:
[0039] 步骤101、判断内存池中是否存在缓存内存块,如果存在则执行步骤102,否则执行步骤103;
[0040] 步骤102、从上述缓存内存块中分配内存块,然后转至步骤105;
[0041] 步骤103、从内存池未初始化内存块链表中分配内存块;
[0042] 步骤104、初始化内存块非易变成员部分;
[0043] 步骤105、构造内存块,初始化内存块中易变成员部分;
[0044] 内存块的释放阶段包括以下步骤:
[0045] 步骤107、使内存块中的非易变成员回归到初始化状态;
[0046] 步骤108、将内存块释放到内存池中。
[0047] 上述方案中,内存池的数据结构中保存了内存池创建函数的四个回调函数指针,非易变成员初始化函数mp_init、非易变成员终结函数mp_fini、构造函数mp_ctor和析构函数mp_dtor指针,非易变成员的初始化通过调用mp_init实现,其销毁通过调用mp_fini实现;易变成员初始化通过调用mp_ctor实现,其销毁通过调用mp_dtor实现。这四个函数指针组合在一起决定了一个数据结构哪些成员是非易变成员,哪些是易变成员,以及如何对这些成员如何初始化和销毁。这四个回调函数指针是使用者创建内存池时传递给内存池实现模块的,其由使用者实现,所以是使用者决定了数据结构哪些成员是非易变成员,哪些是易变成员,以及如何对这些成员如何初始化和销毁。从而达到对易变成员和非易变成员的初始化和销毁分开处理。
[0048] 本发明实现了一种高效通用内存池技术,与系统提供的内存分配和释放调用比较,性能有极大的提高,特别是在复杂内存结构的分配和释放上,性能具有数量级的提高。在许多嵌入式软件项目中,存在很多低效的内存分配代码,如果采用本专利实现的内存池技术,将极大提高代码性能。同时本专利实现了互斥保护选项、内存块的引用计数、预分配与动态分配可选及可结合机制、主辅内存池机制、内存池使用统计和错误检测、空闲内存块回收高级特性,这些高级特性在大型复杂系统中具有广泛的用途。

附图说明

[0049] 图1为本发明提供的的高效内存池实现方法中的内存块生命周期示意图;
[0050] 图2为本发明提供的高效内存池数据结构图;
[0051] 图3为本发明提供的高效内存池实现方法创建函数流程图;
[0052] 图4为本发明提供的高效内存池实现方法内存块分配函数流程图;
[0053] 图5为本发明提供的高效内存池实现方法内存块释放函数流程图;
[0054] 图6为本发明提供的高效内存池实现方法内存池销毁函数流程图;
[0055] 图7为本发明提供的高效内存池实现方法副内存池创建函数流程图;
[0056] 图8为本发明提供的高效内存池实现方法从参考内存池中借用内存块函数流程图;
[0057] 图9为本发明提供的高效内存池实现方法内存块引用计数查找函数流程图。

具体实施方式

[0058] 本发明提供了一种嵌入式系统高效内存池的实现方法,利用该方法,可以大大减少内存池中内存块的初始化和销毁所需的时间,并且通过引用计数、副内存池机制避免数据的无益拷贝,提高了嵌入式系统的内存使用效率,从而最大限度地满足了嵌入式系统实时性、高效性和高可靠性的要求。
[0059] 本发明的思路是针对非易变成员和易变成员采用不同的处理方法,图1为该方法中的内存块生命周期示意图,结合图1所示,该方法包括以下步骤:
[0060] 步骤101、通过Cached_nums是否为0判断内存池中是否存在已经初始化了的缓存内存块(特指非易变成员已经初始化的内存块),如果存在则执行步骤102,否则执行步骤103;
[0061] 步骤102、从上述缓存内存块中分配内存块供非易变成员使用,然后转至步骤105;
[0062] 步骤103、从内存池未初始化内存块链表中分配一个未初始化的内存块(特指非易变成员未初始化的内存块);
[0063] 步骤104、调用mp_init初始化内存块供非易变成员使用;
[0064] 步骤105、调用mp_ctor构造内存块,并初始化;
[0065] 步骤106、使用内存块;
[0066] 步骤107、调用mp_dtor使非易变成员占用的内存块的回归到初始化状态;
[0067] 步骤108、将内存块释放到内存池中。
[0068] 下面对上述方法作出进一步的详细说明,上述方法包括内存池的创建、从内存池中分配内存块、使用内存块、释放内存块和当内存池不再使用时销毁内存池五个步骤。
[0069] 接下来以一个内存池的具体实例加以具体说明。
[0070] 内存池mem_pool_t20的数据结构结构如图2所示,其中:
[0071] 200-内存池名称name;
[0072] 201-后继内存池指针Next_pool,该指针指向下一个内存池;
[0073] 202-参考内存池指针Ref_pool,用于实现副内存池机制;
[0074] 203-统计信息成员,用于存放本内存池使用情况的统计数据;
[0075] 204-回调函数指针,包括mp_init初始化函数指针2041、mp_fini终结函数指针2042、mp_ctor构造函数指针2043和mp_dtor析构函数2044四个函数指针,其中,mp_init用于初始化内存块中的非易变成员,一般只调用一次;mp_fini用于销毁内存块中的非易变成员,一般只调用一次,并且是在将内存块释放到一级内存配置器前调用;mp_ctor用于初始化内存块中的易变成员,每次分配内存块时都要调用;mp_dtor用于销毁内存块中的易变成员,每次释放内存块时都要调用,析构函数mp_dtor的另外一个作用是将内存块中的非易变成员还原为初始化状态,例如非易变成员中的计数信号量,就需要在mp_dtor里对此计数信号量清零。
[0076] 205-预分配信息,包括预分配内存块的起始地址2051,用于记录从一级内存配置器中分配的内存块的起始地址;预分配内存块的基线地址2052,用于记录2051对齐后的地址;预分配内存块的数量2053,用于确定预分配内存的大小。上述预分配信息用于查找引用计数、判定内存块是否能够回收以及在内存块销毁时判定一个内存块是预分配的还是动态分配的;
[0077] 206-引用计数数组指针,其指向附加部分中计数数组的地址。如果创建函数参数的引用计数标志位为置位,则在附加部分为引用计数数组保留内存,并且206指向附加部分中引用计数数组地址;如果未置位,引用计数数组指针为空;
[0078] 207-缓存的内存块个数Cached_nums;
[0079] 208-缓存内存块数组指针Cached_blocks_ptr,用于指向附加部分中缓存内存块数组;
[0080] 209-未初始化内存块链表指针uninitiated_list_ptr,其指向附加部分中未初始化内存块链表;
[0081] 20a-锁指针,其指向附加部分中锁的地址。如果创建函数参数的锁标志位置位,则在附加部分为锁保留内存,并且20a指向附加部分中锁地址;如果未置位,20a为空。对内存池的操作需要判断锁是否非空决定是否加锁和解锁;
[0082] 20b-附加部分,即图3中背景为斜线的部分,或者因为内存池数据结构中有的成员大小无法确定(像20b3、20b4),或者是因为的成员是可选的(像20b1、20b4),或者是因为主、副内存池要共享(像20b1、20b2、20b4),所以这些成员在附加部分保留内存,并且使用指针指向这些成员;
[0083] 20b1-锁;
[0084] 20b2-未初始化内存块链表,存放从一级内存配置器中已经分配但还没有初始化非易变成员的内存块;
[0085] 20b3-缓存内存块数组,存放缓存内存块(特指易变成员已经初始化的内存块),大小等于最大内存块的个数,数组的每一个指针指向一个已经初始化了的内存块地址。由于缓存内存块已经初始化,所以该内存块不能够是链表结构,因为其没有办法存放一个内存块结点的直接后继或者前驱地址的指针域,所以缓存内存块只能使用数组存放。在使用者分配内存块时优先从此数组的末尾分配,在使用者释放内存块时,将内存块还原到初始化状态,然后插入到此数组的末尾,这样才保证了内存块只初始化一次;
[0086] 20b4-引用计数数组,数组里的每一个元素是其对应的预分配内存块的引用计数,大小等于预分配内存块的个数;
[0087] 在图2中,一个内存块在其生命周期中各个阶段存放的位置不同:使用者创建内存池时,从一级内存配置器中预分配的一大块内存23,在对齐后分割而成一块块原始内存块2301,然后将内存块的起始地址作为未初始化内存块链表的后继指针uninitiated_list_ptr,于是原始内存块2301就变成了一个个链表元素24插入未初始化内存块链表uninitiated_list 20b2中;未初始化内存块24从20b2中移出后,在对非易变成员初始化后但没有被使用者分配时,其变为非易变成员已初始化内存块25缓存在Cached_blocks20b3中,因为非易变成员已初始化内存块25的非易变成员已经初始化,但是内存池实现模块不知道非易变成员在内存块中的确切位置,所以没有存放后继指针的地方,因此通过数组存放;内存块从内存池中分配后,从20b3中移出,内存池不再对其管理,由使用者自己管理该内存块;使用者使用完毕后,内存块被释放后缓存在2b03中。
[0088] 在图2中,20是一个主内存池,21是后继内存池,22是一个依赖于20的副内存池。内存池20、21、22通过next_pool后继内存池指针串成一个链表,组成这个链表是为了系统管理的需要,例如查看各个内存池的统计信息等。主内存池20中的参考内存池指针指向副内存池22,副内存池22中的参考内存池指针指向主内存池20。主、副内存池的数据结构完全一样,但附加部分不同;主内存池的附加部分包括锁、未初始化内存块链表uninitiated_list、缓存的内存块数组Cached_blocks和引用计数数组;副内存池的附加部分仅仅包括Cached_blocks数组。副内存池依赖于主内存池,共享主内存池某些资源,即预分配内存块、引用计数数组、未初始化链表、锁。其预分配信息225和主内存池的205的各个字段都相同,都存放预分配地址块23相关信息;副内存池的引用计数数组指针226指向主内存池的20b4;副内存池的uninitiated_list_ptr229指向主内存池的20b2;副内存池的锁指针22a指向主内存池的20b1。除了前述共享资源外,副内存池其余的资源是其私有的,像统计信息,回调函数指针,缓存内存块数组。
[0089] 创建一个如图2所示的内存池(此处特指主内存池)的创建函数流程如图3所示,该创建函数的参数有内存池名字、内存块尺寸、对其方式、预分配块数、最大可分配块数、四个回调函数指针、引用计数标志和锁标志,创建步骤如下:
[0090] 步骤301、根据参数中的锁标志,决定是否在附加部分中为锁保留内存;根据引用计数标志,决定是否在附加部分为引用数组保留内存,引用数组的大小是预分配块数;缓存内存块数组的大小是参数中的最大可分配块数;基于上述锁20b1、未初始化链表20b2、缓存内存块数组20b3和引用计数数组20b4需分配的内存计算出附加部分的大小;
[0091] 步骤302、从系统一级内存配置器中分配内存用于创建内存池,分配内存的大小等于主体部分+附加部分;
[0092] 步骤303、根据创建函数中的参数初始化该内存池mem_pool_t,即将步骤302中分配的内存块全部清零并初始化内存池的相关数据结构,在内存池初始化过程中,根据锁标志参数,确定锁20b1在附加部分中的地址,并对锁进行初始化,将锁指针20a指向20b1;根据引用计数标志参数,确定引用计数数组20b4在附加部分中的地址,将207指向20b4;将208指向20b3;将209指向20b2;
[0093] 步骤304、根据参数内存块尺寸、对齐尺寸、预分配块数计算预分配内存的大小。此步骤中,首先根据对齐尺寸对内存块尺寸参数调整,预分配内存的大小=调整后的内存块尺寸×预分配块数+对齐尺寸;
[0094] 步骤305、从系统中预分配大块内存,并填充内存池预分配的相关信息,具体为,从一级内存配置器中一次性分配步骤304计算出的预分配内存的大小的内存单元,并填写预分配信息,其中2051指向一级内存配置器返回的内存地址,2052指向预分配内存地址2051对齐后的地址,2053为预分配块数。
[0095] 步骤306、将预分配内存分割成一个个内存块,并将这些内存块的起始地址初始化为链表结构插入到uninitiated_list20b2中;
[0096] 步骤307、按上述步骤创建多个内存池并将新建的内存池插入到上一个内存池的内存池链表末尾,所有内存池串成一个链表。
[0097] 步骤308、返回内存池给用户。
[0098] 上述步骤中,创建内存池的函数具有max_items和prealloc_items两个参数,其中参数max_items表示最大可分配块数,参数prealloc_items表示预分配块数,在创建内存池时从系统中预分配prealloc_items个内存块,其不能回收,max_items减去prealloc_items的剩余数目为动态分配的数目,不是创建内存池时从系统中预分配的,是在内存块分配函数时从系统中分配一个内存块大小的内存,实现了根据需要动态扩展,回收;如果prealloc_items等于max_items,则内存池中的所有内存块都是预先分配的,不可以回收;如果prealloc_items等于零,则内存池中的所有内存块都动态分配的,都是可以回收的。
[0099] 图4为上述内存池在使用时内存块的分配流程图,如图4所示,该分配过程包括以下步骤:
[0100] 步骤401、将内存池分配函数的返回值设置为空;
[0101] 步骤402、根据内存池锁指针是否为空判断是否对内存池进行加锁,如果锁指针为非空则执行步骤403对该内存池进行加锁;
[0102] 步骤404、判断内存池中的Cached_blocks是否为空,若Cached_blocks为空则执行步骤405;否则执行步骤412;
[0103] 步骤405、判断内存池中的Uninitiated_list列表是否为空,若Uninitiated_list为空执行步骤406;否则执行步骤409;
[0104] 步骤406、判断从一级内存配置器里已分配的内存块数是否小于max_items,若小于则执行步骤407;否则,执行步骤414;
[0105] 步骤407、从一级内存配置器里分配一个内存块;
[0106] 步骤408、将分配函数的返回值指向407分配的内存块,执行步骤411;
[0107] 步骤409、从uninitiated_list中移走一个内存块;
[0108] 步骤410、将分配函数的返回值指向被移走的内存块供用户使用;
[0109] 步骤411、使用mp_init对返回值指向的内存块的非易变成员进行初始化,执行步骤414;
[0110] 步骤412、从Cached_blocks移走一个内存块;
[0111] 步骤413、将返回值指向被移走的内存块;
[0112] 步骤414、判断返回值是否为空,不为空则执行步骤419;
[0113] 步骤415、判断参考内存池是否非空,不为空则执行步骤419;
[0114] 步骤416、从参考内存池的缓存内存块中移走一个内存块,将返回值指向被移走的内存块;
[0115] 步骤417、判断返回值是否为空,不为空则执行步骤419;
[0116] 步骤418、在统计信息中增加统计和错误信息;
[0117] 步骤419、判断返回值是否非空,该步骤为一个冗余检查;
[0118] 步骤420、判断构造函数是否非空,不为空则执行步骤422;
[0119] 步骤421、执行构造函数mp_ctor,对易变成员进行初始化;
[0120] 步骤422、在统计信息中增加统计和错误信息;
[0121] 步骤423、判断锁指针是否为空,如果为非空则执行步骤424对内存池进行解锁;
[0122] 步骤424、返回内存块地址给用户。
[0123] 上述步骤中,步骤402、403根据锁指针是否为空决定是否加锁;步骤423、424根据锁指针是否为空决定是否解锁,这是实现互斥操作可选机制的重要步骤。步骤404、405、406说明了分配内存块的优先顺序,首先是从Cached_blocks中分配,其次从uninitiated_list中分配,接下来从一级内存配置器中分配,最后从副内存池的缓存内存块中分配。从步骤404到步骤412、413这条分配路径避免了内存块中非易变成员的初始化,是内存块分配的一条最快速最优的路径,也是分配时调用最多的路径,从而达到了对内存块分配的优化。
步骤404到步骤405、411这条路径比较复杂的,对内存块的非易变成员进行了初始化,花费也是很大的,但在内存块分配时这是调用最少的路径,一个内存块在生命周期中一般只走一次这条路径。步骤406、407、408是实现动态内存池机制重要步骤。步骤415、416是实现副内存池机制的重要步骤。
[0124] 图5为内存池中内存块的释放流程图,如图5所示,内存块的释放包括以下步骤:
[0125] 步骤501、判断锁指针是否为空,如果为空则执行步骤503;
[0126] 步骤502、对该内存池进行加锁;
[0127] 步骤503、判断析构函数mp_dtor是否为非空,如为空则执行步骤505;
[0128] 步骤504、调用析构函数mp_dtor,将内存块中的非易变成员还原到初始化状态;
[0129] 步骤505、将已经销毁的内存块插入到cached_blocks的末尾;
[0130] 步骤506、在统计信息中增加统计信息;
[0131] 步骤507、判断锁指针是否为空,如果为非空则执行步骤508对内存池进行解锁;
[0132] 至此,内存块释放结束。
[0133] 上述步骤中,步骤501、502根据锁指针是否为空决定是否加锁,步骤507、508根据锁指针是否为空决定是否解锁,这是实现互斥操作可选机制重要步骤。步骤503中如果内存池的析构函数非空,则执行504内存池的析构函数,对内存块中的易变成员析构,同时将内存块中非易变成员保持为针对特定目标而初始化的状态,避免了将内存块中非易变成员销毁。
[0134] 图6为内存池销毁流程图,如图6所示,内存池销毁包括以下步骤:
[0135] 步骤601、判断锁指针是否为空,如果为空则执行步骤603;
[0136] 步骤602、执行加锁操作;
[0137] 步骤603、判断mp_fini是否非空,不为空则执行步骤608;
[0138] 步骤604、判断cached_blocks列表是否非空,不为空则执行步骤608;
[0139] 步骤605、对其中的每一个内存块执行mp_fini销毁非易变成员;
[0140] 步骤606、将其中的每一个内存块从cached_blocks中移走;
[0141] 步骤607、将其中的每一个内存块加入到uninitiated_list中;
[0142] 步骤608、判断参考内存池是否为空,不为空则执行步骤621;
[0143] 步骤609、判断uninitiated_list是否非空,不为空则执行步骤612;
[0144] 步骤610、判断该内存块是否为非预分配的,不是则执行步骤612;
[0145] 步骤611、将该内存块从uninitiated_list中移走,并将其释放回一级内存配置器中,返回步骤609;
[0146] 步骤612、判断预分配的内存块是否为非空,不为空则执行步骤614;
[0147] 步骤613,将预分配内存释放回一级内存配置器中;
[0148] 步骤614、判断锁指针是否为空,如果为空则执行步骤616;
[0149] 步骤615、执行解锁操作;
[0150] 步骤616、关中断;
[0151] 步骤617、判断是否锁指针非空,否则执行步骤619;
[0152] 步骤618、对锁进行销毁;
[0153] 步骤619、从内存池链表中移出该内存池;
[0154] 步骤620、将内存池释放回一级内存配置器中;
[0155] 步骤621、开中断,返回;
[0156] 步骤621、将参考内存池的参考内存池指针置为空;
[0157] 步骤622、关中断;
[0158] 步骤623、从内存池链表中移出该内存池;
[0159] 步骤624、将内存池释放回一级内存配置器中;
[0160] 步骤625、开中断,返回。
[0161] 至此,内存池销毁。
[0162] 上述步骤中,步骤601、602和步骤614、615根据锁指针是否为空决定是否对内存池加锁和解锁,所以说对内存池的某些部分的销毁是在锁的保护下执行的。步骤603中,如果mp_fini非空,则执行604、605、606、607,对每一内存块执行mp_fini,从缓存集合中移出,插入到uninitiated_list中。步骤608根据副内存池是否非空来决定是否销毁主、副内存池中共享的某些成员。
[0163] 图7为副内存池创建函数流程图,其参数有内存池名字、四个回调函数指针、指向主内存池的指针。如图7所示,副内存池的创建包括以下步骤:
[0164] 步骤701、计算副内存池附加部分的大小,副内存池的附加部分中只有cached_blockes数组;
[0165] 步骤702、从一级内存配置器中为副内存池分配一块内存,用于创建副内存池;
[0166] 步骤703、对副内存池不需要互斥的部分进行初始化,包括统计信息、四个回调函数指针;
[0167] 步骤704、判断主内存池中的锁指针是否为空,如果为空则执行步骤706;否则执行步骤705对主内存池进行加锁;副内存池与主内存池共享一些资源,锁就是共享资源,主内存池使用了锁,副内存池就必须使用,应用计数也是如此,所以创建函数里没有创建主内存池的函数所具有的锁和应用计数标志参数。
[0168] 步骤706、将主、副内存池的参考内存池指针互相指向;
[0169] 步骤707、将副内存池的一些字段指向主内存池的相应共享字段,如Cached_blocks Cached_nums、预分配信息、锁、引用计数数组、未初始化链表;
[0170] 步骤708、判断主内存池中的锁指针是否为空,如果为非空则执行步骤709对主内存池进行解锁;否则执行步骤710;
[0171] 步骤710、返回内存池给使用者。
[0172] 主内存池20中的参考内存池指针指向副内存池22,副内存池22中的参考内存池指针指向主内存池20,因此二者互为参考内存池。
[0173] 图8为从参考内存池中借用内存块流程图,如图8所示,包括以下步骤:
[0174] 步骤801、判断从参考内存池中借用内存块的参数是否有效,当参数无效时退出;有效时执行步骤802;
[0175] 步骤802、从参考内存池的cached_blocks中移出一个内存块;
[0176] 步骤803、判断参考内存池的mp_fini是否为非空,如果参考内存池的mp_fini非空,则执行步骤804对该内存块执行mp_fini,销毁内存块中的非易变成员变量,否则,执行步骤805;
[0177] 步骤805、判断该内存池的mp_init是否为非空,如果mp_init为非空,则执行步骤806对该内存块执行mp_init,初始化内存块非易变成员变量,执行步骤807;
[0178] 步骤807、返回该内存块供用户使用。
[0179] 例如,副内存池共享主内存池的内存块资源,副内存池创建时未预分配内存块。当使用者从副内存池中第一次分配内存块时,(1)首先其缓存内存块为零,不能分配;(2)从其未初始化链表中分配,但该链表指针是指向主内存池未初始化链表;(3)如果主内存池未初始化链表也为空无法分配,再判断是否可以从一级内存配置器中(已分配块数是否达到max_items)分配,如果可以就从一级内存配置器中分配;(4)还是没有分配到内存块,就借用主内存池中缓存的内存块(因为缓存的是非易变成员已经初始化了的对象,现在借用就要销毁非易变成员,所以放在最后)。当使用者使用完了后,就将其释放到副内存池的缓存数组里,使用者再从副内存池分配时,就可以从其缓存的数组中分配。副内存池可以借用主内存池的缓存内存块,反之主内存池也可以借用副内存池的缓存内存块。
[0180] 图9为本发明实现内存块引用计数查找的流程图,如图9所示,包括以下步骤:
[0181] 步骤901、将引用计数地址的返回值设为空;
[0182] 步骤902、判断内存池中的锁指针是否为空,如果为非空则执行步骤903进行加锁;否则执行步骤904;
[0183] 步骤904、判断该内存块是否为预分配的,如果是预分配的,则执行步骤905;否则执行步骤907;
[0184] 步骤905、根据内存块相对于预分配内存块基线地址2052的偏移以及内存块的大小,计算出引用计数数组的索引号;
[0185] 步骤906、返回该索引号对应的引用计数数组的地址,执行步骤908;
[0186] 步骤907、设置内存块的引用计数地址为该内存块的末尾四字节地址;
[0187] 步骤908、判断内存池中的锁指针是否为空,如果为非空则执行步骤909进行解锁;否则执行步骤910;
[0188] 步骤910、返回引用计数的地址。
[0189] 综上所述,本发明具有以下优点:
[0190] (1)、本发明提出了非易变成员和易变成员的概念,对非易变成员的初始化和销毁进行了优化,从内存池中分配内存块时,按照本身缓存的内存、未初始化链表对应的内存,一级内存配置器中的动态内存的优先次序进行分配,如果还没有分配到内存块,就从副内存池的缓存内存块中借用一块,先对其执行终结函数mp_fini,再对其初始化非易变成员后返回给使用者。其中,步骤101→102→105是内存块分配时调用最多的路径,也是一条最优最快速的路径;步骤101→103→104→105中,步骤104对非易变成员的初始化会消耗大量资源,本专利保证了这条路径是一条分配时调用很少的路径;步骤107→108是内存块释放时调用路径,本专利对此路径进行优化没有对非易变成员进行销毁,在调用最多的路径上不会出现对非易变成员的初始化和销毁。内存块中非易变成员只在第一次分配时由内存池调用mp_init进行一次初始化,内存块释放到内存池时不执行非易变成员的销毁,非易变成员只是在其释放到一级配置器时执行销毁或者在主、副内存池借用内存块时调用mp_fini对非易变成员销毁。在内存块释放到内存池时,一般通过调用内存块析构函数mp_dtor,将内存块中非易变成员保持为针对特定目而初始化的状态,插入到内存池中缓存链表中,后续的内存块分配不需要执行初始化函数,因为从上次释放和调用析构之后,它已经处于所需的初始化状态中了。易变成员在内存块每次分配时都调用mp_ctor进行初始化,在内存块每次释放时,调用mp_dtor执行其销毁过程。在内存块的历次使用之间,保持内存块中非易变成员的初始化状态,这样在每次使用内存块时,这些非易变成员就不需要销毁和重新创建。例如,一个包含一个互斥锁的内存块仅仅需要在内存块第一次被分配时执行一次互斥锁的初始化,该内存块之后可以被释放和重分配无数次而不必每次承受互斥锁销毁和初始化的开销。从而大大提高了内存池分配和释放内存块的效率。本发明通过内存池的引用计数、副内存池机制避免了数据的无益拷贝,从而提高了系统效率。
[0191] (2)、本发明实现了副内存池机制。副内存池创建时绑定的内存池就是主内存池。主、副内存池相似,分配内存块、释放内存块、销毁内存池函数都是一样的,只是主、副内存池的创建函数不同。副内存池依赖于主内存池。主内存池必须先于副内存池创建,副内存池的销毁先于主内存池的销毁,这些顺序由使用者保证。副内存池绑定主内存池,其共享主内存池的未初始化内存块链表、锁、引用计数数组。主、副内存池互为参考内存池。从对象的角度来讲,主、副内存池缓存具有相同数据结构的两种不同对象(属性不同),从一个参考内存池分配的对象,在使用过程中可以改变属性变成另一种对象,使用完毕后被释放到另一个参考内存池中,作为另一种对象缓存;并且一个内存池中如果没有可以分配的对象了,它可以从其参考内存池借用其缓存的对象,将该借用对象改变属性后赋予新的属性,以作为一种新的对象供分配。
[0192] (3)、实现了内存块的引用计数。内存池的数据结构中具有引用计数数组,在创建时为预分配的内存块分配该数组,数组大小是预分配内存块的个数,用于存放每一个预分配内存块的引用计数;动态分配的内存块,在分配内存时,末尾额外多分配四个字节的内存,用于引用计数;在查找一个内存块的引用计数时,如果其地址落在预分配的内存地址范围内,则根据其相对于预分配内存地址偏移得到引用计数数组的索引号,从而得到该内存块的引用计数;如果内存块是动态分配的,其引用计数就是该内存块末尾多分配的四个字节。
[0193] (4)、实现了锁的可选。内存池的数据结构中具有锁和指向该锁的锁指针,在分配和释放内存块时根据锁指针是否非空对内存池进行加锁或解锁,从而实现了锁的可选。
[0194] (5)、实现了内存池从系统中预分配内存和从系统中动态分配内存两种方案。创建内存池的函数有max_items和prealloc_items两个参数,其中参数max_items表示最大可分配块数;参数prealloc_items表示预分配块数,在创建内存池时从系统中预分配prealloc_items个内存块,其不能回收;max_items减去prealloc_items的剩余数目,是动态分配的,不是创建内存池时从系统中预分配的,是在内存块分配函数时从系统中分配一个内存块大小的内存,实现了根据需要动态扩展,回收。如果prealloc_items等于max_items,则内存池中的所有内存块都是预先分配的,不可以回收;如果prealloc_items等于零,则内存池中的所有内存块都动态分配的,都是可以回收的。
[0195] 本发明不局限于上述最佳实施方式,任何人应该得知在本发明的启示下作出的结构变化,凡是与本发明具有相同或相近的技术方案,均落入本发明的保护范围之内。