STM32F1与STM32CubeIDE编程实例-ThreadX的内存管理

ThreadX的内存管理

文章目录

在前面的线程服务文章中提到线程的栈有几种方式:
字节池
块池
数组、或者简单地指定
内存中的物理起始地址。在示例中,我们使用数组作为线程的栈。这种方式简单,但是在实际应用不推荐使用。因为从内存管理角度,它经常是不可取的,而且非常不灵活。本文重点介绍两种具有很大灵活性的 ThreadX 内存管理资源:
**字节内存池(Memory Byte Pools)内存块池(Memory Block Pool)**。

字节内存池是一个连续的字节块。 在这样的池中,可以使用和重用任何大小的字节组(取决于池的总大小)。 字节内存池很灵活,可以用于线程栈和其他需要内存的资源。 但是,这种灵活性会导致一些问题,例如字节内存池的碎片化,因为使用了不同大小的字节组。

内存块池也是一个连续的字节块,但它被组织成一组固定大小的内存块。 因此,从内存块池中使用或重用的内存量总是相同的——一个固定大小的内存块的大小。 不存在碎片问题,分配和释放内存块速度很快。 一般来说,内存块池的使用优于字节内存池。

1、字节内存池(Memory Byte Pools)

1.1 字节内存池介绍

字节内存池类似于标准C语言的堆。与 C语言的堆相比,ThreadX 应用程序可能使用多个字节内存池。 此外,线程可以在字节内存池上挂起,直到请求的内存可用。

字节内存池的分配类似于传统的 malloc 调用,其中包括所需的内存量(以字节为单位)。 ThreadX 以优先适应(fit-first)方式从字节内存池中分配内存,即,它使用第一个足够大的空闲内存块来满足请求。 ThreadX 将此块中多余的内存转换为新块并将其放回空闲内存列表中。 这个过程称为**分片(Fragmentation)**。

当 ThreadX 为足够大的空闲内存块执行后续分配搜索时,它会将相邻的空闲内存块合并在一起。 这个过程称为**碎片整理(Defragmentation)**。

每个字节内存池都是公共资源; ThreadX 对字节内存池的使用方式没有任何限制。应用程序可以在初始化期间或运行时创建字节内存池。 应用程序可以使用的字节内存池的数量没有明确的限制。

字节内存池中可分配的字节数略少于创建期间指定的字节数。 这是因为空闲内存区域的管理引入了一些开销。 池中的每个空闲内存块都需要相当于两个 C 指针的开销。 此外,在创建池时,ThreadX 会自动将其分成两个块,一个大的空闲块和一个小的永久分配块,位于内存区域的末尾。 这个分配的结束块用于提高分配算法的性能。 它消除了在合并过程中不断检查池区域末端的需要。 在运行时,池中的开销通常会增加。 这部分是因为当分配奇数字节时,ThreadX 会填充该块以确保下一个内存块的正确对齐。 此外,随着池变得更加分散,开销也会增加。

字节内存池的内存区域在创建期间指定。 与其他内存区域一样,它可以位于目标地址空间中的任何位置。 这是一个重要功能,因为它为应用程序提供了相当大的灵活性。 例如,如果目标硬件有一个高速内存区域和一个低速内存区域,用户可以通过在每个区域中创建一个池来管理这两个区域的内存分配。

应用程序线程可以在等待来自池的内存字节时挂起。 当有足够的连续内存可用时,挂起的线程会收到它们请求的内存并恢复。 如果多个线程在同一个字节内存池中挂起,ThreadX 会给它们内存并按照它们在挂起线程列表(通常是 FIFO)上出现的顺序恢复它们。 但是,应用程序可以通过在解除线程暂停的字节释放调用之前调用 tx_byte_pool_prioritize 来导致暂停线程的优先级恢复。 字节池优先级服务将最高优先级的线程放在挂起列表的最前面,同时让所有其他挂起的线程保持相同的 FIFO 顺序。

1.2 字节内存池控制块(Memory Byte Pool Control Block)

每个字节内存池的特性都可以在其控制块中找到。 它包含有用的信息,例如池中的可用字节数。 字节内存池控制块可以位于内存中的任何位置,但最常见的是通过在任何函数范围之外定义控制块来使控制块成为全局结构。其结构定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
typedef struct TX\_BYTE\_POOL\_STRUCT
{

/\* Define the byte pool ID used for error checking. \*/
ULONG tx_byte_pool_id;

/\* Define the byte pool's name. \*/
CHAR \*tx_byte_pool_name;

/\* Define the number of available bytes in the pool. \*/
ULONG tx_byte_pool_available;

/\* Define the number of fragments in the pool. \*/
UINT tx_byte_pool_fragments;

/\* Define the head pointer of byte pool. \*/
UCHAR \*tx_byte_pool_list;

/\* Define the search pointer used for initial searching for memory
in a byte pool. \*/
UCHAR \*tx_byte_pool_search;

/\* Save the start address of the byte pool's memory area. \*/
UCHAR \*tx_byte_pool_start;

/\* Save the byte pool's size in bytes. \*/
ULONG tx_byte_pool_size;

/\* This is used to mark the owner of the byte memory pool during
a search. If this value changes during the search, the local search
pointer must be reset. \*/
struct TX\_THREAD\_STRUCT
\*tx_byte_pool_owner;
/\* Define the byte pool suspension list head along with a count of
how many threads are suspended. \*/
struct TX\_THREAD\_STRUCT
\*tx_byte_pool_suspension_list;
UINT tx_byte_pool_suspended_count;

...

}TX_BYTE_POOL;

大多数情况下,开发者可以忽略字节内存池控制块的内容。 但是,有几个字段在调试过程中可能很有用,例如可用字节数、片段数以及在此字节内存池上挂起的线程数。

1.3 字节内存池的陷阱

尽管字节内存池提供了最灵活的内存分配,但它们也存在一些不确定的行为。 例如,字节内存池可能有 2,000 字节的可用内存,但无法满足甚至 1,000 字节的分配请求。 这是因为无法保证有多少空闲字节是连续的。 即使存在 1,000 字节的空闲块,也无法保证找到该块可能需要多长时间。 分配服务很可能必须搜索整个内存池才能找到 1,000 字节的块。 由于这个问题,通常最好避免在需要确定性实时行为的区域中使用内存字节服务。 许多此类应用程序在初始化或运行时配置期间预先分配其所需的内存。 另一种选择是使用内存块池。

用户不得写入字节池分配内存的边界之外。 如果发生这种情况,损坏会发生在相邻(通常是随后的)内存区域中。 结果是不可预测的,而且往往是灾难性的。

1.4 字节内存池服务

1.4.1 创建字节内存池

函数tx_byte_pool_create用于创建字节内存池。其原型如下:

UINT tx_byte_pool_create(TX_BYTE_POOL *pool_ptr,CHAR *name_ptr,VOID *pool_start, ULONG pool_size);

  • pool_ptr:指向字节内存池结构的指针。
  • name:字节内存池名称
  • pool_start:字节内存池基地址,一般使用数组,某些情况下,可以直接指定内存地址。
  • pool_size:字节内存池大小。

如下面示例:

1
2
3
4
5
6
7
8
9
10
// 10k应用程序内存
#define APP\_MEMORY\_SIZE 1024 \* 10
// 字节内存汉子结构
static TX_BYTE_POOL __s_app_statc_pool;
// 内存池数组
static uint8\_t __app_memory_area[APP_MEMORY_SIZE];

UINT status;
status = tx\_byte\_pool\_create(&__s_app_statc_pool,"app\_byte\_pool",__app_memory_area,APP_MEMORY_SIZE);

1.4.2 字节内存池分配

在创建字节内存池完成之后,就可以通过调用tx_byte_allocate函数来分配内存。其原型如下:

UINT tx_byte_allocate(TX_BYTE_POOL *pool_ptr,VOID **memory_ptr,ULONG memory_size, ULONG wait_option);

  • pool_ptr:指向字节内存池的指针。
  • memory_ptr:指向用于存放分配的字节指针的指针
  • memory_size:分配大小
  • wait_operation:分配操作类型

函数返回如下结果:

  • TX_POOL_ERROR
  • TX_PTR_ERROR
  • TX_WAIT_ERROR
  • TX_CALLER_ERROR
  • TX_SIZE_ERROR
  • TX_SUCCESS

示例如下:

1
2
3
4
5
uint8\_t \*task_stack_pointer = NULL;
#define TASK\_STACK\_SIZE 512
uint16\_t status;
status = tx\_byte\_allocate(&__s_app_statc_pool, (VOID \*\*) &task_stack_pointer, TASK_STACK_SIZE, TX_NO_WAIT);

当变量status的值为TX_SUCCESS时,表示分配成功。创建成功后,即可用于线程创建。

1.4.3 删除字节内存池

可以使用 tx_byte_pool_delete函数删除字节内存池。所有在字节池上挂起的线程都使用 TX_DELETED 状态码恢复。需要注意的是,在调用此函数时,正在删除的字节池或与之关联的内存不应处于使用状态。其原型如下:

UINT tx_byte_pool_delete(TX_BYTE_POOL *pool_ptr);

例如:

1
2
3
4
5
TX_BYTE_POOL __s_app_statc_pool;
UINT status;
...
status = tx\_byte\_pool\_delete(&__s_app_statc_pool);

当变量的值为TX_SUCCESS时,表示删除成功。

1.4.4 检索字节内存池信息

ThreadX提供了三个函数用于检索字节内存池信息:

  • tx_byte_pool_info_get:从字节内存池控制块中检索信息子集。此信息提供了特定时刻(即调用服务时)的**快照(Snapshot)**。
  • tx_byte_pool_performance_info_get:提供特定字节内存池的信息摘要,直至调用该服务。
  • tx_byte_pool_performance_system_info_get:检索系统中所有字节内存池的信息摘要,直到调用服务。

后面两个服务用于基于收集的运行时性能数据的摘要信息。而tx_byte_pool_info_get 服务检索有关字节内存池的各种信息。检索到的信息包括字节池名称、可用字节数、内存碎片数、该字节池挂起列表中第一个线程的位置、该字节池上当前挂起的线程数,以及下一个创建的字节内存池的位置。

例如:

1
2
3
4
5
6
7
8
9
10
11
CHAR \*name;
ULONG available;
ULONG fragments;
TX_THREAD \*first_suspended;
ULONG suspended_count;
TX_BYTE_POOL \*next_pool;
UINT status;


status = tx\_byte\_pool\_info\_get(&__s_app_statc_pool, &name,&available, &fragments,&first_suspended, &suspended_count,&next_pool);

如果变量status返回的值为TX_SUCCESS,则表示检索成功。

1.4.5 对字节内存池挂起列表进行优先级排序

当一个线程因为等待一个字节内存池而被挂起时,它会以先进先出的方式被放入线程挂起列表中。 当字节内存池重新获得足够的内存量时,挂起列表中的第一个线程(无论优先级如何)都会获得从该字节内存池分配字节的机会。

tx_byte_pool_prioritize 服务将因特定字节内存池的所有权而挂起的最高优先级线程放在挂起列表的前面。 所有其他线程保持与它们被挂起时相同的 FIFO 顺序。其原型如下:

UINT tx_byte_pool_prioritize(TX_BYTE_POOL *pool_ptr);

简单示例如下:

1
2
3
4
5
TX_BYTE_POOL my_pool;
UINT status;

status = tx\_byte\_pool\_prioritize(&__s_app_statc_pool);

如果 status 等于 TX_SUCCESS,则最高优先级的挂起线程位于列表的前面。 如果有足够的内存来满足它的请求,下一个 tx_byte_release 调用将唤醒这个线程。

1.4.6 释放内存到字节池

tx_byte_release 服务将先前分配的内存区域释放回其关联的池。 如果一个或多个线程在该池中挂起,则每个挂起的线程都会收到它请求的内存并恢复——直到池的内存耗尽或没有更多挂起的线程。 这个为挂起线程分配内存的过程总是从挂起列表上的第一个线程开始。其原型如下:

UINT tx_byte_release(VOID *memory_ptr);

简单示例如下:

1
2
3
4
5
unsigned char \*memory_ptr;
UINT status;
...
status = tx\_byte\_release((VOID \*) memory_ptr);

如果变量status的值为TX_SUCCESS,那么memory_ptr指向的内存块已经被返回到字节内存池。

1.4.7 字节内存池内部管理

当使用 TX_BYTE_POOL 数据类型声明字节池时,会创建一个字节池控制块,并将该控制块添加到双向循环链表中,如下图所示:

在这里插入图片描述

名称为tx_byte_pool_created_ptr的指针指向列表中的第一个控制块。 有关字节池属性、值和其他指针,请参见字节池控制块中的字段。

创建时的字节内存池的组织如下图所示:

在这里插入图片描述

在开始时,所有可用的内存空间都被组织成一个连续的字节块。然而,从这个字节池中的每个连续分配都可以潜在地细分可用的内存空间。如下图所示第一次内存分配后的字节内存池:

在这里插入图片描述

2、内存块池(Memory Block Pools)

2.1 内存块池介绍

在实时应用程序中,以快速和确定的方式分配内存是必不可少的。 这可以通过创建和管理多个固定大小的**内存块池(Memory Block Pools)**来实现。

由于内存块池由固定大小的块组成,所以使用它们不涉及碎片问题。这一点是非常重要的,因为碎片化会导致本质上不确定的行为。 此外,分配和释放固定大小的块很快——所需时间与简单的链表操作相当。 此外,分配服务在从内存块池中分配和解除分配时不必搜索块列表——它总是在可用列表的开头进行分配和解除分配。 这提供了最快的链表处理,并可能有助于将当前使用的内存块保留在缓存中。

缺乏灵活性是固定大小内存池的主要缺点。 池的块大小必须足够大,以处理其用户的最坏情况下的内存需求。从同一个池中发出许多不同大小的内存请求可能会导致内存浪费。 一种可能的解决方案是创建几个不同的内存块池,其中包含不同大小的内存块。

每个内存块池都是公共资源。 ThreadX 对池的使用方式没有任何限制。 应用程序可以在初始化期间或在运行时从应用程序线程内创建内存块池。 应用程序可以使用的内存块池的数量没有限制。

如前所述,内存块池包含许多固定大小的块。 块的大小(以字节为单位)在创建池期间指定。 池中的每个内存块都会产生少量开销,即C 指针的大小。 此外,ThreadX 可能会填充块大小,以使每个内存块的开头保持正确对齐。

池中内存块的数量取决于块大小和创建期间提供的内存区域中的总字节数。 要计算池的容量(可用的块数),除以块大小(包括填充和
指针开销字节)到提供的内存区域中的总字节数。

内存块池的内存区域是在创建时指定的,可以位于目标地址空间中的任何位置。 这是一个重要功能,因为它为应用程序提供了相当大的灵活性。 例如,假设通信产品具有用于 I/O 的高速存储区。 可以通过将其设置为内存块池来轻松管理此内存区域。

应用程序线程可以在等待来自空池的内存块时挂起。当一个块返回到池中时,ThreadX 将该块交给挂起的线程并恢复线程。 如果多个线程在同一个内存块池中挂起,ThreadX 会按照它们在挂起线程列表(通常是 FIFO)上出现的顺序来恢复它们。

但是,应用程序也可以导致最高优先级的线程被恢复。 为此,应用程序在解除线程挂起的块释放调用之前调用tx_block_pool_prioritize。 块池优先级服务将最高优先级的线程放在挂起列表的最前面,同时让所有其他挂起的线程保持相同的 FIFO 顺序。

2.2 内存块池控制块(Memory Block Pool Control Block)

每个内存块池的特性都在它的控制块中找到。它包含诸如块大小和剩余内存块数等信息。 内存池控制块可以位于内存中的任何位置,但它们通常被定义为任何函数范围之外的全局结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
typedef struct TX\_BLOCK\_POOL\_STRUCT
{

/\* Define the block pool ID used for error checking. \*/
ULONG tx_block_pool_id;

/\* Define the block pool's name. \*/
CHAR \*tx_block_pool_name;

/\* Define the number of available memory blocks in the pool. \*/
UINT tx_block_pool_available;

/\* Save the initial number of blocks. \*/
UINT tx_block_pool_total;

/\* Define the head pointer of the available block pool. \*/
UCHAR \*tx_block_pool_available_list;

/\* Save the start address of the block pool's memory area. \*/
UCHAR \*tx_block_pool_start;

/\* Save the block pool's size in bytes. \*/
ULONG tx_block_pool_size;

/\* Save the individual memory block size - rounded for alignment. \*/
UINT tx_block_pool_block_size;

/\* Define the block pool suspension list head along with a count of
how many threads are suspended. \*/
struct TX\_THREAD\_STRUCT
\*tx_block_pool_suspension_list;
UINT tx_block_pool_suspended_count;

/\* Define the created list next and previous pointers. \*/
struct TX\_BLOCK\_POOL\_STRUCT
\*tx_block_pool_created_next,
\*tx_block_pool_created_previous;

#ifdef TX\_BLOCK\_POOL\_ENABLE\_PERFORMANCE\_INFO

/\* Define the number of block allocates. \*/
ULONG tx_block_pool_performance_allocate_count;

/\* Define the number of block releases. \*/
ULONG tx_block_pool_performance_release_count;

/\* Define the number of block pool suspensions. \*/
ULONG tx_block_pool_performance_suspension_count;

/\* Define the number of block pool timeouts. \*/
ULONG tx_block_pool_performance_timeout_count;
#endif

/\* Define the port extension in the block pool control block. This
is typically defined to whitespace in tx\_port.h. \*/
TX_BLOCK_POOL_EXTENSION

} TX_BLOCK_POOL;

已分配内存块的用户不得在其边界之外写入。 如果发生这种情况,损坏会发生在相邻(通常是随后的)内存区域中。 结果是不可预测的,而且往往是灾难性的。

大多数情况下,开发者可以忽略Memory Block Pool Control Block的内容。 但是,有几个字段在调试过程中可能有用,例如可用块数、初始块数、实际块大小、块池中的总字节数以及在此上挂起的线程数 内存块池。

2.3 内存块池服务

2.3.1 创建内存块池

内存块池使用TX_BLOCK_POOL数据类型声明,并使用tx_block_pool_create服务定义。 定义内存块池时,需要指定其控制块、内存块池的名称、内存块池的地址、可用字节数。其原型如下:

UINT tx_block_pool_create(TX_BLOCK_POOL *pool_ptr,CHAR *name_ptr, ULONG block_size,VOID *pool_start, ULONG pool_size);

其中:

  • pool_ptr:为指向内存块池TX_BLOCK_POOL的指针
  • name:内存块池名称
  • block_size:块大小(以字节为单位)
  • pool_start:内存块池基地址
  • pool_size:内存块池大小

如果创建成功,则返回TX_SUCCESS。

简单示例如下:

1
2
3
4
5
TX_BLOCK_POOL my_pool;
UINT status;

status = tx\_block\_pool\_create(&my_pool, "my\_pool\_name",50, (VOID \*) 0x100000, 1000);

内存块的数量计算方法如下:













+

(

s

i

z

e

o

f

(

v

o

i

d

)

)

内存块数量 = \frac{可用字节总数}{每块大小+(sizeof(void*))}

内存块数量=每块大小+(sizeof(void∗))可用字节总数​

在上述示例中,内存块数量如下:













1000

50

+

4

18

内存块数量 = \frac{1000}{50 + 4} \simeq 18块

内存块数量=50+41000​≃18块

使用前面的公式可以避免浪费内存块池中的空间。 请务必仔细估计所需的块大小和池可用的内存量。

2.3.2 分配内存块池

在成功创建内存块池后,可以开始在各种应用程序中使用它。 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_SUCCESS。

示例如下:

1
2
3
4
5
6
TX_BLOCK_POOL my_pool;
unsigned char \*memory_ptr;
UINT status;
...
status = tx\_block\_allocate(&my_pool, (VOID \*\*) &memory_ptr,TX_WAIT_FOREVER);

如果变量 status的值为TX_SUCCESS,那么表示已经成功分配了一个固定大小的内存块。 此块由 memory_ptr指向。

2.3.3 删除内存块池

可以使用 tx_block_pool_delete 服务删除内存块池。所有在内存块池上挂起的线程都使用 TX_DELETED 状态码恢复。需要注意的是,在调用此函数时,正在删除的内存块池或与之关联的内存不应处于使用状态。其原型如下:

UINT tx_block_pool_delete(TX_BLOCK_POOL *pool_ptr);

示例如下:

1
2
3
4
5
TX_BLOCK_POOL my_pool;
UINT status;
...
status = tx\_block\_pool\_delete(&my_pool);

如果变量 status的值为TX_SUCCESS,表示删除成功。

2.3.4 内存块池信息检索

tx_block_pool_info_get 服务检索有关内存块池的各种信息。 检索到的信息包括块池名称、可用块数、池中块的总数、该块池挂起列表中第一个线程的位置、当前挂起的线程数 这个块池,以及下一个创建的内存块池的位置。其原型如下:

UINT tx_block_pool_info_get(TX_BLOCK_POOL *pool_ptr, CHAR **name,ULONG *available_blocks, ULONG *total_blocks,TX_THREAD **first_suspended,ULONG *suspended_count,TX_BLOCK_POOL **next_pool);

简单示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TX_BLOCK_POOL my_pool;
CHAR \*name;
ULONG available;
ULONG total_blocks;
TX_THREAD \*first_suspended;
ULONG suspended_count;
TX_BLOCK_POOL \*next_pool;
UINT status;

status = tx\_block\_pool\_info\_get(&my_pool, &name,
&available,&total_blocks,
&first_suspended,
&suspended_count,
&next_pool);

当变量status的值为TX_SUCCESS时,表示检索成功。

2.3.5 对内存块池挂起列表进行优先级排序

当一个线程因为等待一个内存块池而被挂起时,它会以先进先出的方式被放入线程挂起列表中。 当内存块池重新获得一块内存时,挂起列表中的第一个线程(无论优先级如何)都会获得从该内存块池中获取块的机会。 tx_block_pool_prioritize 服务将因特定内存块池的所有权而挂起的最高优先级线程放在挂起列表的前面。 所有其他线程保持与它们暂停时相同的 FIFO 顺序。其原型如下:

UINT tx_block_pool_prioritize(TX_BLOCK_POOL *pool_ptr);

简单示例如下:

1
2
3
4
5
TX_BLOCK_POOL my_pool;
UINT status;

status = tx\_block\_pool\_prioritize(&my_pool);

如果变量 status 的值为TX_SUCCESS,则优先级请求成功。 线程挂起列表中等待名为“my_pool”的内存块池的最高优先级线程已移动到线程挂起列表的前面。 如果没有线程在等待这个内存块池,服务调用也会返回 TX_SUCCESS。 在这种情况下,挂起线程列表保持不变。

2.3.6 内存块内部管理

当使用 TX_BLOCK_POOL 数据类型声明块池时,会创建一个块池控制块,并将该控制块添加到双向循环链表中,如下图所示:

在这里插入图片描述

名称为tx_block_pool_created_ptr 的指针指向列表中的第一个控制块。 查看块池控制块中的字段以获取块池属性、值和其他指针。

如前面所述,块池包含固定大小的内存块。 这种方法的优点包括快速分配和释放块,并且没有碎片问题。一个可能的缺点是如果块大小太大,可能会浪费空间。但是,开发人员可以通过创建多个块池来最小化这个潜在问题。

如前所述,块池包含固定大小的内存块。 这种方法的优点包括快速分配和释放块,并且没有碎片问题。一个可能的缺点是如果块大小太大,可能会浪费空间。但是,开发人员可以通过创建多个不同的块池来最小化这个潜在问题 块大小。 池中的每个块都需要少量开销,即所有者指针和下一个块指针。 如下图所示的内存块池的组织结构:

在这里插入图片描述

3、字节内存池与内存块池的比较

内存字节池,它允许使用和重用可变大小的字节组。 这种方法具有简单和灵活的优点,但会导致碎片问题。 由于碎片,内存字节池可能有足够的总字节数来满足内存请求,但由于可用字节不连续,可能无法满足该请求。 因此,我们通常建议避免将内存字节池用于确定性的实时应用程序。

内存管理的第二种方法是内存块池,它由固定大小的内存块组成,从而消除了碎片问题。 内存块池失去了一些灵活性,因为所有内存块的大小都相同,并且给定的应用程序可能不需要那么多空间。 但是,开发人员可以通过创建多个内存块池来缓解这个问题,每个内存块池都有不同的块大小。 此外,分配和释放内存块是快速且可预测的。 通常,我们建议将内存块池用于确定性的实时应用程序。

4、字节内存池完整示例

STM32CubeIDE创建工程、系统配置、调试配置,在这里不再做介绍,请参考:

ThreadX移植,请参考:

1)基本定义

app相关定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/\*
\* app.h
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#ifndef \_\_APP\_H\_\_
#define \_\_APP\_H\_\_

void app\_start(void);

// 用于创建任务栈
uint16\_t create\_task\_stack(void\*\* statck,uint32\_t size);

#endif /\* \_\_APP\_H\_\_ \*/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/\*
\* app.c
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/
#include <stdio.h>
#include "main.h"
#include "app.h"
#include "tasks/hello\_task.h"
#include "tasks/led\_blink\_task.h"
#include "tx\_api.h"
#include "tx\_thread.h"
#include <stdbool.h>

// 10k应用程序内存
#define APP\_MEMORY\_SIZE 1024 \* 10
// 字节内存池对象
static TX_BYTE_POOL __s_app_statc_pool;
// 应用程序字节内存池数组
static uint8\_t __app_memory_area[APP_MEMORY_SIZE];

// 创建应用程序字节内存池
uint16\_t create\_app\_byte\_pool() {
UINT status;
// 创建字节内存池
status = tx\_byte\_pool\_create(&__s_app_statc_pool,"app\_byte\_pool",__app_memory_area,APP_MEMORY_SIZE);
return status;
}
// 为应用程序线程从字节内存池中分配栈
uint16\_t create\_task\_stack(void \*\*statck, uint32\_t size) {
UINT status;
// 从字节内存池中分配
status = tx\_byte\_allocate(&__s_app_statc_pool,statck,size,TX_NO_WAIT);
return status;
}

void tx\_application\_define(void \*first_unused_memory) {

uint16\_t status = create\_app\_byte\_pool();
if(status != TX_SUCCESS){
printf("create app memory byte pool failed:code = %d\r\n",status);
while(true);
}
// 创建应用程序任务
create\_hello\_task();
create\_led\_task();
}

void app\_start(void) {
// 启动内核调度
tx\_kernel\_enter();
}


2)应用程序线程定义

hello线程相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/\*
\* hello\_task.h
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#ifndef \_\_HELLO\_TASK\_H\_\_
#define \_\_HELLO\_TASK\_H\_\_

void create\_hello\_task(void);

#endif /\* \_\_HELLO\_TASK\_H\_\_ \*/


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/\*
\* hello\_task.c
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#include "hello\_task.h"
#include "tx\_api.h"
#include "tx\_thread.h"
#include <stdio.h>
#include "app.h"
TX_THREAD hello_thread_handle;
#define HELLO\_THREAD\_STACK\_SIZE 512
//uint8\_t hello\_thread\_stack[HELLO\_THREAD\_STACK\_SIZE];
uint8\_t \* hello_thread_stack = NULL;

void hello\_task\_entry(void \*params) {
int counter = 0;
printf("[task]hello\_task started\r\n");
for (;;) {
printf("[task]hello\_task:counter = %d\r\n", counter);
counter++;
tx\_thread\_sleep(1000);
}
}

void create\_hello\_task(void){
uint16\_t status = create\_task\_stack(&hello_thread_stack, HELLO_THREAD_STACK_SIZE);
if(status != TX_SUCCESS){
printf("cannot create hello task stack\r\n");
return;
}
tx\_thread\_create(/\*创建线程\*/
&hello_thread_handle, /\*线程句柄\*/
"hello\_task", /\*线程名称\*/
hello_task_entry, /\*线程执行函数\*/
NULL, /\*线程函数\*/
hello_thread_stack, /\*线程堆栈入口\*/
HELLO_THREAD_STACK_SIZE, /\*线程堆栈大小\*/
3, /\*优先级\*/
3, /\*抢占阈值\*/
TX_NO_TIME_SLICE, /\*线程时间切片类型\*/
TX_AUTO_START /\*线程启动类型:自动启动\*/
);
printf("created hello task\r\n");
}


led闪烁线程相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/\*
\* led\_blink\_task.h
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#ifndef \_\_LED\_BLINK\_TASK\_H\_\_
#define \_\_LED\_BLINK\_TASK\_H\_\_

void create\_led\_task(void);

#endif /\* \_\_LED\_BLINK\_TASK\_H\_\_ \*/


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/\*
\* led\_blink\_task.c
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#include "tx\_api.h"
#include "tx\_thread.h"
#include <stdio.h>
#include "led\_blink\_task.h"
#include "gpio.h"
#include "app.h"

TX_THREAD led_thread_handle;
#define LED\_THREAD\_STACK\_SIZE 512
//uint8\_t led\_thread\_stack[LED\_THREAD\_STACK\_SIZE];
uint8\_t\* led_thread_stack = NULL;

void led\_task\_entry(void \*params) {
int counter = 0;
printf("[task]led\_task\_entry started\r\n");
for (;;) {
printf("[task]led\_blink\_task:counter = %d\r\n", counter);
HAL\_GPIO\_TogglePin(LED1_GPIO_Port, LED1_Pin);
counter++;
tx\_thread\_sleep(500);
}
}

void create\_led\_task(void){
uint16\_t status = create\_task\_stack(&led_thread_stack, LED_THREAD_STACK_SIZE);
if(status != TX_SUCCESS){
printf("cannot create led task stack\r\n");
return;
}
tx\_thread\_create(/\*创建线程\*/
&led_thread_handle, /\*线程句柄\*/
"led\_blink", /\*线程名称\*/
led_task_entry, /\*线程执行函数\*/
NULL, /\*线程函数\*/
led_thread_stack, /\*线程堆栈入口\*/
LED_THREAD_STACK_SIZE, /\*线程堆栈大小\*/
3,
3,
TX_NO_TIME_SLICE, /\*线程时间切片类型\*/
TX_AUTO_START /\*线程启动类型:自动启动\*/
);
printf("createdd led task\r\n");
}

3)主程序

在main.c文件中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/\* Private includes ----------------------------------------------------------\*/
/\* USER CODE BEGIN Includes \*/
#include "app.h"
#include <stdio.h>
/\* USER CODE END Includes \*/

int main(void) {
/\* USER CODE BEGIN 1 \*/

/\* USER CODE END 1 \*/

/\* MCU Configuration--------------------------------------------------------\*/

/\* Reset of all peripherals, Initializes the Flash interface and the Systick. \*/
HAL\_Init();

/\* USER CODE BEGIN Init \*/

/\* USER CODE END Init \*/

/\* Configure the system clock \*/
SystemClock\_Config();

/\* USER CODE BEGIN SysInit \*/

/\* USER CODE END SysInit \*/

/\* Initialize all configured peripherals \*/
MX\_GPIO\_Init();
MX\_USART1\_UART\_Init();
/\* USER CODE BEGIN 2 \*/
printf("\*\*\*\*STM32CubeIDE:ThreadX Demo\*\*\*\*\r\n");
// 启动应用程序
app\_start();
/\* USER CODE END 2 \*/

/\* Infinite loop \*/
/\* USER CODE BEGIN WHILE \*/
while (1) {
/\* USER CODE END WHILE \*/

/\* USER CODE BEGIN 3 \*/
}
/\* USER CODE END 3 \*/
}

文章来源: https://iotsmart.blog.csdn.net/article/details/125109715