应用程序中为了某种特殊需要,经常需要动态的分配内存,而操作系统的特质置一,就是能不能保证动态内存分配的时效性,也就是说分配时间是可确定的
Ucos提供内存分配功能,它将内存空间分为两级管理,将一块连续的内存空间分为若干个分区,每个分区单位又分成大小相同的若干个内存块,分区时操作系统的管理单位,而内存块是分配单位,内存分区以及内存块的使用情况有一个叫做内存控制块的表来记录,内存控制块的基本结构如下
typedef struct os_mem {
void *OSMemAddr;
void *OSMemFreeList;
INT32U OSMemBlkSize;
INT32U OSMemNBlks;
INT32U OSMemNFree;
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName;
#endif
} OS_MEM;
OSMemAddr 内存分区的指针
OSMemFreeList 内存控制块链表的指针
OSMemBlkSize 内存块的长度
OSMemNBlks 分区内内存块的数目
OSMemNFree 分区内部当前可分配内存块数目
系统在初始化的时候将当前系统内部的所有内存控制块构成一个链表,叫做空闲内存控制块表,变量如下
OS_EXT OS_MEM *OSMemFreeList;
而系统初始时刻存在的内存控制块的数目是给定的
OS_EXT OS_MEM OSMemTbl[OS_MAX_MEM_PART];
OS_MAX_MEM_PART是一个宏,决定系统拥有几个内存分区,在os_cfg.h文件中定义
初始化内存控制块列表的函数为OS_MemInit,其中的核心code为
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl));
for (i = 0u; i < (OS_MAX_MEM_PART - 1u); i++) {
pmem = &OSMemTbl[i];
pmem->OSMemFreeList = (void *)&OSMemTbl[i + 1u];
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
}
pmem = &OSMemTbl[i];
pmem->OSMemFreeList = (void *)0;
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
OSMemFreeList = &OSMemTbl[0];
类似于tcb初始化的流程,也是将一个数组链表化,并且将OSMemFreeList指向链表的开头,此时系统是还没有开始管理内存的,因为我们并没有告诉系统我们可分配的内存在哪里,那在哪儿指明了呢,答案是在OSMemCreate中,该函数初始化一个内存分区,并返回一个内存分区控制结构体,函数原型如下
OS_MEM *OSMemCreate (void *addr,INT32U nblks,INT32U blksize,INT8U *perr)
指明我们空闲内存分区的开始地址,我们分配的内存块的长度以及内存块的数目,三者计算就能得到我们分配的内存大小nblks*blksize
在OSMemCreate中先做了以下操作
plink = (void **)addr;
pblk = (INT8U *)addr;
loops = nblks - 1u;
for (i = 0u; i < loops; i++) {
pblk += blksize;
*plink = (void *)pblk;
plink = (void **)pblk;
}
这句话实现的实际上是将一个内存分区分解为nblks个内存块,然后在每一个内存块的最头上位置放置了一个指针,这个指正指向下一个内存块的首地址,用图比较好解释
如上就是基本的内存管理结构了
pmem->OSMemAddr = addr;
pmem->OSMemFreeList = addr;
pmem->OSMemNFree = nblks;
pmem->OSMemNBlks = nblks;
pmem->OSMemBlkSize = blksize;
接下来就是进行了内存控制块的赋值操作,也就是说,ucos的两级内存管理实际上是通过两级链表来操作的.空闲内存控制块构成一个空闲链表,分配的内存块有内存块本身包含的指针形成单向链表,此时内存控制块的指向为可分配内存的头
而向操作系统申请内存块的函数为OSMemGet,其核心流程代码如下
if (pmem->OSMemNFree > 0u) {
pblk = pmem->OSMemFreeList;
pmem->OSMemFreeList = *(void **)pblk;
pmem->OSMemNFree--;
该函数需要我们给出一个内存控制块指针,函数从控制块中返回空闲内存,首先检测控制块内是否还有空闲内存,若有,将最近的空闲内存返回,控制快内空闲内存-1, OSMemFreeList指向下一个空闲内存块的首地址.
而释放内存块的操作使用OSMemPut完成,该函数接受两个参数,一个是空闲内存控制块的指针,一个是想要释放的内存指针,核心代码为
*(void **)pblk = pmem->OSMemFreeList;
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++;
通过分析以上代码可以得出,新释放的内存块被插入到空闲块的最头上地址,控制块中的空闲块指针指向释放的地址,释放地址的首地址存放原来的第一个空闲块的地址,链表恢复,并将空闲内存++;
另外可以通过OSMemQuery查询内存分区的状态,简单接口,不再赘述
但是一定要注意,ucos的内存管理是存在问题的!
在OSMemCreate中有以下语句
INT8U *pblk;
这是一个八位指针,后面在进行内存块划分的时候有以下代码
for (i = 0u; i < loops; i++) {
pblk += blksize;
但是pblk是8位的,最多只能支持255内存管理,32位内存空间的话就不能用这个,所以32位寻址的时候,需要修改这部分的东西,我推荐这个部分的内存管理代码可以自己写,反正和操作系统任务调度也没有关系