以下内容转载自安富莱电子: http://forum.armfly.com/forum.php
内存管理介绍
在 ANSI C 中,可以用 malloc()和 free()2 个函数动态的分配内存和释放内存,但是,在嵌入式实时
操作系统中,调用 malloc()和 free()却是危险的,因为多次调用这两个函数会把原来很大的一块连续内场
区域逐渐地分割成许多非常小而且彼此又不相邻的内存块,也就是内存碎片。由于这些内存碎片的大量存
在,使得程序到后来连一段非常小的连续内存也分配不到。另外,由于内存管理算法上的原因,malloc()
和 free()函数的执行时间是不确定的。
在 RTX 中,操作系统把连续的大块内存按分区来管理。每个分区中包含整数个大小相同的内存块。如
图 18.1 所示:
利用这种机制,就可以得到和释放固定大小的内存块。 这样内存的申请和释放函数的执行时间就是确定的了。
在一个系统中可以有多个内存分区,这样,应用程序就可以从不同的内存分区中得到不同大小的内存
块。但是特定的内存块在释放时,必须重新放回到它以前所属于的内存分区。显然,采用这样的内存管理
算法,上面的内存碎片文件就得到了解决。
其实 RTX 的内存管理也非常好理解,可以理解成一个二维数组,比如我们定义一个二维数组为:
uint8_t mpool[10][32]。 对应到 RTX 的内存管理上就是定义了 10 个内存块,每块大小是 32 字节。
如果还需要其它大小的内存块,还可以多定义几个其它大小的。
内存管理 API 函数
使用如下 7 个函数可以实现 RTX 的内存管理:
_declare_box
_declare_box8
_init_box
_init_box8
_alloc_box
_calloc_box
_free_box
函数_declare_box
函数原型:
#define _declare_box( \
pool, \ /* 内存池变量名 */
size, \ /* 内存块大小,单位字节 */
cnt ) \ /* 内存池中内存块的个数 */
U32 pool[((size+3)/4)*(cnt) + 3]
函数描述:
函数_declare_box 用于定义一块内存池。
第 1 个参数填写内存池的变量名。
第 2 个参数填写内存块的大小,单位字节。
第 3 个参数填写内存池中内存块的个数。
使用这个函数要注意以下问题:
1. 宏定义中通过操作(size+3)/4 保证了每个内存块大小是 4 字节的倍数,从而也就保证了每个内存块
的首地址是 4 字节对齐的(4 字节对齐的含义是地址对 4 求余等于 0)。这里初学者也要注意数据类
型的字节对齐问题。
基本数据类型的字节对齐问题
这个问题在 MDK5 安装目录里面的文档 DUI0375G_02_mdk_armcc_user_guide.pdf 里面有详细的说明,
简单的说明, 数据类型有几个字节, 那么这个数据类型的变量就是几字节对齐,比如 32 位的 int 型
有 4 个字节,那么此 int 型定义的变量就是 4 字节对齐,对于初学者要牢牢的记住这个知识点。
其实这个在keil的帮助文档里面就有,善于利用ide的帮助文档,这是最直接快捷的熟悉编译器的方式。
2. 内存池中额外定义的 12 个字节用于将内存块做指针链表,方便动态的申请和释放。
函数_declare_box8
函数原型:
#define _declare_box8( \
pool, \ /* 内存池变量名 */
size, \ /* 内存块大小,单位字节 */
cnt ) \ /* 内存池中内存块的个数 */
U64 pool[((size+7)/8)*(cnt) + 2]
函数描述:
函数_declare_box 用于定义一块内存池。
第 1 个参数填写内存池的变量名。
第 2 个参数填写内存块的大小,单位字节。
第 3 个参数填写内存池中内存块的个数。
使用这个函数要注意以下问题:
1. 宏定义中通过操作(size+7)/8 保证了每个内存块大小是 8 字节的倍数,从而也就保证了每个内存块
的首地址是 8 字节对齐的(8 字节对齐的含义是地址对 8 求余等于 0)。这里初学者也要注意数据类
型的字节对齐问题。
2. 内存池中额外定义的 16 个字节用于将内存块做指针链表,方便动态的申请和释放。
函数_init_box
函数原型:
int _init_box (
void* box_mem, /* 内存池首地址 */
U32 box_size, /* 内存池大小,单位字节 */
U32 blk_size ); /* 内存块大小, 单位字节 */
函数描述:
函数_init_box 用于内存池的初始化,初始化时用到的参数都是源自于_declare_box。
第 1 个参数填写内存池的首地址。
第 2 个参数填写内存池的大小,单位字节。
第 3 个参数填写内存块大小,单位字节。
使用这个函数要注意以下问题:
1. 强烈建议跟函数_declare_box 一起使用。用户不要自己去初始化这个函数,用_declare_box 声明的
才是最保险的。
2. 如果用户没有使用函数_declare_box 进行定义,那么要保证内存池首地址是 4 字节对齐的。
3. 如果用户没有使用函数_declare_box 进行定义,那么要保证内存池的大小 box_size 至少有 12 个字节,
因为这个 12 个字节是用于将内存块做成指针链表,方便动态申请和释放。
函数_init_box8
函数原型:
int _init_box (
void* box_mem, /* 内存池首地址 */
U32 box_size, /* 内存池大小,单位字节 */
U32 blk_size ); /* 内存块大小, 单位字节 */
函数描述:
函数_init_box8 用于内存池的初始化,初始化时用到的参数都是源自于_declare_box8。
第 1 个参数填写内存池的首地址。
第 2 个参数填写内存池的大小,单位字节。
第 3 个参数填写内存块大小,单位字节。
使用这个函数要注意以下问题:
1. 强烈建议跟函数_declare_box8 一起使用。用户不要自己去初始化这个函数,用_declare_box 声明的
才是最保险的。
2. 如果用户没有使用函数_declare_box8 进行定义,那么要保证内存池首地址是 8 字节对齐的。
3. 如果用户没有使用函数_declare_box8 进行定义,那么要保证内存池的大小 box_size 至少有 16 个字
节,因为这个 16 个字节是用于将内存块做成指针链表,方便动态申请和释放。
函数_alloc_box
函数原型:
void *_alloc_box (
void* box_mem ); /* 内存池的首地址 */
函数描述:
函数_alloc_box 用于从首地址是 box_mem 的内存池中申请一个内存块。
第 1 个参数填写内存池的首地址。
使用这个函数要注意以下问题:
1. 调用此函数前,一定要使用函数_init_box 或者_init_box8 进行初始化。
2. 函数_alloc_box 支持重入,而且是线程安全的,也即是说用户可以没有限制的在主函数和中断中调用此函数。
函数_free_box
函数原型:
int _free_box (
void* box_mem, /* 内存池首地址 */
void* box ); /* 要释放的内存块首地址 */
函数描述:
函数_free_box 用于释放使用函数_alloc_box 申请的内存块。
第 1 个参数填写内存池的首地址。
第 2 个参数填写要释放的内存块首地址。
使用这个函数要注意以下问题:
1. 此函数的第二个参数必须要填写正确,也就是用户使用的时候最好配套_alloc_box 一起使用。
2. 函数_free_box 支持重入,而且是线程安全的,也即是说用户可以没有限制的在主函数和中断中调用此函数。
代码练兵场:
串口打印:
这里有两点需要说明:
第一,为什么按键按下之后,先打印消息队列的输出,因为消息处理任务优先级高于按键处理任务(如果按键处理任务优先级高于消息处理,就会先打印按键消息);
第二,在下面函数中采用官方demo的方式:
我们可以使用void *pMsg,然后在wait中给&pMsg,最后打印的时候转换成自己想要的类型,也可以:
直接给出想要的类型在wait函数中强转,安富莱就是使用的后者。