详解UCOS中的内存管理
在嵌入式设备中,持续的调用malloc()和free()容易产生内存碎片,长时间的运行最终会导致内存消耗殆尽。UCOS提供了一套内存管理机制,在系统初始化的时候就分配好内存空间,将所有可用的空间组织成链表,需要申请内存的时候直接从链表中申请,释放内存的时候直接将内存归还到空余内存链表中即可。使用这种方法不仅避免了内存碎片的产生,而且使得在常数时间内分配内存空间成为可能。
UCOS中内存管理的结构体是OS_MEM,具体结构如下:
typedef struct os_mem //内存控制块
{
void *OSMemAddr; //指向内存分区的首地址
void *OSMemFreeList; //该内存分区的block链表的表头
INT32U OSMemBlkSize; //每一个block的大小
INT32U OSMemNBlks; //该分区中block的数目
INT32U OSMemNFree; //该分区中空闲block的数目
} OS_MEM;
在UCOS中,一个内存分区被划分成很多个大小相等的内存块,这些内存块链接成链表,链表的表头存于OSMemFreeList中。
比如一个有4个内存块,每个内存块128bit的内存分区Memoy如下:
INT32U Memory[4][4]
UCOS的思路是我们将要分配的空间组织成二维数组,然后二维数组的每一行就是一个block,二维数组的列数既是block的大小。
Memory的第一个block的首地址是 Memor[0]
Memory的第二个block的首地址是 Memory[1]
Memory的第三个block的首地址是 Memory[2]
Memory的第四个block的首地址是 Memory[3]
为了管理方便我们在每一个block的开头存储下一个block的地址,这样就把所有的block串接成了单链表。
第一个block的开头存储 Memory[1]
第一个block的开头存储 Memory[2]
第一个block的开头存储 Memory[3]
第一个block的开头存储 NULL
最终结果如下图:
UCOS中创建内存分区的核心代码如下(代码取自OSMemCreate):
//内存分区的首地址
plink = (void **)addr;
//第二个block的地址
pblk = (INT8U *)((INT32U)addr + blksize);
for (i = 0; i < (nblks - 1); i++)
{
//每一个block的开头存放下一个block的首地址
*plink = (void *)pblk;
//更新指针而已
plink = (void **)pblk;
pblk = (INT8U *)((INT32U)pblk + blksize);
}
//最后一个block指向NULL
*plink = (void *)0;
上述代码的核心就在于*pblink = (void*)pblk;每一个block开头存放下一个block的地址,这样便把所有的block组织成了一条链表。
申请一个内存块:
//还有内存可以分配
if (pmem->OSMemNFree > 0)
{
//获取一个block
pblk = pmem->OSMemFreeList;
//使链表表头指向这个block的下一个block
pmem->OSMemFreeList = *(void **)pblk;
//更新内存块的数量
pmem->OSMemNFree--;
}
代码的核心就在于pmem->OSMemFreeList = *(void **)pblk; 因为pblk指向该block,该block的首地址存放的是下一个block的地址,所以这句实际上让空余链表的表头指向了下一个block。
删除一个内存块:
//在将要删除的内存块的首地址存放block表头的地址
*(void **)pblk = pmem->OSMemFreeList;
//更新链表表头
pmem->OSMemFreeList = pblk;
//更新可用内存块的数目
pmem->OSMemNFree++;
代码的核心就是*(void **)pblk = pmem->OSMemFreeList;在将要删除的block的开头存放链表的表头,即相当于把这个block链接到了空余链表的表头中。