静态内存池管理

静态内存池工作原理

内存池管理结构示意图

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

内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。从图中可以看到,物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列。

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

静态内存池控制块

  1. struct rt_mempool
  2. {
  3. struct rt_object parent;
  4.  
  5. void *start_address;/* 内存池数据区域开始地址 */
  6. rt_size_t size; /* 内存池数据区域大小 */
  7.  
  8. rt_size_t block_size; /* 内存块大小 */
  9. rt_uint8_t *block_list; /* 内存块列表 */
  10.  
  11. /* 内存池数据区域中能够容纳的最大内存块数 */
  12. rt_size_t block_total_count;
  13. /* 内存池中空闲的内存块数 */
  14. rt_size_t block_free_count;
  15. /* 因为内存块不可用而挂起的线程列表 */
  16. rt_list_t suspend_thread;
  17. /* 因为内存块不可用而挂起的线程数 */
  18. rt_size_t suspend_thread_count;
  19. };
  20. typedef struct rt_mempool* rt_mp_t;

每一个内存池对象由上述结构组成,其中suspend_thread形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂起在suspend_thread链表上。

静态内存池接口

创建内存池

创建内存池操作将会创建一个内存池对象并从堆上分配一个内存池。创建内存池是从对应内存池中分配和释放内存块的先决条件,创建内存池后,线程便可以从内存池中执行申请、释放等操作。创建内存池使用下面的函数接口,该函数返回一个已创建的内存池对象。

  1. rt_mp_t rt_mp_create(const char* name, rt_size_t block_count, rt_size_t block_size);

使用该函数接口可以创建一个与需求的内存块大小、数目相匹配的内存池,前提当然是在系统资源允许的情况下(最主要的是动态堆内存资源)才能创建成功。创建内存池时,需要给内存池指定一个名称。然后内核从系统中申请一个内存池对象,然后从内存堆中分配一块由块数目和块大小计算得来的内存缓冲区,接着初始化内存池对象,并将申请成功的内存缓冲区组织成可用于分配的空闲块链表。

函数参数


  1. 参数 描述

  1. name 内存池名;
  2.  
  3. block_count 内存块数量;
  4.  
  5. block_size 内存块容量。

函数返回

创建内存池对象成功,将返回内存池的句柄;否则返回RT_NULL。

删除内存池

删除内存池将删除内存池对象并释放申请的内存。使用下面的函数接口:

  1. rt_err_t rt_mp_delete(rt_mp_t mp);

删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回-RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。

函数参数


  1. 参数 描述

  1. mp rt_mp_create返回的内存池对象句柄。

函数返回

返回RT_EOK

初始化内存池

初始化内存池跟创建内存池类似,只是初始化内存池用于静态内存管理模式,内存池控制块来源于用户在系统中申请的静态对象。另外与创建内存池不同的是,此处内存池对象所使用的内存空间是由用户指定的一个缓冲区空间,用户把缓冲区的指针传递给内存池对象控制块,其余的初始化工作与创建内存池相同。函数接口如下:

  1. rt_err_t rt_mp_init(rt_mp_t mp, const char* name, void *start, rt_size_t size, rt_size_t block size);

初始化内存池时,把需要进行初始化的内存池对象传递给内核,同时需要传递的还有内存池用到的内存空间,以及内存池管理的内存块数目和块大小,并且给内存池指定一个名称。这样,内核就可以对该内存池进行初始化,将内存池用到的内存空间组织成可用于分配的空闲块链表。

函数参数


  1. 参数 描述

  1. mp 内存池对象;
  2.  
  3. name 内存池名;
  4.  
  5. start 内存池的起始位置;
  6.  
  7. size 内存池数据区域大小;
  8.  
  9. block_size 内存块容量。

函数返回

初始化成功返回RT_OK;否则返回-RT_ERROR。

脱离内存池

脱离内存池将把内存池对象从内核对象管理器中删除。脱离内存池使用下面的函数接口:

  1. rt_err_t rt_mp_detach(rt_mp_t mp);

使用该函数接口后,内核先唤醒所有等待在该内存池对象上的线程,然后将内存池对象从内核对象管理器中删除。

函数参数


  1. 参数 描述

  1. mp 内存池对象。

函数返回

返回RT_EOK。

分配内存块

从指定的内存池中分配一个内存块,使用如下接口:

  1. void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time);

如果内存池中有可用的内存块,则从内存池的空闲块链表上取下一个内存块,减少空闲块数目并返回这个内存块;如果内存池中已经没有空闲内存块,则判断超时时间设置:若超时时间设置为零,则立刻返回空内存块;若等待时间大于零,则把当前线程挂起在该内存池对象上,直到内存池中有可用的自由内存块,或等待时间到达。

函数参数


  1. 参数 描述

  1. mp 内存池对象;
  2.  
  3. time 超时时间。

函数返回

成功时返回分配的内存块地址,失败时返回RT_NULL。

释放内存块

任何内存块使用完后都必须被释放,否则会造成内存泄露,释放内存块使用如下接口:

  1. void rt_mp_free (void *block);

使用该函数接口时,首先通过需要被释放的内存块指针计算出该内存块所在的(或所属于的)内存池对象,然后增加内存池对象的可用内存块数目,并把该被释放的内存块加入空闲内存块链表上。接着判断该内存池对象上是否有挂起的线程,如果有,则唤醒挂起线程链表上的首线程。

函数参数


  1. 参数 描述

  1. block 内存块指针。

函数返回

内存池使用的例程如下所示:

  1. /*
  2. * 程序清单:内存池例程
  3. *
  4. * 这个程序会创建一个静态的内存池对象,2 个动态线程。
  5. * 两个线程会试图分别从内存池中获得内存块
  6. */
  7. #include <rtthread.h>
  8. #include "tc_comm.h"
  9. static rt_uint8_t *ptr[48];
  10. static rt_uint8_t mempool[4096];
  11. static struct rt_mempool mp; /* 静态内存池对象 */
  12. /* 指向线程控制块的指针 */
  13. static rt_thread_t tid1 = RT_NULL;
  14. static rt_thread_t tid2 = RT_NULL;
  15. /* 线程 1 入口 */
  16. static void thread1_entry(void* parameter)
  17. {
  18. int i;
  19. char *block;
  20. while(1)
  21. {
  22. for (i = 0; i < 48; i++)
  23. {
  24. /* 申请内存块 */
  25. rt_kprintf("allocate No.%d\n", i);
  26. if (ptr[i] == RT_NULL)
  27. {
  28. ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
  29. }
  30. }
  31.  
  32. /* 继续申请一个内存块,因为已经没有内存块,线程应该被挂起 */
  33. block = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
  34. rt_kprintf("allocate the block mem\n");
  35. /* 释放这个内存块 */
  36. rt_mp_free(block);
  37. block = RT_NULL;
  38. }
  39. }
  40.  
  41. /* 线程 2 入口,线程 2 的优先级比线程 1 低,应该线程 1 先获得执行。*/
  42. static void thread2_entry(void *parameter)
  43. {
  44. int i;
  45. while(1)
  46. {
  47. rt_kprintf("try to release block\n");
  48. for (i = 0 ; i < 48; i ++)
  49. {
  50. /* 释放所有分配成功的内存块 */
  51. if (ptr[i] != RT_NULL)
  52. {
  53. rt_kprintf("release block %d\n", i);
  54. rt_mp_free(ptr[i]);
  55. ptr[i] = RT_NULL;
  56. }
  57. }
  58.  
  59. /* 休眠 10 个 OS Tick */
  60. rt_thread_delay(10);
  61. }
  62. }
  63.  
  64. int mempool_simple_init()
  65. {
  66. int i;
  67. for (i = 0; i < 48; i ++) ptr[i] = RT_NULL;
  68. /* 初始化内存池对象 */
  69. rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80);
  70.  
  71. /* 创建线程 1 */
  72. tid1 = rt_thread_create("t1",
  73. thread1_entry, /* 线程入口是 thread1_entry */
  74. RT_NULL, /* 入口参数是 RT_NULL */
  75. THREAD_STACK_SIZE, THREAD_PRIORITY,
  76. THREAD_TIMESLICE);
  77. if (tid1 != RT_NULL)
  78. rt_thread_startup(tid1);
  79. else
  80. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  81.  
  82. /* 创建线程 2 */
  83. tid2 = rt_thread_create("t2",
  84. thread2_entry, /* 线程入口是 thread2_entry */
  85. RT_NULL, /* 入口参数是 RT_NULL */
  86. THREAD_STACK_SIZE, THREAD_PRIORITY + 1,
  87. THREAD_TIMESLICE);
  88. if (tid2 != RT_NULL)
  89. rt_thread_startup(tid2);
  90. else
  91. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  92. return 0;
  93. }
  94. #ifdef RT_USING_TC
  95. static void _tc_cleanup()
  96. {
  97. /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
  98. rt_enter_critical();
  99. /* 删除线程 */
  100. if (tid1 != RT_NULL && tid1->stat != RT_THREAD_CLOSE)
  101. rt_thread_delete(tid1);
  102. if (tid2 != RT_NULL && tid2->stat != RT_THREAD_CLOSE)
  103. rt_thread_delete(tid2);
  104. /* 执行内存池脱离 */
  105. rt_mp_detach(&mp);
  106. /* 调度器解锁 */
  107. rt_exit_critical();
  108. /* 设置 TestCase 状态 */
  109. tc_done(TC_STAT_PASSED);
  110. }
  111. int _tc_mempool_simple()
  112. {
  113. /* 设置 TestCase 清理回调函数 */
  114. tc_cleanup(_tc_cleanup);
  115. mempool_simple_init();
  116. /* 返回 TestCase 运行的最长时间 */
  117. return 100;
  118. }
  119. /* 输出函数命令到 finsh shell 中 */
  120. FINSH_FUNCTION_EXPORT(_tc_mempool_simple, a memory pool
  121. example);
  122. #else
  123. /* 用户应用入口 */
  124. int rt_application_init()
  125. {
  126. mempool_simple_init();
  127. return 0;
  128. }
  129. #endif