灵动微课堂|使用MM32F3270基于Azure RTOS动态内存管理的应用
简 介 Azure RTOS ThreadX 是 Microsoft 提供的高级工业级实时操作系统 (RTOS)。它是专门为深度嵌入式实时 IoT 应用程序设计的。Azure RTOS ThreadX 提供高级计划、通信、同步、计时器、内存管理和中断管理功能。此外,Azure RTOS ThreadX 具有许多高级功能,包括 picokernel™ 体系结构、preemption-threshold™ 计划、event-chaining™、执行分析、性能指标和系统事件跟踪。Azure RTOS ThreadX 非常易于使用,适用于要求极其苛刻的嵌入式应用程序。Azure RTOS ThreadX 在各种产品(包括消费者设备、医疗电子设备和工业控制设备)上的部署次数已达数十亿次。 具体的介绍和用户指南可以参考: https://docs.microsoft.com/zh-cn/azure/rtos/threadx/ 在前文描述移植基本内核的基础上,该应用手册描述了MM32F3270系列MCU结合Azure RTOS ThreadX内存池的使用,引导用户理解Azure RTOS ThreadX动态内存管理功能。 表 1 适用系列型号 系列 | 芯片型号 | 开发板 | MM32F3270 | MM32F3273G9P | EVB-F3270 |
1 移植应用的准备 1.1 硬件开发板的准备 该移植过程中应用的开发板为MM32的EVB-F3270,板载MM32F3273G9P。
EVB-F3270 (MM32F3273G9P) 的简要参数: Arm Cortex-M3 内核 板载 MM32F3273G9P(LQFP144) USB Host / Device、SPI、I2C 4 x Key、4 x LED I2S Speaker TF-Card Ethernet PHY
1.2 软件的准备 库函数和例程(Lib Samples) 该移植过程中应用的 Firmware 分别为 MM32F3270 库函数和例程,下载地址: https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/
Azure RTOS ThreadX(源码) ThreadX 的源代码已经开放,我们可以从 ThreadX 公共源代码存储库获取 Azure RTOS ThreadX,网址为: https://github.com/azure-rtos/threadx/ 具体的商用使用条件参考Azure的许可证说明: https://www.microsoft.com/en-us/legal/intellectualproperty/tech-licensing/programs?msclkid=f7ab4ff3afa011ec90a79366a52034fa&activetab=pivot1:primaryr11 Microsoft publishes the Azure RTOS source code to GitHub. No license is required to install and use the software for internal development, testing, and evaluation purposes. A license is required to distribute or sell components and devices unless using Azure RTOS licensed hardware. Azure RTOS 何时需要许可证? Microsoft 将 Azure RTOS 源代码发布到 GitHub。安装和使用该软件进行内部开发、测试和评估无需许可证。分发或销售组件和设备需要许可证,除非使用 Azure RTOS 许可的硬件。 ThreadX 安装 可以通过将 GitHub 存储库克隆到本地计算机来安装 ThreadX。下面是用于在 PC 上创建 ThreadX 存储库的克隆的典型语法。 shell复制 或者,也可以使用 GitHub 主页上的“下载”按钮来下载存储库的副本。 下载后的仓库代码目录列表如下:
Azure RTOS ThreadX(源码)支持的开发环境 ThreadX 内核提供好了各种主流硬件平台和软件平台的移植文件,以Cortex_M3为例,可以支持以下六种开发环境:
本次移植过程使用Azure RTOS原有的sample_threadx.c文件为例,稍作修改,演示动态内存管理功能。 2 ThreadX 动态内存管理的应用 该章节介绍动态内存管理相关知识,演示程序可在MM32F3273G9P的EVB-F3270上运行。此示例在文件 main_malloc_demo.c 中实现,旨在说明如何在嵌入式多线程环境中使用动态内存管理功能。 2.1 动态内存管理 2.1.1 内存块池 在实时应用程序中,采用快速且确定的方式分配内存始终是一项挑战。考虑到这一点,ThreadX 提供了创建和管理多个固定大小的内存块池的功能。 由于内存块池由固定大小的块组成,因此永远不会出现任何碎片问题。当然,碎片会导致出现本质上不确定的行为。此外,分配和释放固定大小内存块所需的时间与简单的链接列表操作所需的时间相当。另外,还可以在可用列表的开头完成内存块分配和取消分配。这可以提供最快的链接列表处理速度,并且有助于将实际的内存块保存在缓存中。 缺乏灵活性是固定大小内存池的主要缺点。池的块大小必须足够大,才能处理其用户最坏情况下的内存需求。当然,如果对同一个池发出了许多大小不同的内存请求,则可能会浪费内存。一种可能的解决方案是创建多个不同的内存块池,这些池包含不同大小的内存块。 每个内存块池都是一个公用资源。ThreadX 对如何使用池没有任何限制。 2.1.1 创建内存块池 内存块池由应用程序线程在初始化期间或运行时创建。应用程序中内存块池的数量没有限制。 2.1.3 内存块大小 如前所述,内存块池包含许多固定大小的块。块大小(以字节为单位)在创建池时指定。 ThreadX 为池中的每个内存块增加了少量开销(C 指针的大小)。此外,ThreadX 可能需要填充块大小,从而确保每个内存块的开头能够正确对齐。 2.1.4 池容量 池中的内存块数是在创建过程中提供的内存区域的块大小和总字节数的函数。池容量的计算方法是将块大小(包括填充和指针开销字节)除以提供的内存区域的总字节数。 2.1.5 池的内存区域 如前所述,块池的内存区域在创建时指定。与 ThreadX 中的其他内存区域一样,该区域可以位于目标地址空间的任何位置。 这是一项重要的功能,因为它提供了相当大的灵活性。例如,假设某个通信产品有一个用于 I/O 的高速内存区域。将此内存区域设置为 ThreadX 内存块池,即可轻松对其进行管理。 2.1.6 线程挂起 在等待空池中的内存块时,应用程序线程可能会挂起。当块返回到池时,将为挂起的线程提供此块,并恢复线程。 如果同一内存块池中挂起多个线程,这些线程将按挂起的顺序 (FIFO) 恢复。 不过,如果应用程序在取消线程挂起的块释放调用之前调用 tx_block_pool_prioritize,还可以恢复优先级。块池设置优先级服务将优先级最高的线程置于挂起列表的前面,让所有其他挂起的线程采用相同的 FIFO 顺序。 2.1.7 运行时块池性能信息 ThreadX 提供可选的运行时块池性能信息。如果 ThreadX 库和应用程序是在定义 TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO 的情况下生成的,ThreadX 会累积以下信息。 整个系统的总数: 已分配的块数 已释放的块数 分配挂起数 分配超时数 每个块池的总数: 已分配的块数 已释放的块数 分配挂起数 分配超时数 此信息在运行时通过 tx_block_pool_performance_info_get和 tx_block_pool_performance_system_info_get服务提供。块池性能信息在确定应用程序是否正常运行时非常有用。此信息对于优化应用程序也很有用。例如,“分配挂起数”相对较高可能表明块池太小。 2.1.8 内存块池控制块 TX_BLOCK_POOL 每个内存块池的特征都可在其控制块中找到。该控制块包含诸如可用的内存块数和内存池块大小等信息。此结构在 tx_api.h文件中定义。 池控制块也可以位于内存中的任意位置,但最常见的是通过在任何函数的作用域外部定义该控件块来使其成为全局结构。 2.1.9 覆盖内存块 务必确保已分配内存块的用户不会在其边界之外写入。如果发生这种情况,则会损坏其相邻的内存区域(通常是后续区域)。结果不可预测,且对于应用程序来说通常很严重。 2.1.10 内存字节池 内存字节池由应用程序线程在初始化期间或运行时创建。应用程序中内存字节池的数量没有限制。 2.1.12 池容量 内存字节池中可分配的字节数略小于创建期间指定的字节数。这是因为可用内存区域的管理带来了一些开销。池中的每个可用内存块都需要相当于两个 C 指针的开销。此外,创建的池包含两个块:一个较大的可用块和在内存区域末端永久分配的一个较小的块。这个分配块用于提高分配算法的性能。这样就无需在合并期间持续检查池区域末端。 在运行时,池中的开销通常会增加。如果分配奇数字节数,系统会加以填充,以确保正确对齐下一个内存块。此外,随着池变得更加零碎,开销也会增加。 2.1.13 池的内存区域 内存字节池的内存区域在创建过程中指定。与 ThreadX 中的其他内存区域一样,该区域可以位于目标地址空间的任何位置。这是一项重要的功能,因为它提供了相当大的灵活性。例如,如果目标硬件有高速内存区域和低速内存区域,用户可以通过在每个区域中创建池来管理这两个区域的内存分配。 2.1.14 线程挂起 在等待池中的内存字节时,应用程序线程可能会挂起。当有足够的连续内存可用时,将为已挂起的线程提供其请求的内存,并且恢复线程。 如果同一内存字节池中挂起多个线程,则按这些线程挂起的顺序 (FIFO) 为其提供内存(恢复)。 不过,如果应用程序在信号灯发出取消线程挂起的字节释放调用之前调用 tx_byte_pool_prioritize,还可以恢复优先级。字节池设置优先级服务将最高优先级的线程置于挂起列表的前面,让所有其他挂起的线程采用相同的 FIFO 顺序。 2.1.15 运行时字节池性能信息 ThreadX 提供可选的运行时字节池性能信息。如果 ThreadX 库和应用程序是在定义 TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO 的情况下生成的,ThreadX 会累积以下信息。 整个系统的总数: 分配数 版本 搜索的片段数 合并的片段数 创建的片段数 分配挂起数 分配超时数 每个字节池的总数: 分配数 版本 搜索的片段数 合并的片段数 创建的片段数 分配挂起数 分配超时数 此信息在运行时通过 tx_byte_pool_performance_info_get和 tx_byte_pool_performance_system_info_get 服务提供。字节池性能信息在确定应用程序是否正常运行时非常有用。此信息对于优化应用程序也很有用。例如,“分配挂起数”相对较高可能表明字节池太小。 2.1.16 内存字节池控制块 TX_BYTE_POOL 每个内存字节池的特征都可在其控制块中找到。该控制块包含诸如池中可用的字节数等有用的信息。此结构在 tx_api.h 文件中定义。 池控制块也可以位于内存中的任意位置,但最常见的是通过在任何函数的作用域外部定义该控件块来使其成为全局结构。 2.1.17 非确定性行为 尽管内存字节池提供了最灵活的内存分配,但这些池也受一些非确定性行为的影响。例如,内存字节池可能有 2,000 字节的可用内存,但可能无法满足 1,000 字节的分配请求。这是因为无法保证有多少可用字节是连续的。即使存在 1,000 字节可用块,也不能保证找到此块需要多长时间。完全有可能需要搜索整个内存池来查找这个 1,000 字节块。 由于内存字节池的不确定性行为,通常应避免在需要确定性实时行为的区域中使用内存字节服务。许多应用程序在初始化或运行时配置期间预先分配其所需的内存。 2.1.18 覆盖内存块 务必确保已分配内存的用户不会在其边界之外写入。如果发生这种情况,则会损坏其相邻的内存区域(通常是后续区域)。结果不可预测,且对于程序执行来说通常是灾难性的。 2.2 Azure ThreadX 动态内存管理的相关函数 tx_block_allocate分配固定大小的内存块UINT tx_block_allocate(
TX_BLOCK_POOL *pool_ptr,
VOID **block_ptr,
ULONG wait_option);
说明 此服务从指定的内存池中分配固定大小的内存块。内存块的实际大小是在创建内存池的过程中确定的参数。 pool_ptr:指向之前创建的内存块池的指针。 block_ptr:指向目标块指针的指针。成功分配时,已分配内存块的地址就位于此参数所指向的位置。 wait_option:定义此服务在没有可用的内存块时的行为方式。等待选项的定义如下: TX_NO_WAIT (0x00000000) 如果选择 TX_NO_WAIT,则无论此服务是否成功,都会导致立即从此服务返回 。如果从非线程(例如初始化、计时器或 ISR)调用服务,则这是唯一有效的选项。 TX_WAIT_FOREVER (0xFFFFFFF) 选择 TX_WAIT_FOREVER 会导致发出调用的线程无限期挂起,直到内存块可用为止 。 超时值(0x00000001 至 0xFFFFFFFE) 如果选择一个数值(1 到 0xFFFFFFFE),则会指定在等待内存块时发出调用的线程保持挂起的最大计时器时钟周期数。 返回值 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_BLOCK_POOL my_pool;
unsigned char *memory_ptr;
UINT status;
/* Allocate a memory block from my_pool. Assume that the pool has
already been created with a call to tx_block_pool_create. */
status = tx_block_allocate(&my_pool, (VOID **) &memory_ptr,
TX_NO_WAIT);
/* If status equals TX_SUCCESS, memory_ptr contains the address of
the allocated block of memory. */
另请参阅 tx_block_pool_create tx_block_pool_delete tx_block_pool_info_get tx_block_pool_performance_info_get tx_block_pool_performance_system_info_get tx_block_pool_prioritize tx_block_release
tx_byte_allocate tx_byte_pool_create tx_byte_pool_delete tx_byte_pool_info_get tx_byte_pool_performance_info_get tx_byte_pool_performance_system_info_get tx_byte_pool_prioritize tx_byte_release 具体函数的中文说明可以参考: https://docs.microsoft.com/zh-cn/azure/rtos/threadx/chapter4 具体函数的英文说明可以参考: https://docs.microsoft.com/en-us/azure/rtos/threadx/threadx-smp/chapter4
2.3 动态内存管理的应用演示 2.3.1 工程目录的建立 打开目标工程文件夹“MM32F3270Project”:
移除原有样例.c 文件sample_threadx.c:
参考sample_threadx.c建立main_malloc_demo.c文件,并添加hardware目录中的led.c、key.c到工程项目中。
注意: 需要在delay.c中配置USE_SYSTICK_DELAY 为 0。 #define USE_SYSTICK_DELAY 0
3 ThreadX 的内存管理应用 创建如下几个任务: LED1闪烁指示当前系统运行。 按键(K1、K2、K3、K4)按下,对应的事件标志置位: 获取事件标志,执行处理程序: K1按下,从指定的内存块池中申请固定大小的内存块 K2按下,将以前分配的内存块释放回其关联的内存池 K3按下,从指定的内存字节池中分配指定的字节数 K4按下,将以前分配的内存字节数释放回其关联的内存池 打印内存池信息 3.1 代码实现 下载调试默认会运行到main()函数,如下为全部实现的代码。 Demo演示代码 /* This is a small demo of the high-performance ThreadX kernel. It includes examples of six
threads of different priorities, using a message queue, semaphore, and an event flags group. */
#include "tx_api.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "uart.h"
#define DEMO_STACK_SIZE 1024
#define THREAD0_PRIORITY 1
#define THREAD0_PREEMPTION_THRESHOLD 1
#define THREAD1_PRIORITY 2
#define THREAD1_PREEMPTION_THRESHOLD 2
#define THREAD5_PRIORITY 4
#define THREAD5_PREEMPTION_THRESHOLD 4
//#define THREAD5_PREEMPTION_THRESHOLD_NEW 0
#define BIT_0 ((ULONG)0x0000001)
#define BIT_1 ((ULONG)0x0000002)
#define BIT_2 ((ULONG)0x0000004)
#define BIT_3 ((ULONG)0x0000008)
#define BIT_ALL (BIT_0|BIT_1|BIT_2|BIT_3)
/* Define the ThreadX object control blocks... */
TX_THREAD thread_0;
TX_THREAD thread_1;
TX_THREAD thread_5;
TX_EVENT_FLAGS_GROUP EventGroup;
TX_BLOCK_POOL MyBlock;
TX_BYTE_POOL MyByte;
/* Define the counters used in the demo application... */
ULONG thread_0_counter;
ULONG thread_1_counter;
ULONG thread_5_counter;
/* Define the thread stacks. */
UCHAR thread_0_stack[DEMO_STACK_SIZE];
UCHAR thread_1_stack[DEMO_STACK_SIZE];
UCHAR thread_5_stack[DEMO_STACK_SIZE];
/* Define thread prototypes. */
void thread_0_entry(ULONG thread_input);
void thread_1_entry(ULONG thread_input);
void thread_5_entry(ULONG thread_input);
volatile unsigned int bootloop;
uint32_t MyBlockBuf[1024];
uint32_t MyByteBuf[1024];
void System_Init(void);
void AppThreadCreate(void);
void AppModuleCreate(void);
/* Define main entry point. */
int main()
{
System_Init();
/* Enter the ThreadX kernel. */
tx_kernel_enter();
}
/* Define what the initial system looks like. */
void tx_application_define(void* first_unused_memory)
{
AppThreadCreate();
AppModuleCreate();
}
void System_Init(void)
{
DELAY_Init();//can not use systick
LED_Init();
KEY_Init();
CONSOLE_Init(115200);
printf("!!! Start !!!\r\n");
}
void AppThreadCreate(void)
{
/* Create thread 0. */
tx_thread_create(
&thread_0,
"thread 0",
thread_0_entry,
0,
thread_0_stack,
DEMO_STACK_SIZE,
THREAD0_PRIORITY,
THREAD0_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE,
TX_AUTO_START);
/* Create thread 1. */
tx_thread_create(
&thread_1,
"thread 1",
thread_1_entry,
0,
thread_1_stack,
DEMO_STACK_SIZE,
THREAD1_PRIORITY,
THREAD1_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE,
TX_AUTO_START);
/* Create thread 5. */
tx_thread_create(
&thread_5,
"thread 5",
thread_5_entry,
5,
thread_5_stack,
DEMO_STACK_SIZE,
THREAD5_PRIORITY,
THREAD5_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE,
TX_AUTO_START);
}
void AppModuleCreate(void)
{
/* Creates a memory block for applying for a fixed-size memory unit */
tx_block_pool_create(&MyBlock,
"MyBlock",
4, /* The size of the memory unit */
(VOID *)MyBlockBuf, /* Memory block address, ensure 4-byte alignment */
sizeof(MyBlockBuf));/* Memory block size, in bytes */
/* Create a memory pool */
tx_byte_pool_create(&MyByte,
"MyByte",
(VOID *)MyByteBuf, /* Memory pool address, ensure 4-byte alignment */
sizeof(MyByteBuf)); /* Memory pool size */
/* Create an event flag group */
tx_event_flags_create(&EventGroup, "EventGroupName");
}
/* Define the test threads. */
void thread_0_entry(ULONG thread_input)
{
/* This thread simply controls LED flashing to indicate that the system is running */
while(1)
{
/* Increment the thread counter. */
thread_0_counter++;
LED1_TOGGLE();
/* Sleep for 300 ticks. */
tx_thread_sleep(300);
}
}
void thread_1_entry(ULONG thread_input)
{
UCHAR *BlockPtr;
UCHAR *BytePtr;
ULONG available;
ULONG actual_events;
UINT status;
while(1)
{
/* Increment the thread counter. */
thread_1_counter++;
status = tx_event_flags_get(&EventGroup,
BIT_ALL,
TX_OR_CLEAR,
&actual_events,
TX_WAIT_FOREVER);
if(status == TX_SUCCESS)
{
switch(actual_events)
{
case BIT_0:
/* Apply for memory blocks of 4 bytes each time */
status = tx_block_allocate(&MyBlock,
(VOID **)&BlockPtr,
TX_NO_WAIT);
if(status == TX_SUCCESS)
{
printf("Succeeded in applying for a memory block. \r\n");
tx_block_pool_info_get(&MyBlock,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("Number of blocks available in MyBlock = %d\r\n", (int)available);
}
break;
case BIT_1:
status = tx_block_release(BlockPtr);
if(status == TX_SUCCESS)
{
printf("The memory block was released successfully.\r\n");
tx_block_pool_info_get(&MyBlock,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("Number of blocks available in MyBlock = %d\r\n", (int)available);
}
break;
case BIT_2:
/* Apply for a memory byte pool, specifying 100 bytes */
status = tx_byte_allocate(&MyByte,
(VOID **)&BytePtr,
100,
TX_NO_WAIT);
if(status == TX_SUCCESS)
{
printf("Succeeded in applying for the memory byte pool.\r\n");
tx_byte_pool_info_get(&MyByte,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("The available MyByte size = %d bytes. \r\n", (int)available);
}
break;
case BIT_3:
status = tx_byte_release(BytePtr);
if(status == TX_SUCCESS)
{
printf("The memory byte pool was released successfully. \r\n");
tx_byte_pool_info_get(&MyByte,
TX_NULL,
&available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("The available MyByte size = %d bytes. \r\n", (int)available);
}
break;
default:
break;
}
}
}
}
void thread_5_entry(ULONG thread_input)
{
UCHAR t = 0;
/* This thread simply scan button is pressed to send the semaphore. */
while(1)
{
/* Increment the thread counter. */
thread_5_counter++;
t = KEY_Scan(0);
if(KEY1_PRES == t)
{
//LED1_TOGGLE();
tx_event_flags_set(&EventGroup, BIT_0, TX_OR);
}
else if(KEY2_PRES == t)
{
//LED2_TOGGLE();
tx_event_flags_set(&EventGroup, BIT_1, TX_OR);
}
else if(KEY3_PRES == t)
{
//LED3_TOGGLE();
tx_event_flags_set(&EventGroup, BIT_2, TX_OR);
}
else if(KEY4_PRES == t)
{
//LED4_TOGGLE();
tx_event_flags_set(&EventGroup, BIT_3, TX_OR);
}
else
{
tx_thread_sleep(10);
}
}
}
3.2 下载与调试 运行程序,板载LED1闪烁。观察串口调试助手,依次按下K1、K2、K3、K4键,串口打印信息:
创建事件标志组初始化为零,用于任务同步。任务5执行按键扫描,当按键按下时通过tx_event_flags_set设置事件标志。任务1通过tx_event_flags_get用于检索事件标志,执行对应的处理程序,其中K1进行内存块申请,K2进行内存块释放,K3用于内存字节申请,K4用于内存字节释放,观测串口打印信息,Demo演示成功。 按下K3从MyByte内存池中申请分配了100个字节,还有3980字节可用,而按下K4释放后变成了4088字节可用,前后差值为108个字节,为什么多了8个字节?感兴趣的同学可以思考一下,找找答案!
4 小结 Azure RTOS ThreadX提供内存池能够以快速且确定的方式分配内存,结合MM32F3270的强大性能,可以实现Azure RTOS广泛的应用场景。
|