论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
第22章 ThreadX动态内存管理
本章节为大家讲解ThreadX动态内存管理,ThreadX支持固定数据大小的内存块式分配,也支持类似C库的malloc方式分配。
22.1 ThreadX内存块方式介绍
22.2 ThreadX内存字节池方式介绍
22.3 内存块创建函数tx_block_pool_create
22.4 内存块申请函数tx_block_allocate
22.5 内存块释放函数tx_block_release
22.6 内存池创建函数tx_byte_pool_create
22.7 内存池申请函数tx_byte_allocate
22.8 内存池释放函数tx_byte_release
22.9 内存池使用情况获取函数tx_byte_pool_info_get
22.10 实验例程
22.11 总结
22.1 ThreadX内存块方式介绍
在ANSI C中,可以用malloc()和free()动态的分配内存和释放内存,但是,在嵌入式实时操作系统中,调用malloc()和free()却是危险的,因为多次调用这两个函数会把原来很大的一块连续内场区域逐渐地分割成许多非常小而且彼此又不相邻的内存块,也就是内存碎片。由于这些内存碎片的大量存在,使得程序到后来连一段非常小的连续内存也分配不到。另外,由于内存管理算法上的原因,malloc()和free()函数的执行时间是不确定的。
在ThreadX内存块管理方式中,操作系统把连续的大块内存按分区来管理。每个分区中包含整数个大小相同的内存块:
利用这种机制,就可以得到和释放固定大小的内存块。这样内存的申请和释放函数的执行时间就是确定的了。
在一个系统中可以有多个内存分区,这样,应用程序就可以从不同的内存分区中得到不同大小的内存块。但是特定的内存块在释放时,必须重新放回到它以前所属的内存分区。显然,采用这样的内存管理算法,上面的内存碎片文件就得到了解决。
缺乏灵活性是固定大小内存块的主要缺点。块大小必须足够大,才能处理用户最坏情况下的内存需求。如果发出许多大小不同的内存请求,则可能会浪费内存。 一种可能的解决方案是创建多个不同的内存块。
22.2 ThreadX内存字节池方式介绍
ThreadX内存字节池与标准C库类似。不同之处在于,ThreadX的内存字节池支持多个不同内存区的创建管理。此外,任务可在池中挂起,直到请求的内存可用为止。
内存字节池的分配与传统的 malloc 调用类似,其中包含所需的内存量(以字节为单位)。内存采用“first-fit”的方式从池中分配;例如,使用满足请求的第一个可用内存块,此块中多余的内存会转换为新块,并放回可用内存列表中,此过程称为碎片。相邻的可用内存块在后续的分配搜索过程中合并为一个足够大的可用内存块。此过程称为碎片整理。
每个内存字节池都是一个公用资源。 除了不能从中断服务程序里面调用内存字节服务之外,ThreadX 对如何使用也没有任何限制。
22.3 内存块创建函数tx_block_pool_create
函数原型:
代码语言:javascript复制UINT tx_block_pool_create(
TX_BLOCK_POOL pool_ptr,
CHAR name_ptr,
ULONG block_size,
VOID pool_start,
ULONG pool_size);
函数描述:
此函数用于内存块创建,内存块中每个内存单元的大小是固定。
函数参数:
1、 第1个参数是内存控制块。
2、 第2个参数是内存块名字。
3、 第3个参数是内存块中每个内存单元的大小。
4、 第4个参数是内存块起始地址,必须ULONG对齐,即4字节对齐。
5、 第5个参数是内存块总大小,单位字节。
6、 返回值
- TX_SUCCESS (0x00) 创建成功。
- TX_POOL_ERROR (0x02) 无效的内存控制块。或者内存块已经创建。又或者指针为NULL。
- TX_PTR_ERROR (0x03) 表示无效的内存块地址。
- TX_CALLER_ERROR (0x13) 表示无效的调用。
- TX_SIZE_ERROR(0x05)表示无效的内存块大小。
注意事项:
- 可以在初始化和任务中调用。
- 总的内存单元个数是total blocks = (total bytes) / (block size sizeof(void *))。
使用举例:
代码语言:javascript复制TX_BLOCK_POOL AppBlock;
uint32_t AppBlockBuf[1024];
/* 创建内存块,用于固定大小内存单元申请 */
tx_block_pool_create(&AppBlock,
"AppBlock",
4, /* 内存单元大小 */
(VOID *)AppBlockBuf, /* 内存块地址,需要保证4字节对齐 */
sizeof(AppBlockBuf));/* 内存块总大小,单位字节 */
22.4 内存块申请函数tx_block_allocate
函数原型:
代码语言:javascript复制UINT tx_block_allocate(
TX_BLOCK_POOL *pool_ptr,
VOID **block_ptr,
ULONG wait_option);
函数描述:
用于内存块申请,每次可申请一个内存单元。
函数参数:
1、 第1个参数是内存控制块。
2、 第2个参数用于存放申请的内存单元地址。
3、 第3个参数是没有可用的内存单元时执行方式,支持如下三种参数
- TX_NO_WAIT (0x00000000),表示不管是否获取成功,立即返回。如果在初始化阶段调用,必须要设置成这个参数。
- TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到有内存单元可用。
- 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍、
4、 返回值
- TX_SUCCESS:(0x00) 成功分配内存块。
- TX_DELETED:(0x01) 线程挂起时删除了内存块。
- TX_NO_MEMORY:(0X10) 服务无法在指定的等待时间内分配内存块。
- TX_WAIT_ABORTED:(0x1A) 挂起状态由其他线程、计时器或 ISR 中止。
- TX_POOL_ERROR:(0x02) 内存块指针无效。
- TX_WAIT_ERROR:(0x04) 从初始化阶段,定时器任务或者中断复位程序里面调用了除
- TX_NO_WAIT 以外的等待选项。
- TX_PTR_ERROR:(0x03) 无效的内存单元存放地址。
注意事项:
- 可以在初始化,任务,定时器组或中断复位程序里面调用。
使用举例:
代码语言:javascript复制TX_BLOCK_POOL AppBlock;
uint8_t *BlockPtr;
/* 申请内存块,每次申请4字节 */
status = tx_block_allocate(&AppBlock,
(VOID **)&BlockPtr,
X_NO_WAIT);
if(status == TX_SUCCESS)
{
printf("AppBlock内存块申请成功rn");
}
22.5 内存块释放函数tx_block_release
函数原型:
UINT tx_block_release(VOID *block_ptr);
函数描述:
用于释放从内存块中申请的内存单元。
函数参数:
1、 第1个参数是内存控制块。
2、 返回值
- TX_SUCCESS:(0x00) 成功释放内存块。
- TX_PTR_ERROR:(0x03) 无效的内存单元存放地址。
注意事项:
- 可以在初始化,任务,定时器组或中断复位程序里面调用。
使用举例:
代码语言:javascript复制uint8_t *BlockPtr;
status = tx_block_release(BlockPtr);
if(status == TX_SUCCESS)
{
printf("AppBlock内存块释放成功rn");
}
22.6 内存池创建函数tx_byte_pool_create
函数原型:
代码语言:javascript复制UINT tx_byte_pool_create(
TX_BYTE_POOL *pool_ptr,
CHAR *name_ptr,
VOID *pool_start,
ULONG pool_size);
函数描述:
此函数用于内存池创建。
函数参数:
1、 第1个参数是内存池控制块。
2、 第2个参数是内存池名字。
3、 第3个参数是内存池首地址,必须ULONG对齐,即4字节对齐。
4、 第4个参数是内存池大小,单位字节。
5、 返回值
- TX_SUCCESS:(0X00) 成功创建内存池。
- TX_POOL_ERROR:(0x02) 内存池指针无效。指针为 NULL 或池已创建。
- TX_PTR_ERROR:(0x03) 内存池的起始地址无效。
- TX_SIZE_ERROR:(0x05) 内存池大小无效。
- NX_CALLER_ERROR:(0x13) 无效调用。
注意事项:
- 可以在初始化和任务中调用。
使用举例:
代码语言:javascript复制TX_BYTE_POOL AppPool;
uint32_t AppPoolBuf[1024];
/* 创建内存池,类似malloc和free */
tx_byte_pool_create(&AppPool,
"AppPool",
(VOID *)AppPoolBuf, /* 内存池地址,需要保证4字节对齐 */
sizeof(AppBlockBuf)); /* 内存池大小 */
22.7 内存池申请函数tx_byte_allocate
函数原型:
代码语言:javascript复制UINT tx_byte_allocate(
TX_BYTE_POOL *pool_ptr,
VOID **memory_ptr,
ULONG memory_size,
ULONG wait_option);
函数描述:
用于从内存池中申请指定字节数。
函数参数:
1、 第1个参数是内存池控制块。
2、 第2个参数用于存放申请的内存地址。
3、 第3个参数是申请的字节数。
4、 第4个参数是没有可用的内存时执行方式,支持如下三种参数
- TX_NO_WAIT (0x00000000),表示不管是否获取成功,立即返回。如果在初始化阶段调用,必须要设置成这个参数。
- TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到有内存单元可用。
- 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍、
5、 返回值
- TX_SUCCESS:(0x00) 成功分配内存块。
- TX_DELETED:(0x01) 线程挂起时删除了内存块。
- TX_NO_MEMORY:(0X10) 服务无法在指定的等待时间内分配内存块。
- TX_WAIT_ABORTED:(0x1A) 挂起状态由其他线程、计时器或 ISR 中止。
- TX_POOL_ERROR:(0x02) 内存块指针无效。
- TX_WAIT_ERROR:(0x04) 从初始化阶段,定时器任务或者中断复位程序里面调用了除
- TX_NO_WAIT 以外的等待选项。
- TX_PTR_ERROR:(0x03) 无效的内存单元存放地址。
- TX_SIZE_ERROR:(0X05) 所请求的大小为零或超过池大小。
- TX_CALLER_ERROR:(0x13) 无效的调用。
注意事项:
- 可以在初始化和任务里面调用。
- 不可用于时间关键的场景,因为申请的时间不是确定
使用举例:
代码语言:javascript复制TX_BYTE_POOL AppPool;
uint8_t *PoolPtr;
ULONG available;
status = tx_byte_allocate(&AppPool,
(VOID **)&PoolPtr,
77,
TX_NO_WAIT);
if(status == TX_SUCCESS)
{
printf("AppPool内存池申请成功rn");
tx_byte_pool_info_get(&AppPool,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("内存池剩余大小 = %d字节rn", (int)available);
}
22.8 内存池释放函数tx_byte_release
函数原型:
UINT tx_byte_release(VOID *memory_ptr);
函数描述:
用于内存释放。
函数参数:
1、 第1个参数是内存控制块。
2、 返回值
- TX_SUCCESS:(0x00) 成功释放内存块。
- TX_PTR_ERROR:(0x03) 内存区域指针无效。
- TX_CALLER_ERROR:(0x13) 无效的调用。
注意事项:
- 可以在初始化和任务里面调用。
使用举例:
代码语言:javascript复制TX_BYTE_POOL AppPool;
uint8_t *PoolPtr;
ULONG available;
status = tx_byte_release(PoolPtr);
if(status == TX_SUCCESS)
{
printf("AppPool内存池释放成功rn");
tx_byte_pool_info_get(&AppPool,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("内存池剩余大小 = %d字节rn", (int)available);
}
22.9 内存池使用情况获取函数tx_byte_pool_info_get
函数原型:
代码语言:javascript复制UINT tx_byte_pool_info_get(
TX_BYTE_POOL *pool_ptr,
CHAR **name,
ULONG *available,
ULONG *fragments,
TX_THREAD **first_suspended,
ULONG *suspended_count,
TX_BYTE_POOL **next_pool);
函数描述:
用于获取内存池的使用情况。
函数参数:
1、 第1个参数是内存池控制块。
2、 第2个参数是内存池名字地址。
3、 第3个参数是剩余字节数。
4、 第4个参数是内存池中内存片段总数。
5、 第5个参数是内存挂起列表中第1个等待的任务。
6、 第6个参数是内存池中当前挂起的任务数。
7、 第7个参数是下个内存池地址。
8、 返回值
- TX_SUCCESS:(0x00) 信息获取成功。
- TX_POOL_ERROR:(0x03) 无效的内存池控制块地址。
注意事项:
- 可以在初始化,任务,定时器组或中断复位程序里面调用。
- 如果函数的形参设置为TX_NULL,表示用不到。
使用举例:
代码语言:javascript复制TX_BYTE_POOL AppPool;
uint8_t *PoolPtr;
ULONG available;
status = tx_byte_release(PoolPtr);
if(status == TX_SUCCESS)
{
printf("AppPool内存池释放成功rn");
tx_byte_pool_info_get(&AppPool,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("内存池剩余大小 = %d字节rn", (int)available);
}
22.10 实验例程
配套例子:
V7-3017_ThreadX Malloc
实验目的:
- 学习ThreadX动态内存管理
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
========================================================
CPU利用率 = 0.89%
任务执行时间 = 0.586484645s
空闲执行时间 = 85.504470575s
中断执行时间 = 0.173225395s
系统总执行时间 = 86.264180615s
=======================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
5 4092 167 167 App Msp Pro
4 4092 167 167 App Task UserIF
5 4092 167 167 App Task COM
0 1020 191 191 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
System Timer Thread任务:系统定时器任务
2、K2按键按下演示内存块的申请和释放,K3按键按下演示内存池的申请和释放。
3、(1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。
4、默认上电是通过串口打印信息,如果使用RTT打印信息
(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可
#define Enable_RTTViewer 1
(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
RTT打印信息方式(AC5,AC6和IAR):
Embedded Studio仅支持调试状态RTT打印:
由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
22.11 总结
本章节主要为大家讲解了ThreadX内存管理的实现方法,关于嵌入式内存管理的方案还有很多,有兴趣的同学可以查阅相关资料了解一下。