少年意气强不羁,虎胁插翼白日飞。这篇文章主要讲述RT-Thread快速入门-动态内存堆管理相关的知识,希望能为你提供帮助。
每种 RTOS 均有内存管理机制,RT-Thread 的内存管理分为两类:动态内存堆管理、内存池管理。
本篇文章先来介绍一下动态内存堆管理相关的内容。
内存堆管理机制RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。内存堆管理根据具体内存设备划分为三种情况:
- 针对小内存块的分配管理(小内存管理算法);
- 针对大内存块的分配管理(
slab
管理算法); - 针对多内存堆的分配情况(
memheap
管理算法)
- 小内存管理算法。主要用于系统资源较少的系统。
slab
管理算法。主要用在系统资源比较丰富的场景。memheap
管理算法。适用于系统存在多个内存堆的情况,它可以将多个内存连接在一起,形成一个大的内存堆。
注意事项:内存堆管理为了满足多线程场景下的安全分配,考虑多线程间的互斥问题。因此,不要在中断服务程序中分配或释放动态内存块。否则,会引起当前上下文挂起,引发问题出现。
1. 小内存管理算法
这种算法比较简单。初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。
每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如下图所示:
文章图片
数据头内容包括:
magic
: 变数(幻数),初始值为 0x1ea,用于标记这个内存块是一个内存管理用的内存数据块 。used
:用于标识当前内存块是否已经分配。next
用于将各个内存块链接起来,指向下一个内存块节点prev
用于将各个内存块链接起来,指向当前内存节点的上一个节点。
slab
管理算法RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。
RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。 slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:
文章图片
一个 zone 的大小在 32K 到 128K 字节之间,分配器会在堆初始化时根据堆的大小自动调整 。
系统中 zone 的个数最大为 72,一次最大可以分配 16K 的内存空间,如果超出了 16K 那么直接从页分配器中分配。每个 zone 上分配的内存块大小是固定的,相同大小内存块的 zone 会链接在一个链表中。
72 中对象的 zone 链表则放在一个数组中进行统一管理(
zone_array[]
)。(1)内存分配
分配内存时,首先从
zone_array[]
链表头数组中查找相依大小的 zone
链表。如果链表为空,则分配一个新的 zone
,然后从 zone
中返回第一个空闲内存块。若非空,则返回空闲块地址。(2)内存释放
内存释放时,分配器需要找到内存块所在的
zone
节点,然后把内存块链接到 zone 的空闲内存块链表中 。3.
memheap
管理算法这种管理方法适用于系统中含有多个地址可不连续的内存堆。这种方法可以简化系统中存在多个内存堆时的使用:用户在系统初始化时将多个
memheap
初始化,并开启 memheap
功能,就可以把多个 memheap
粘合起来用于系统的 heap 分配。其工作机制如下图所示。系统首先会将多个内存加入到
memheap_item
链表进行粘合。应用程序就可以在粘合后的内存堆中申请分配内存,就像在操作一个内存堆。文章图片
内存堆管理方式RT-Thread 的内存堆管理操作有以下几种:初始化、申请内存块、释放内存块。
文章图片
需要注意的是,在使用完动态内存之后,应该将其释放掉。否则,会出现内存泄漏的问题。
1. 分配和释放内存块
RT-Thread 系统提供的动态申请内存块的函数接口如下,与我们平时接触到的
malloc()
类似。void *rt_malloc(rt_size_t nbytes)
函数
rt_malloc()
会从系统堆空间中找到合适大小的内存块,然后把内存块首地址返回给用户。参数
nbytes
为需要分配内存的大小,单位为字节。分配成功,则返回内存块的地址;失败,返回 RT_NULL
。在申请的动态内存使用完毕后,必须及时释放,否则会造成内存泄漏。释放内存块的接口函数如下:
void rt_free (void *ptr)
此函数会把待释放的内存块还给堆管理器。
参数
ptr
为动态申请内存块的指针,即需要释放的内存块指针。如果为空指针,则直接返回。2. 重新分配内存块
同 C 函数库类似,RT-Thread 也提供了重新分配内存块,即在已分配内存块的基础上重新分配内存块的大小。重新分配内存块时,原来的内存块数据保持不变。如果内存块缩小,则后面的数据会被截断。
重新分配内存块大小的函数接口如下:
void *rt_realloc(void *rmem, rt_size_t newsize)
参数
rmem
为指向已分配的内存块指针;newsize
为重新分配的内存大小,单位为字节。分配成功,则返回重新分配的内存块地址;否则,返回
RT_NULL
。3. 分配多个内存块
RT-Thread 也提供了从内存堆中分配连续内存的多个内存块的接口,其具体的函数原型如下:
void *rt_calloc(rt_size_t count, rt_size_t size)
参数
count
表示内存块的数量;size
表示每个内存块的大小,单位为字节。分配成功,则返回第一个内存块地址的指针;失败,则返回
RT_NULL
。该函数会把所有分配的内存块初始化为零。
应用实例老规矩,用一个示例来演示 RT-Thread 内存管理接口的使用方法。
这个例程会创建一个动态线程,这个线程动态申请内存并释放,一共申请 10 次。每次申请更大的内存,当申请不到的时候就结束,如下代码所示 :
/* 线程 1 入口 */
static void thread1_entry(void *parameter)int i;
char *ptr = RT_NULL;
/* 内存块指针 */for (i = 0;
i <
10;
i++)/* 每次分配 (1 <
<
i) 大小字节数的内存空间 */
ptr = rt_malloc(1 <
<
i);
/* 如果分配成功 */
if (ptr != RT_NULL)rt_kprintf("get memory :%d byte\\n", (1 <
<
i));
/* 释放内存块 */
rt_free(ptr);
rt_kprintf("free memory :%d byte\\n", (1 <
<
i));
ptr = RT_NULL;
elsert_kprintf("try to get %d byte memory failed!\\n", (1 <
<
i));
return;
int main()rt_thread_t thread1 = RT_NULL;
/* 动态创建线程1 */
thread1 = rt_thread_create("thread1", thread1_entry, RT_NULL,
1024, THREAD_PRIORITY, THREAD_TIMESLICE);
if(thread1 != RT_NULL)/* 启动线程 */
rt_thread_startup(thread1);
return 0;
编译运行结果如下:
文章图片
【RT-Thread快速入门-动态内存堆管理】OK,今天先到这,下次继续。加油~
推荐阅读
- #yyds干货盘点#将CSV的数据发送到kafka(java版)
- C# 将Excel转为PDF时设置内容适应页面宽度
- Nginx实践(用rewrite规则实现域名重定向及客户端IP地址透传)
- CentOS 文件的属性及类型
- Pod概念详解
- 准时下班系列!Access合集之第5集—闭环的Access系统开发流程演示
- # yyds干货盘点 # 实战篇(盘点Pandas中的factorize()函数妙用)
- RouterOS 重置密码
- 二叉树面试题(前中序求后序中后序求前序)