第二十四章 内存管理(第二部分)
六、静态内存管理的函数接口
静态内存管理函数的使用——开发流程:
- 规划一片内存区域作为静态内存池。
- 调用 rt_mp_create()函数。进行静态内存使用前的创建。
- 调用 rt_mp_alloc()函数。系统内部将会从空闲链表中获取第一个空闲块,并返回该块的用户空间地址。
- 调用 rt_mp_free()函数。将该块内存加入空闲块链表,进行内存的释放。
对内存池的操作包含:创建 / 初始化内存池、申请内存块、释放内存块、删除 / 脱离内存池,但不是所有的内存池都会被删除,这与设计者的需求相关,但是使用完的内存块都应该被释放。
1、静态内存控制块
1 struct rt_mempool {
2 struct rt_object parent; /**<继承自 rt_object */ (1)
**静态内存会在自身结构体里面包含一个对象类型的成员,通过这
个成员可以将内存挂到系统对象容器里面。 **
3
4 void *start_address; /**< 内存池起始地址 */ (2)**内存池开始地址**
5 rt_size_t size; /**< 内存池大小 */ (3) **内存池大小**
6
7 rt_size_t block_size; /**< 内存块大小 */ (4)**内存块大小**
8 rt_uint8_t *block_list; /**< 内存块链表 */ (5) **内存块链表,所有可用的内存块都挂载在此链表上**
9
10 rt_size_t block_total_count; /**< 内存块总数量 */ (6)**内存池数据区域中能够容纳的最大内存块数**
11 rt_size_t block_free_count; /**< 空闲内存块数量 */ (7) **内存池中空闲的内存块数**
12
13 rt_list_t suspend_thread; (8) **挂起在内存池的线程列表**
14 rt_size_t suspend_thread_count; (9) **挂起在内存池的线程数量**
15 };
16 typedef struct rt_mempool *rt_mp_t;
2 静态内存创建函数 rt_mp_create()
1 /**
2 * 此函数将创建一个 mempool 对象并从堆中分配内存池。
3 *
4 *
5 * @param name 内存池名称
6 * @param block_count 内存块数量
7 * @param block_size 内存块大小
8 *
9 * @return 已创建的内存池对象
10 */
11 rt_mp_t rt_mp_create(const char *name, (1) **name 内存池名称。**
12 rt_size_t block_count, (2) **block_count 初始化内存池中可分配内存块最大数量**
13 rt_size_t block_size) (3) **block_size 初始化内存块的大小**
14 {
15 rt_uint8_t *block_ptr;
16 struct rt_mempool *mp;
17 register rt_base_t offset;
18
19 RT_DEBUG_NOT_IN_INTERRUPT;
20
21 /* 分配对象 */
22 mp = (struct rt_mempool *)rt_object_allocate(RT_Object_Class_MemPool, name);
23 /* 分配对象失败 */
24 if (mp == RT_NULL) (4)**分配内存池对象**
25 return RT_NULL;
26
27 /* 初始化内存池信息 */
28 block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);
(5) **初始化内存池信息,初始化内存块大小,使其对齐方式与系统内存对齐方式一致**
29 mp->block_size = block_size; (6) **内存块大小按传递进来的 block_size 来进行初始化配置**
30 mp->size = (block_size + sizeof(rt_uint8_t *)) * block_count; (7)
**计 算 得 出 内 存 池 需 要 的 内 存 大 小 , 其 大 小 为 (block_size +
sizeof(rt_uint8_t *)) * block_count,也就是[内存块大小 +4 个字节大小(指向内存池控制
块)]乘以内存块的数量。 **
31
32 /* 分配内存 */
33 mp->start_address = rt_malloc((block_size + sizeof(rt_uint8_t *)) *
34 block_count); (8) **分配内存池**
35 if (mp->start_address == RT_NULL) {
36 /* 没有足够内存,删除内存池对象句柄 */
37 rt_object_delete(&(mp->parent)); (9)
**系统已经没有足够的内存了,分配失败,需要删除内存池对象句柄,所以在静态内存池创建的时候一定要考虑到系统的内存大小。**
38
39 return RT_NULL;
40 }
41
42 mp->block_total_count = block_count; (10) **分配成功,静态内存控制块的 block_total_count(内存块总数量)**
43 mp->block_free_count = mp->block_total_count; (11)**初始化空闲内存块数量。**
44
45 /* 初始化阻塞链表 */
46 rt_list_init(&(mp->suspend_thread)); (12) **初始化线程的阻塞列表和在此列表上线程的数量**
47 mp->suspend_thread_count = 0;
48
49 /* 初始化空闲内存块链表 */
50 block_ptr = (rt_uint8_t *)mp->start_address; (13) **初始化第一个内存块的起始地址。 **
51 for (offset = 0; offset < mp->block_total_count; offset ++) { (14)
**:在 for 循环中初始化空闲内存块列表,循环执行次数为空闲内存块的数量值。**
52 *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *)))
53 = block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *));
54 } (15)**将所有的内存块都连接起来,在分配的时候更容易管理**
55
56 *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *)))
57 = RT_NULL; (16)**最后一块内存块的下一个内存是没有了,就是 NULL。 **
58
59 mp->block_list = block_ptr; (17)**内存块列表指向第一块可用内存块**
60
61 return mp; (18) **创建成功返回内存池对象句柄**
62 }
63 RTM_EXPORT(rt_mp_create);
** (15)将所有的内存块都连接起来,在分配的时候更容易管理 **
使用该函数接口可以创建一个静态内存池,前提是在系统资源允许的情况下(最主要
的是动态堆内存资源)才能创建成功。
创建内存池成功将返回内存池的句柄,否则返回 RT_NULL。
例子:
1 /* 定义内存池控制块 */
2 static rt_mp_t test_mp = RT_NULL;
3 /* 定义申请内存的指针 */
4 static rt_uint32_t *p_test = RT_NULL;
5 /* 相关宏定义 */
6 #define BLOCK_COUNT 20 //内存块数量
7 #define BLOCK_SIZE 3 //内存块大小
8 /* 创建一个静态内存池 */
9 test_mp = rt_mp_create("test_mp",
10 BLOCK_COUNT,
11 BLOCK_SIZE);
12 if (test_mp != RT_NULL)
13 rt_kprintf("静态内存池创建成功!\n\n");
3 静态内存删除函数 rt_mp_delete()
删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回-RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。删除内存池后将无法向内存池申请内存块
1 /**
2 * 这个函数会删除内存池对象并且释放内存池对象的内存
3 *
4 * @param mp 内存池对象句柄
5 *
6 * @return 删除成功返回 RT_EOK
7 */
8 rt_err_t rt_mp_delete(rt_mp_t mp) (1) **:mp 内存池对象句柄**
9 {
10 struct rt_thread *thread;
11 register rt_ubase_t temp;
12
13 RT_DEBUG_NOT_IN_INTERRUPT;
14
15 /* 检查内存池对象 */
16 RT_ASSERT(mp != RT_NULL); (2) **检查内存池对象句柄 mp 是否有效**
17
18 /* 唤醒所有在阻塞中的线程 */
19 while (!rt_list_isempty(&(mp->suspend_thread))) { (3) **如果当前有线程挂在内存池的阻塞列表中,需要将该线程唤醒,
直到没有线程阻塞的时候才退出 while 循环。 **
20 /* 关中断 */
21 temp = rt_hw_interrupt_disable();
22
23 /* 获取阻塞线程 */ (4) **获取阻塞的线程**
24 thread = rt_list_entry(mp->suspend_thread.next, struct rt_thread, tlist);
25 /* 返回线程错误 */
26 thread->error = -RT_ERROR;
27
28 /*
29 * 恢复线程
30 * 在 rt_thread_resume 函数中,它将从挂起列表中删除当前线程
31 *
32 */
33 rt_thread_resume(thread); (5)
**调用 rt_thread_resume 线程恢复函数,将该线程恢复,该函数会将线程从阻塞链表中删除。 **
34
35 /* 挂起线程数减一 */
36 mp->suspend_thread_count --; (6) **将内存池控制块中记录线程挂起数量的 suspend_thread_count 变量
减一。**
37
38 /* 开中断 */
39 rt_hw_interrupt_enable(temp);
40 }
41
42 #if defined(RT_USING_MODULE) && defined(RT_USING_SLAB) (7)
**在这里我们并没有使用 slab 分配机制,未使能 RT_USING_SLAB
这个宏定义,所以还不需要使用 rt_module_free 释放内存函数。 **
43
44 if (mp->parent.flag & RT_OBJECT_FLAG_MODULE)
45 rt_module_free(mp->parent.module_id, mp->start_address);
46 else
47 #endif
48
49 /* 释放申请的内存池 */
50 rt_free(mp->start_address); (8)
**释放内存池的内存,因为这个内存池是从系统堆内存动态划分的,删除后要进行释放。 **
51
52 /* 删除内存池对象 */
53 rt_object_delete(&(mp->parent)); (9) **调用 rt_object_delete()函数删除内存池对象**
54
55 return RT_EOK; (10) **返回删除结果 RT_EOK。 **
56 }
57 RTM_EXPORT(rt_mp_delete);
注意:
删除的时候会将所有因为申请不到内存块而进入阻塞的线程恢复,被恢复的线程会得到一个-RT_ERROR,所以,建议在删除内存池之前我们应确保所有的线程没有阻塞,并且以后也不会再向这个内存池申请内存块,才进行删除操作。
例子:
1 /* 定义内存池控制块 */
2 static rt_mp_t test_mp = RT_NULL;
3
4 rt_err_t uwRet = RT_EOK;
5
6 /* 删除一个静态内存池 */
7 uwRet = rt_mp_delete(test_mp);
8 if (RT_EOK == uwRet)
9 rt_kprintf("静态内存池删除成功!\n\n");
4 静态内存初始化函数 rt_mp_init()
与创建内存池不同的是:此处内存池对象所使用的内存空间是由用户指定的一个缓冲区空间,用户把缓冲区的指针传递给内存池对象控制块,其余的初始化工作与创建内存池相同
1 /**
2 *
3 * 此函数将初始化内存池对象,通常用于静态对象。
4 *
5 * @param mp 内存池对象
6 * @param name 内存池名称
7 * @param start 内存池起始地址
8 * @param size 内存池总大小
9 * @param block_size 每个内存块的大小
10 *
11 * @return RT_EOK
12 */
13 rt_err_t rt_mp_init(struct rt_mempool *mp, (1)**mp 内存池对象句柄**
14 const char *name, (2)**name 内存池名称,是字符串常量类型。 **
15 void *start, (3) **start 内存池起始地址,由用户自己定义的具体的起始地址**
16 rt_size_t size, (4)**size 初始化内存池总容量大小**
17 rt_size_t block_size) (5) **block_size 每个内存块的大小。**
18 {
19 rt_uint8_t *block_ptr;
20 register rt_base_t offset;
21
22 /* 检查内存池 */
23 RT_ASSERT(mp != RT_NULL); (6)**检查内存池对象句柄 mp 是否有效**
24
25 /* 初始化内存池对象 */
26 rt_object_init(&(mp->parent), RT_Object_Class_MemPool, name); (7) **初始化内存池内核对象**
27
28 /* 初始化内存池 */
29 mp->start_address = start; (8)**初始化内存池,内存池的地址是由用户传递进来的地址。**
30 mp->size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE); (9)
**初始化内存池容量 size,使其以 4 字节对齐方式对齐,如果不满足
对齐倍数将返回其最小的对齐倍数,如想要对齐 13 字节大小的内存块, RT_ALIGN
(13,4) ,将返回 16(字节)。 **
31
32 /* 内存块大小对齐 */
33 block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE); (10)**初始化内存块大小 block_size,**
34 mp->block_size = block_size;
35
36
37 mp->block_total_count = mp->size / (mp->block_size + sizeof(rt_uint8_t *));
38 mp->block_free_count = mp->block_total_count; (11)
**通过计算得出内存池中最大内存块数量,例如内存池大小为 200
个字节,内存块的大小为 16 个字节,但是需要再加上 4 个字节大小的内存头(指向内存池
控制),很显然,内存块的数量最大为 5=200/(16+4),并且初始化可用空闲内存块个数。 **
39
40 /* 初始化阻塞链表 */
41 rt_list_init(&(mp->suspend_thread)); (12) **初始化线程的阻塞列表和线程阻塞的数量**
42 mp->suspend_thread_count = 0;
43
44 /* 初始化内存块空闲链表 */
45 block_ptr = (rt_uint8_t *)mp->start_address; (13) **初始化第一个内存块的起始地址。 **
46 for (offset = 0; offset < mp->block_total_count; offset ++) {(14)
**在 for 循环中初始化空闲内存块链表,循环执行次数为空闲内存块的数量值。 **
47 *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *))) =
48 (rt_uint8_t *)(block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *)));
49 } (15) **将所有的空闲内存块都连接起来,在分配的时候更容易管理**
50
51 *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) =
52 RT_NULL; (16)**最后一块内存块的下一个内存是没有了,就是 NULL**
53
54 mp->block_list = block_ptr; (17) **内存控制块的 block_list 内存块链表指向第一块可用内存块**
55
56 return RT_EOK; (18)**创建成功返回内存池对象句柄。**
57 }
58 RTM_EXPORT(rt_mp_init);
(15):将所有的空闲内存块都连接起来,在分配的时候更容易管理
例子:
1 /* 定义内存池控制块 */
2 static rt_mp_t test_mp = RT_NULL;
3 static rt_uint8_t mempool[4096];
4
5 rt_err_t uwRet = RT_EOK;
6
7 /* 初始化内存池对象 */
8 uwRet = rt_mp_init(&test_mp, /**内存池对象**/
9 "test_mp", /**内存池名称**/
10 &mempool[0], /**内存池起始地址**/
11 sizeof(mempool), /**内存池总大小**/
12 80); /**每个内存块的大小**/
13 if (RT_EOK == uwRet)
14 rt_kprintf("初始化内存成功!\n");
5 静态内存申请函数 rt_mp_alloc()
用于申请固定大小的内存块。
1 /**
2 * 这个函数用于从指定内存池分配内存块
3 *
4 * @param mp 内存池对象
5 * @param time 超时时间
6 *
7 * @return 分配成功的内存块地址或 RT_NULL 表示分配失败
8 */
9 void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time) (1) **mp 内存池对象,time 超时时间。**
10 {
11 rt_uint8_t *block_ptr;
12 register rt_base_t level;
13 struct rt_thread *thread;
14 rt_uint32_t before_sleep = 0;
15
16 /* 获取当前线程 */
17 thread = rt_thread_self(); (2) **获取当前线程。 **
18
19 /* 关中断 */
20 level = rt_hw_interrupt_disable();
21
22 while (mp->block_free_count == 0) { (3) **如果无内存块可用,进入 while 循环。 **
23 /* 无内存块可用 */
24 if (time == 0) { (4) **如果用户不设置等待时间,则直接返回错误码**
25 /* 开中断 */
26 rt_hw_interrupt_enable(level);
27
28 rt_set_errno(-RT_ETIMEOUT);
29
30 return RT_NULL;
31 }
32
33 RT_DEBUG_NOT_IN_INTERRUPT;
34
35 thread->error = RT_EOK;
36
37 /* 需要挂起当前线程 */
38 rt_thread_suspend(thread); (5) **因为能到这一步,用户肯定设置了等待时间的,那么,不管三七
二十一将当前线程挂起。**
39 rt_list_insert_after(&(mp->suspend_thread), &(thread->tlist));
40 mp->suspend_thread_count++; (6) **记录因为挂起的线程数量。**
41
42 if (time > 0) {
43 /* 获取当前系统时间 */
44 before_sleep = rt_tick_get(); (7) **获取当前系统时间**
45
46 /* 重置线程超时时间并且启动定时器 */
47 rt_timer_control(&(thread->thread_timer), (8) **重置线程计时器的超时时间,调用 rt_timer_control()函数改变当前
线程阻塞时间 thread_timer。 **
48 RT_TIMER_CTRL_SET_TIME,
49 &time);
50 rt_timer_start(&(thread->thread_timer)); (9)**启动定时器开始计时。**
51 }
52
53 /* 开中断 */
54 rt_hw_interrupt_enable(level);
55
56 /* 发起线程调度 */
57 rt_schedule(); (10)**因为现在线程是等待着了,要进行线程切换,
所以进行一次线程调度。**
58
59 if (thread->error != RT_EOK)
60 return RT_NULL;
61
62 if (time > 0) {
63 time -= rt_tick_get() - before_sleep;
64 if (time < 0)
65 time = 0;
66 }
67 /* 关中断 */
68 level = rt_hw_interrupt_disable();
69 }
70
71 /* 内存块可用,记录当前可用内存块个数,申请之后空闲内存块数量减一 */
72 mp->block_free_count--; (11) **当前内存池中还有内存块可用,记录当前可用内存块个数,申请
之后可用内存块数量减一。 **
73
74 /* 获取内存块指针 */
75 block_ptr = mp->block_list; (12)**获取内存块指针,指向空闲的内存块。**
76 RT_ASSERT(block_ptr != RT_NULL);
77
78 /* 设置下一个空闲内存块为可用内存块 */
79 mp->block_list = *(rt_uint8_t **)block_ptr; (13) **设置当前申请内存块的下一个内存块为可用内存块,将 mp-
>block_list 的指针指向下一个内存块**
80
81
82 *(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp; (14)
83
84 /* 开中断 */
85 rt_hw_interrupt_enable(level);
86
87 RT_OBJECT_HOOK_CALL(rt_mp_alloc_hook,
88 (mp, (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t
*))));
89
90 return (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *));(15)
**返回用户真正能读写操作的内存地址,其地址向下偏移了 4 个字
节。 **
91 }
92 RTM_EXPORT(rt_mp_alloc);
(13):设置当前申请内存块的下一个内存块为可用内存块,将 mp->block_list 的指针指向下一个内存块,
(14):如图所示,每一个内存块的前 4 个字节是指向内存池控制块的指针,
目的:是让我们在释放内存的时候能找到内存控制块。
问:为什么要记录内存控制块指针呢?
ANS:因为 block_list 是单链表,在申请成功内存的时候,已使用的内存块相当于脱离了内存块列表,那么在释放内存块的时候就没办法正常释放,所以需要保存内存控制块的指针。
例子:
1 /* 定义申请内存的指针 */
2 static rt_uint32_t *p_test = RT_NULL;
3 rt_kprintf("正在向内存池申请内存...........\n");
4
5 p_test = rt_mp_alloc(test_mp,0);
6 if (RT_NULL == p_test) /* 没有申请成功 */
7 rt_kprintf("静态内存申请失败!\n");
8 else
9 rt_kprintf("静态内存申请成功,地址为%d!\n\n",p_test);
10
11 rt_kprintf("正在向 p_test 写入数据...........\n");
12 *p_test = 1234;
13 rt_kprintf("已经写入 p_test 地址的数据\n");
14 rt_kprintf("*p_test = %.4d ,地址为:%d \n\n", *p_test,p_test);
15
6 静态内存释放函数 rt_mp_free()
任何内存块使用完后都必须被释放,否则会造成内存泄露,导致系统发生致命错误。
1 /**
2 * 这个函数会释放一个内存块
3 *
4 * @param block 要释放的内存块的地址
5 */
6 void rt_mp_free(void *block) (1) **block 要释放的内存块的地址。 **
7 {
8 rt_uint8_t **block_ptr;
9 struct rt_mempool *mp;
10 struct rt_thread *thread;
11 register rt_base_t level;
12
13 /* 获取块所属的池的控制块 */
14 block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));(2)
**每个内存块中前 4 个字节保存的信息就是指向内存池控制块指针,
所以,需要进行指针的偏移,为了获得内存池控制块的地址。**
15 mp = (struct rt_mempool *)*block_ptr; (3) **获取内存块所属的内存池对象 mp。 **
16
17 RT_OBJECT_HOOK_CALL(rt_mp_free_hook, (mp, block));
18
19 /* 关中断 t */ 在这里插入代码片
20 level = rt_hw_interrupt_disable();
21
22 /* 增加可以的内存块数量 */
23 mp->block_free_count ++; (4)**记录当前可用内存块数量。 **
24
25 /* 将释放的内存块添加到 block_list 链表中 */
26 *block_ptr = mp->block_list; (5) **将释放的内存块添加到 block_list 链表中**
27 mp->block_list = (rt_uint8_t *)block_ptr; (6)
28
29 if (mp->suspend_thread_count > 0) { (7) **如果当前有线程因为无法申请内存进入阻塞的话,会执行 while
循环中的代码。 **
30 /* 获取阻塞的线程 */
31 thread = rt_list_entry(mp->suspend_thread.next, (8) **获取阻塞的线程。 **
32 struct rt_thread,
33 tlist);
34
35 /* 重置线程错误为 RT_EOK */
36 thread->error = RT_EOK;
37
38 /* 恢复线程 */
39 rt_thread_resume(thread); (9)**调用 rt_ipc_list_resume 函数将该线程恢复。 **
40
41 /* 记录阻塞线程数量,减一 */
42 mp->suspend_thread_count --; (10) **记录阻塞线程数量,suspend_thread_count 减一。 **
43
44 /* 开中断 */
45 rt_hw_interrupt_enable(level);
46
47 /* 发起线程调度 */
48 rt_schedule(); (11) **恢复挂起的线程,需要发起一次线程调度。 **
49
50 return;
51 }
52
53 /* 开中断 */
54 rt_hw_interrupt_enable(level);
55 }
56 RTM_EXPORT(rt_mp_free);
(5):将释放的内存块添加到 block_list 链表中,内存控制块的指向当前
可用内存链表头
总结:
内存释放的使用是非常很简单的,仅将需要释放的内存块地址传递进去即可,系统会根据内存块前 4 字节的内容自动找到对应的内存池控制块,然后根据内存池控制块来进行释放内存操作。
1 /* 定义申请内存的指针 */
2 static rt_uint32_t *p_test = RT_NULL;
3
4 rt_kprintf("正在释放内存...........\n");
5 rt_mp_free(p_test);
来源:CSDN
作者:无脸男&巧巧
链接:https://blog.csdn.net/qq_41070511/article/details/103716643