不直接使用C标准库中的内存管理函数的原因

描述

前言

本文讲RT-Thread的内存管理,包括为何不使用C标准库的内存管理函数、内存管理的特点、RT-Thread 程序内存分布、内存堆管理、内存池管理以及使用STM32进行实验。

一、不直接使用 C 标准库中的内存管理函数的原因

很多人会有疑问,为什么不直接使用 C 标准库中的内存管理函数呢?在电脑中我们可以用malloc()和 free()这两个函数动态的分配内存和释放内存。但是,在嵌入式实时操作系统中,调用 malloc()和 free()却是危险的,原因有以下几点:

1、这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。

2、它们的实现可能非常的大,占据了相当大的一块代码空间。

3、他们几乎都不是线程安全的。

4、它们并不是确定的,每次调用这些函数执行的时间可能都不一样。

5、它们有可能产生碎片。

6、这两个函数会使得链接器配置得复杂。

7、如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难 。

二、内存管理的功能特点

1、分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。

2、随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片(因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一整块的大内存分配出去),系统中还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存。对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决 (每个月或者数个月进行一次),但是对于那些需要常年不间断地工作于野外的嵌入式系统来说,就变得让人无法接受了。

3、嵌入式系统的资源环境也是不尽相同,有些系统的资源比较紧张,只有数十 KB 的内存可供分配,而有些系统则存在数 MB 的内存,如何为这些不同的系统,选择适合它们的高效率的内存分配算法,就将变得复杂化。

三、RT-Thread 程序内存分布

一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。

1、对于STM32,在keil编译后,会出现如下信息:

函数

上面提到的 Program Size 包含以下几个部分:

(1)Code:代码段,存放程序的代码部分;

(2)RO-data:只读数据段,存放程序中定义的常量;

(3)RW-data:读写数据段,存放初始化为非 0 值的全局变量;

(4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;

编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:

1    Total RO  Size (Code + RO Data)                43688 (  42.66kB)2    Total RW  Size (RW Data + ZI Data)              3976 (   3.88kB)3    Total ROM Size (Code + RO Data + RW Data)      43812 (  42.79kB)4

2、程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如图下图 中左图所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。

函数

RT-Thread 内存分布(来源RT-Thread编程指南)

3、STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。

四、内存堆管理

内存堆管理根据具体内存设备划分为三种情况:(1)针对小内存块的分配管理(小内存管理算法);(2)针对大内存块的分配管理(slab 管理算法);(3)针对多内存堆的分配情况(memheap 管理算法)

1、将 *“ZI 段结尾处”* 到内存尾部的空间用作内存堆

(1)内存堆管理用于管理一段连续的内存空间如下图所示,RT-Thread 将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。

函数

RT-Thread 内存分布(来源RT-Thread编程指南)

(2)在前面的其他笔记,都是从内部SRAM申请一块静态内存来作为内存使用。

1#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) 2#define RT_HEAP_SIZE 6*1024 3/* 从内部SRAM申请一块静态内存来作为内存堆使用 */ 4static uint32_t rt_heap[RT_HEAP_SIZE];    // heap default size: 24K(1024 * 4 * 6) 5 6RT_WEAK void *rt_heap_begin_get(void) 7{ 8    return rt_heap; 9}1011RT_WEAK void *rt_heap_end_get(void)12{13    return rt_heap + RT_HEAP_SIZE;14}15#endif161718/* 在rt_hw_board_init中 */1920rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());

(3)那么接下来,我们修改代码,将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。

(A)在board.h添加如下代码:

1#ifdef __ICCARM__ 2// Use *.icf ram symbal, to avoid hardcode. 3extern char __ICFEDIT_region_IRAM1_end__; 4#define STM32_SRAM_END          &__ICFEDIT_region_IRAM1_end__ 5#else 6#define STM32_SRAM_SIZE         96    /* 根据自己的MCU不同修改 */ 7#define STM32_SRAM_END          (0x20000000 + STM32_SRAM_SIZE * 1024)/* 根据自己的MCU不同修改 */ 8#endif 910#ifdef __CC_ARM11extern int Image$$RW_IRAM1$$ZI$$Limit;12#define HEAP_BEGIN    (&Image$$RW_IRAM1$$ZI$$Limit)13#elif __ICCARM__14#pragma section="HEAP"15#define HEAP_BEGIN    (__segment_end("HEAP"))16#else17extern int __bss_end;18#define HEAP_BEGIN    (&__bss_end)19#endif2021#define HEAP_END                STM32_SRAM_END

(B)在board.c中将前面第(2)的那部分代码全部去掉,然后修改rt_hw_board_init函数,在后面加入如下代码:

1#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)2    rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);3#endif

2、小内存管理算法

(1)小内存管理算法是一个简单的内存分配算法。初始时,它是一块大的内存,其大小为(MEM_SIZE)。

函数

初始时的内存(来源[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》)

(2)当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来(内存块链表)。

函数

小内存管理工作机制图(来源[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》)

(3)每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头,其中包括:

(A)magic:变数(或称为幻数),它会被初始化成 0x1ea0(即英文单词 heap),用于标记这个内存块是一个内存管理用的内存数据块;变数不仅仅用于标识这个数据块是一个内存管理用的内存数据块,实质也是一个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被非法改写(正常情况下只有内存管理器才会去碰这块内存)。

(B)used:指示出当前内存块是否已经分配。

(4)内存管理的在表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。空闲链表指针 lfree 初始指向 32 字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52字节)继续留在 lfree链表中,在每次分配内存块前,都会留出 12 字节数据头用于 magic,used 信息及链表节点使用。返回给应用的地址实际上是这块内存块 12 字节以后的地址,而数据头部分是用户永远不应该改变的部分。

函数

小内存管理算法链表结构示意图(来源[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》)

函数

分配 64 字节后的链表结构(来源[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》)

(5)释放时则是相反的过程,分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。

3、slab 管理算法

RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:

函数

slab 内存分配结构图(来源RT-Thread编程指南)

一个 zone 的大小在 32K 到 128K 字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中的 zone 最多包括 72 种对象,一次最大能够分配 16K 的内存空间,如果超出了 16K 那么直接从页分配器中分配。每个 zone 上分配的内存块大小是固定的,能够分配相同大小内存块的 zone 会链接在一个链表中,而 72 种对象的 zone 链表则放在一个数组(zone_array[])中统一管理。

下面是内存分配器主要的两种操作:

(1)内存分配:假设分配一个 32 字节的内存,slab 内存分配器会先按照 32 字节的值,从 zone array 链表表头数组中找到相应的 zone 链表。如果这个链表是空的,则向页分配器分配一个新的 zone,然后从 zone 中返回第一个空闲内存块。如果链表非空,则这个 zone 链表中的第一个 zone 节点必然有空闲块存在(否则它就不应该放在这个链表中),那么就取相应的空闲块。如果分配完成后,zone 中所有空闲内存块都使用完毕,那么分配器需要把这个 zone 节点从链表中删除。

(2)内存释放:分配器需要找到内存块所在的 zone 节点,然后把内存块链接到 zone 的空闲内存块链表中。如果此时zone 的空闲链表指示出 zone 的所有内存块都已经释放,即 zone 是完全空闲的,那么当 zone 链表中全空闲 zone 达到一定数目后,系统就会把这个全空闲的 zone 释放到页面分配器中去。

4、memheap 管理算法

(1)memheap 管理算法适用于系统含有多个地址可不连续的内存堆。使用 memheap 内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。

注意:在开启 memheap 之后原来的 heap 功能将被关闭,两者只可以通过打开或关闭RT_USING_MEMHEAP_AS_HEAP来选择其一。

(2)memheap 工作机制如下图所示,首先将多块内存加入memheap_item链表进行粘合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找 memheap_item 链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。

函数

memheap 处理多内存堆(来源RT-Thread编程指南)

(3)对于有部分ST MCU是将内部SRAM分为地址不连续的两部分SRAM1和SRAM2,那么就可以用memheap管理算法,例如IoT board的MCU STM32L475VET6。在前面讲将到的 “ZI 段结尾处” 到内存尾部的空间用作内存堆,只是修改了SRAM1(96K)部分,那么如果想用SRAM2(32K)部分,需要修改代码。

(A)在board.h中加入如下代码:

1/* 根据自己的MCU不同,确认MCU内部SRAM是否有分为两块SRAM1和SRAM2,STM32L475VET6内部SRAM分为SRAM1和SRAM2两块地址不连续 */2#define STM32_SRAM2_SIZE        323#define STM32_SRAM2_BEGIN       (0x10000000u)4#define STM32_SRAM2_END         (0x10000000 + STM32_SRAM2_SIZE * 1024)5#define STM32_SRAM2_HEAP_SIZE   ((uint32_t)STM32_SRAM2_END - (uint32_t)STM32_SRAM2_BEGIN)

(B)在board.c中加入如下代码:

1#if defined(RT_USING_MEMHEAP) && defined(RT_USING_MEMHEAP_AS_HEAP)2static struct rt_memheap system_heap;3#endif

(C)修改board.c中的rt_hw_board_init函数,内存堆配置和初始化代码改为:

1#if defined(RT_USING_MEMHEAP) && defined(RT_USING_MEMHEAP_AS_HEAP)2    rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);3    rt_memheap_init(&system_heap, "sram2", (void *)STM32_SRAM2_BEGIN, STM32_SRAM2_HEAP_SIZE);4#else5    rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);6#endif

(4)根据自己是否想用使用SRAM2来决定是否使用memheap 管理算法,在rtconfig.h打开关闭相关宏来实现,如需要使用memheap 管理算法,打开如下宏:

1#define RT_USING_MEMHEAP                //定义该宏可开启两个或以上内存堆拼接的使用,未定义则关闭2#define RT_USING_MEMHEAP_AS_HEAP

(5)如果RT_USING_MEMHEAP和RT_USING_MEMHEAP_AS_HEAP这两个宏打开了,则使用memheap,那么系统内存堆的时候首先会从SRAM1(96K的那块)分配内存,当SRAM1(96K的那块)用完了再到SRAM2(32K那块)分配。

(6)打开RT_USING_MEMHEAP_AS_HEAP之后,实现的算法不同,比如rt_malloc()函数的实现。

5、内存堆配置和初始化

(1)在使用内存堆时,必须要在系统初始化的时候进行堆的初始化,可以通过下面的函数接口完成:

1void rt_system_heap_init(void* begin_addr, void* end_addr);

(A)入口参数:

begin_addr:堆内存区域起始地址。end_addr:堆内存区域结束地址。

(2)在使用 memheap 堆内存时,必须要在系统初始化的时候进行堆内存的初始化,可以通过下面的函数接口完成:

1rt_err_t rt_memheap_init(struct rt_memheap *memheap,2                         const char        *name,3                         void              *start_addr,4                         rt_uint32_t        size);

(A)入口参数:

memheap:memheap 控制块。name:内存堆的名称。start_addr:堆内存区域起始地址。size:堆内存大小。

(B)返回值:

RT_EOK:成功。

6、内存堆的管理方式

(1)申请内存块:会从系统堆空间中找到合适大小的内存块,然后把内存块可用地址返回给用户,函数接口如下:

1void *rt_malloc(rt_size_t size);

(A)入口参数:

size:需要分配的内存块的大小,单位为字节。

(B)返回值:

分配的内存块地址:成功。RT_NULL:失败。

(2)释放内存块:应用程序使用完从内存分配器中申请的内存后,必须及时释放,否则会造成内存泄漏,会把待释放的内存还回给堆管理器中,函数接口如下:

1void rt_free(void *rmem);

(A)入口参数:

rmem:待释放的内存块指针。

(3)重分配内存块:在已分配内存块的基础上重新分配内存块的大小(增加或缩小),在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断),函数接口如下:

1void *rt_realloc(void *rmem, rt_size_t newsize);

(A)入口参数:

rmem:指向已分配的内存块。newsize:重新分配的内存大小。

(B)返回值:

重新分配的内存块地址:成功。 RT_NULL:失败。

(4)分配多内存块:从内存堆中分配连续内存地址的多个内存块,可以通过下面的函数接口完成:

1void *rt_calloc(rt_size_t count, rt_size_t size);

(A)入口参数:

count:内存块数量。size:内存块容量。

(B)返回值:

指向第一个内存块地址的指针:成功,并且所有分配的内存块都被初始化成零。RT_NULL:分配失败。

(5)设置分配内存钩子函数:在分配内存块过程中,用户可设置一个钩子函数,设置的钩子函数会在内存分配完成后进行回调。回调时,会把分配到的内存块地址和大小做为入口参数传递进去,函数接口如下:

1void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size));

(A)hook:钩子函数指针。

(B)void hook(void *ptr, rt_size_t size); 函数接口参数:

ptr:分配到的内存块指针。  size:分配到的内存块的大小。

(6)设置是否内存钩子函数:在释放内存时,用户可设置一个钩子函数,设置的钩子函数会在调用内存释放完成前进行回调。回调时,释放的内存块地址会做为入口参数传递进去(此时内存块并没有被释放),函数接口如下:

1void rt_free_sethook(void (*hook)(void *ptr));

(A)hook:钩子函数指针。

(B)void hook(void *ptr); 函数接口参数:

ptr:待释放的内存块指针。

五、内存池

内存堆管理器可以分配任意大小的内存块,非常灵活和方便。但其也存在明显的缺点:一是分配效率不高,在每次分配时,都要空闲内存块查找;二是容易产生内存碎片。为了提高内存分配的效率,并且避免内存碎片,RT-Thread 提供了另外一种内存管理方法:内存池(Memory Pool)。内存池(Memory Pool)是一种用于分配大量大小相同的小内存对象的技术。它可以极大加快内存分配/释放的速度。

1、内存块分配机制

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

函数

内存池示意图(来源[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》)

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

2、内存池的管理方式

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

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

(A)入口参数:name:内存池名。block_count:内存块数量。block_size:内存块容量。

(B)返回值:

内存池的句柄:创建内存池对象成功。RT_NULL:创建失败。

(2)删除内存池:将删除内存池对象并释放申请的内存,删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回 RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。函数接口如下:

1rt_err_t rt_mp_delete(rt_mp_t mp);

(A)入口参数:mp:rt_mp_create返回的内存池对象句柄。

(B)返回值:RT_EOK:删除成功。

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

1rt_err_t rt_mp_init(struct rt_mempool *mp,2                    const char        *name,3                    void              *start,4                    rt_size_t          size,5                    rt_size_t          block_size);

(A)入口参数:

mp:内存池对象。name:内存池名。start:内存池的起始位置。size:内存池数据区域大小。block_size:内存块容量。

(B)返回值:

RT_EOK:初始化成功。RT_ERROR:失败。

注意:内存池块个数 = size / (block_size + 4 链表指针大小),计算结果取整数。例如:内存池数据区总大小 size 设为 4096 字节,内存块大小 block_size 设为 80 字节;则申请的内存块个数为 4096/ (80+4)= 48 个。

(4)脱离内存池:脱离内存池将把内存池对象从内核对象管理器中脱离,内核先唤醒所有等待在该内存池对象上的线程,然后将内存池对象从内核对象管理器中脱离。函数接口如下:

1rt_err_t rt_mp_detach(struct rt_mempool *mp);

(A)入口参数:

mp:内存池对象。

(B)返回值:

RT_EOK:成功。

(5)分配内存块:从指定的内存池中分配一个内存块,函数接口如下:

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

(A)入口参数:

mp:内存池对象。

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

(B)返回值:

分配的内存块地址:成功。RT_NULL:失败。

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

1void rt_mp_free(void *block);

(A)入口参数: block:内存块指针。

六、基于STM32的内存管理实验

光说不练都是假把式,那么接下来就行内存管理的实际操作,基于STM32,使用RTT&正点原子联合出品潘多拉开发板,实现两个实验,分别是内存堆管理实验和内存池实验。

1、内存堆管理实验

(1)实现代码:

1#include "main.h" 2#include "board.h" 3#include "rtthread.h" 4#include "data_typedef.h" 5#include "key.h" 6 7/* 线程句柄 */ 8static rt_thread_t thread1 = RT_NULL; 9void dynmem_sample(void);1011int main(void)12{13    dynmem_sample();14    return 0;15}1617/**************************************************************18函数名称 : thread1_entry19函数功能 : 线程1入口函数20输入参数 : parameter:入口参数21返回值       : 无22备注         : 无23**************************************************************/24void thread1_entry(void *parameter)25{26    u8 key;27    char *ptr = RT_NULL;2829    while(1)30    {31        key = key_scan(0);3233        if(key== KEY0_PRES)34        {   35            ptr = rt_malloc(10);36            if(ptr != RT_NULL)37            {38                rt_kprintf("rt_malloc successful\r\n");39                sprintf(ptr, "%s", "hello RTT");40                rt_kprintf("0x%p\r\n", ptr);/* 打印分配到的地址 */41                rt_kprintf("%s\r\n", ptr);42            }43            else44            {45                rt_kprintf("rt_malloc failed\r\n");46            }4748            rt_thread_mdelay(2000);4950            if(ptr != RT_NULL)51            {52                rt_free(ptr);53                ptr = RT_NULL;54                rt_kprintf("rt_free successful\r\n");55            }56            else57            {58                rt_kprintf("rt_free failed, ptr != NULL\r\n");59            }60        }6162        rt_thread_mdelay(1);63    }64}656667void dynmem_sample(void)68{69    thread1 = rt_thread_create("thread1",70                thread1_entry,71                NULL,72                512,73                3,74                20);75    if(thread1 != RT_NULL)76    {77        rt_thread_startup(thread1);;78    }79    else80    {81        rt_kprintf("create thread1 failed\r\n");82        return;83    }84}

(2)观察FinSH,开机按下3次KEY0,如下现象,会打印出申请到内存的地址,2秒后释放内存:

函数

2、内存池实验

(1)实现代码:

1#include "main.h" 2#include "board.h" 3#include "rtthread.h" 4#include "data_typedef.h" 5#include "key.h" 6 7/* 线程句柄 */ 8static rt_thread_t thread2 = RT_NULL; 9 10static rt_mp_t mp; 11 12void mempool_sample(void); 13 14int main(void) 15{ 16    mempool_sample(); 17 18    return 0; 19} 20 21/************************************************************** 22函数名称 : thread2_entry 23函数功能 : 线程2入口函数 24输入参数 : parameter:入口参数 25返回值       : 无 26备注         : 无 27**************************************************************/ 28void thread2_entry(void *parameter) 29{ 30    u8 key; 31    char *ptr = RT_NULL; 32 33    while(1) 34    { 35        key = key_scan(0); 36 37        if(key== KEY1_PRES) 38        {    39            ptr = rt_mp_alloc(mp, 0); 40            if(ptr != RT_NULL) 41            { 42                rt_kprintf("rt_mp_alloc successful\r\n"); 43                sprintf(ptr, "%s", "hello RTT"); 44                rt_kprintf("0x%p\r\n", ptr);/* 打印分配到的地址 */ 45                rt_kprintf("%s\r\n", ptr); 46            } 47            else 48            { 49                rt_kprintf("rt_mp_alloc failed\r\n"); 50            } 51 52            rt_thread_mdelay(2000); 53 54            if(ptr != RT_NULL) 55            { 56                rt_mp_free(ptr); 57                ptr = RT_NULL; 58                rt_kprintf("rt_mp_free successful\r\n"); 59            } 60            else 61            { 62                rt_kprintf("rt_mp_free failed, ptr != NULL\r\n"); 63            } 64        } 65 66        rt_thread_mdelay(1); 67    } 68} 69 70 71void mempool_sample(void) 72{ 73    mp = rt_mp_create("mp1", 20, 20); 74 75    if(mp != RT_NULL) 76    { 77        rt_kprintf("mempool create successful\r\n"); 78    } 79    else 80    {    81        rt_kprintf("mempool create failed\r\n"); 82        return; 83    } 84 85    thread2 = rt_thread_create("thread2", 86                   thread2_entry, 87                   NULL, 88                   512, 89                   3, 90                   20); 91    if(thread2 != RT_NULL) 92    { 93        rt_thread_startup(thread2);; 94    } 95    else 96    { 97        rt_kprintf("create thread2 failed\r\n"); 98        return; 99    }100101}

(2)观察FinSH,开机,打印创建mempool成功信息,连续按3次KEY1,打印如下信息,包括申请到内存的地址,2秒后释放内存:

函数

参考文献:

1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》

2、《RT-THREAD 编程指南》

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分