分类: 嵌入式
2010-09-25 21:11:36
参考:
《嵌入式实时操作系统》Jean J.Labrosse著 邵贝贝 等译
OSMemCreate() 关键部分试详解
ucos源码分析之OSMemCreate()
在ANSI C中,可以用malloc()和free() 2个函数动态的分配内存和释放内存;但是,在嵌入式实时操作系统中,调用malloc()和free()却是危险的,因为多次调用这2个函数,会把原来很大的一块连续内存区域逐渐的分割成许多非常小而且彼此不相邻的内存块,也就是内存碎片。由于这些碎片的大量存在,使得程序到后来连一段非常小的连续内存也分配不到。另外,由于内存管理算法上的原因,malloc()和free()函数的执行时间是不确定的。
在uc/os-ii中,操作系统把连续的大块内存按分区来管理。每个分区中包含整数个大小相同的内存块,如图F12.1所示。用这种机制,uc/os-ii对malloc()和free()函数进行了改进,使得它们可以得到和释放固定大小的内存块。这样,malloc()和free()函数的执行时间就是确定的了。
如图F12.2所示,在一个系统中可以有多个内存分区,这样应用程序就可以从不同的内存分区中得到不同大小的内存块;但是,特定的内存块在释放时,必须重新放回到它以前所属于的内存分区。显然,采用这样的内存管理算法,上面的内存碎片问题就得到来解决。
为了便于内存的管理,在uc/os-ii中使用内存控制块(memory control block)的数据结构跟踪每一个内存分区,系统中的每个内存分区都有它自己的内存控制块。程序清单L12.1是内存控制块的定义。
程序清单L12.1 内存控制块的数据结构
typedef struct { /* MEMORY CONTROL BLOCK */
void *OSMemAddr; /* Pointer to beginning of memory partition */
void *OSMemFreeList; /* Pointer to list of free memory blocks */
INT32U OSMemBlkSize; /* Size (in bytes) of each block of memory */
INT32U OSMemNBlks; /* Total number of blocks in this partition */
INT32U OSMemNFree; /* Number of memory blocks remaining in this partition */
} OS_MEM;
.OSMemAddr
指向内存分区的起始地址指针,它在建立内存分区时初始化,在此之后就不能更改了。
.OSMemFreeList
指向下一个空余内存控制块或下一个空余内存块的指针,具体的含义须根据该内存分区是否已经建立起来决定。
.OSMemBlkSize
内存分区中内存块的大小,是建立该内存分区时定义的。
.OSMemNBlks
内存分区中总的内存块数量,也是建立该内存分区时定义的。
.OSMemNFree
内存分区中当前可以获得的空余内存块数量。
若要在uc/os-ii中使用内存管理,则需要在OS_CFG.H文件中将开关量OS_MEM_EN设置为1。这样uc/os-ii在启动时就会对内存管理器进行初始化(由OSInit()调用OSMemInit()函数实现)。该初始化主要建立一个图F12.3所示的内存控制块链表,其中的常数OS_MAX_MEM_PART定义了最大的内存分区数,该常数值至少应为2。
从图中可以看出,内存控制块的域OSMemFreeList的作用是,将空余的内存控制块链接成内存控制链表。
在使用一个内存分区之前,必须先建立该内存分区,这个操作可以通过调用函数OSMemCreate()来完成。程序清单L12.2说明了如何建立一个含有100个内存块并且每个内存块的大小为32B的内存分区。
程序清单L12.2 建立一个内存分区
程序清单L12.3是OSMemCreate()函数的源代码。该函数共有4个参数:内存分区的起始地址、分区内的内存块总数、每个内存块的字节数及一个指向出错信息代码的指针。如果OSMemCreate()操作失败,它将返回一个 NULL 指针;否则,它将返回一个指向内存控制块的指针。对内存管理的其他操作,像OSMemGet(),OSMemPut()及OSMemQuery()函数等,都需通过该指针进行。
程序清单L12.3 OSMemCreate()
代码的注释很详尽,关键的地方
首先,参数void *addr指向一个二维数组的起始地址,并且二维数组的大小是nblks x blksize。上述代码中:
INT8U *pblk; //定义一个指向INT8U类型的指针变量;(指针变量:存放变量地址的变量。内存地址即是指针?)
void **plink; //定义一个指向void * 类型的指针变量;(二维指针变量:存放指针变量地址的变量,即指向指针的指针)
…
plink = (void**)addr; //将addr指针,也就是二维数组的起始地址(如0×00000000)强制转换成指向void *类型的指针,并赋值给plink。(将一个一维指针转换成二维指针)
pblk = (INT8U *)addr + blksize; //将addr指针强制转换成指向INT8U类型的指针,并将该指针+blksize得到新的地址,赋值给pblk。(指针加整数:如INT8U类型占一条内存地址,而INT32U类型占4条内存地址,所以指针加整数的值为——指针地址+(INT8U占内存地址数)× blksize),pblk指针是下一内存块的起始地址。
for(i = 0; i < (nblks - 1); i++)
{
*plink = (void *)pblk; //将pblk指针强制转换成指向void 类型的指针,并赋值给plink所指地址内存,正好该内存的值类型也是void * 。它的意义是该内存块的首地址的内存值为下一内存块的起始地址。plink = (void **)pblk; //将pblk指针强制转换成一个指向void *类型的指针,赋值给plink。它的意义在于在下次使用*plink时已经是下一内存块起始地址的内存的值了,因为plink的值是下一内存块的起始地址。
pblk = pblk + blksize; //这里应该知道是什么意思了,不懂请看前面。
}
//上面那个循环就是将每个内存块的起始地址的内存赋值为下一内存块的起始地址,最后一内存块起始地址的内存赋0值。
OSMemCreate()函数完成后,内存控制块及对应的内存分区与分区内的内存块之间的关系如图F12.4所示。
在程序运行期间,经过多次的内存分配和释放后,同一分区内的各内存块之间的链接顺序会发生很大的变化。
在声明的时候,plink是二维指针,在这里将addr强制的转换成二维指针再赋值给plink的原因是:让编译器把addr指向的内容解释成内存地址,也就是一个指针;如果不做这个强制转换,addr指向的内容就不是一个地址,而是普通的数值。换言之,即是把一个普通的数值解释成内存地址值,在addr指向的地方放置一个指针。
在这个函数当中,我们想把addr指向的二维数组(连续内存:即内存分区),分割成大小相同的若干块(内存块),而且用指针把它们链接起来,所以将addr强制转换成二维指针,并赋值给plink,然后让plink去执行链接的操作。uc/os-ii内存管理的精妙之处就在于此:把一块连续的内存划分成大小相等的内存块,并使用指针把它们链接成一个连续的链表,这样既方便了内存的分配和回收(链表的插入和删除操作很方便),也避免了内存碎片的问题。
总结:进行强制转换的目的其实就是为了在addr所指向的地方放置一个指针。
void *pblk;
执行操作:pmem->OSMemFreeList =*(void **)pblk;意味着取出pblk的内容,由于pblk被强制转换成了二维指针,所以它的内容不再是一般的值,而是一个指针。