1

RT-Thread快速入门-动态内存堆管理

 2 years ago
source link: https://blog.51cto.com/u_15505932/5159872
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

首发,公众号【一起学嵌入式

每种 RTOS 均有内存管理机制,RT-Thread 的内存管理分为两类:动态内存堆管理、内存池管理。

本篇文章先来介绍一下动态内存堆管理相关的内容。

内存堆管理机制

RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。内存堆管理根据具体内存设备划分为三种情况:

  • 针对小内存块的分配管理(小内存管理算法);
  • 针对大内存块的分配管理(slab 管理算法);
  • 针对多内存堆的分配情况(memheap 管理算法)

RT-Thread 系统为了满足不同的使用需求,提供了三种内存管理算法:

  • 小内存管理算法。主要用于系统资源较少的系统。
  • slab 管理算法。主要用在系统资源比较丰富的场景。
  • memheap 管理算法。适用于系统存在多个内存堆的情况,它可以将多个内存连接在一起,形成一个大的内存堆。

备注:这几类内存堆管理算法只能启用一个,但是提供给用户的接口完全相同。

注意事项:内存堆管理为了满足多线程场景下的安全分配,考虑多线程间的互斥问题。因此,不要在中断服务程序中分配或释放动态内存块。否则,会引起当前上下文挂起,引发问题出现。

1. 小内存管理算法

这种算法比较简单。初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。

每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如下图所示:

RT-Thread快速入门-动态内存堆管理_RTOS

数据头内容包括:

  • magic: 变数(幻数),初始值为 0x1ea,用于标记这个内存块是一个内存管理用的内存数据块 。
  • used:用于标识当前内存块是否已经分配。
  • next 用于将各个内存块链接起来,指向下一个内存块节点
  • prev 用于将各个内存块链接起来,指向当前内存节点的上一个节点。

2. slab 管理算法

RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。

RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。 slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:

RT-Thread快速入门-动态内存堆管理_RTOS_02

一个 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快速入门-动态内存堆管理_RTOS_03

内存堆管理方式

RT-Thread 的内存堆管理操作有以下几种:初始化、申请内存块、释放内存块。

RT-Thread快速入门-动态内存堆管理_RTOS_04

需要注意的是,在使用完动态内存之后,应该将其释放掉。否则,会出现内存泄漏的问题。

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;
		}
		else
		{
			rt_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快速入门-动态内存堆管理_嵌入式_05

OK,今天先到这,下次继续。加油~


公众号【一起学嵌入式】,分享 RTOS、Linux、C技术知识


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK