建立一个内存分区的步骤是先建立一个二维数组,二维数组的第一维是块数,第二维是块的大小,二维数组把一块连续的内存占了(虽然占了,但是并不能有详细的管理),然后把这个二维数组的地址给OSMemCreate()函数,进行一系列设置,方便系统对这块连续的内存进行管理。
函数作用:
把二维数组与内存控制块联系起来,行程内存分区;
函数工作流程:
OSMemCreate()对内存分区主要做了三个工作:
1,在系统初始化时,根据设置先初始化了一系列空白的内存控制块,并形成链表。在函数中首先在这个空白内存控制块中拿出一个控制块备用。
2,内存分区是一片连续的内存,目的是把这块内存分成一个个小块方便管理,所以函数先把每个块串联成一个链表。每个块前四个字节存放的都是下一个内存块的首地址。末尾指向NULL;
3,把内存与内存控制块联系起来。
详解:
固定流程:判断参数等
OS_MEM *OSMemCreate (void *addr,//内存分区的起始地址,也就是二维数组的起始地址。
INT32U nblks,//块数,就是二维数组的第一维组数。
INT32U blksize,//块大小,注意是字节为单位的。
INT8U *perr)//错误标志。
{
OS_MEM *pmem;
INT8U *pblk;
void **plink;
INT32U loops;
INT32U i;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (addr == (void *)0) { /* Must pass a valid address for the memory part.*/
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (((INT32U)addr & (sizeof(void *) - 1u)) != 0u){ /* Must be pointer size aligned */
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (nblks < 2u) { /* Must have at least 2 blocks per partition */
*perr = OS_ERR_MEM_INVALID_BLKS;
return ((OS_MEM *)0);
}
if (blksize < sizeof(void *)) { /* Must contain space for at least a pointer */
*perr = OS_ERR_MEM_INVALID_SIZE;
return ((OS_MEM *)0);
}
#endif
以上只是进行一系列判断,是否内存首地址有效等。
摘取内存控制块:
OS_ENTER_CRITICAL();
pmem = OSMemFreeList; /* Get next free memory partition */
if (OSMemFreeList != (OS_MEM *)0) { /* See if pool of free partitions was empty */
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;//如果还有空闲的内存控制块,那么先把一个控制块从链表上摘取下来。方法是:把指向下一个空闲内存控制块的地址赋给链表起始地址。则原来的头就被摘取下来了。
}
OS_EXIT_CRITICAL();
if (pmem == (OS_MEM *)0) { /* See if we have a memory partition */
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);//看看摘取下来的内存控制块是否可用,如果不可用返回错误信息。
}
从已经创建的空闲内存控制块链表中取出一个备用。并且如果取出的是非空的,再把空余链表头指向下一个链表节点。
串联内存块成链表:
plink = (void **)addr; /* Create linked list of free memory blocks */
pblk = (INT8U *)addr;
loops = nblks - 1u;
for (i = 0u; i < loops; i++) {
pblk += blksize; /* Point to the FOLLOWING block */
*plink = (void *)pblk; /* Save pointer to NEXT block in CURRENT block */
plink = (void **)pblk; /* Position to NEXT block */
}
这部分比较难理解,因为涉及到二级指针、二维数组与强制转换。
二级指针:A(即B的地址)是指向指针的指针,称为二级指针,用于存放二级指针的变量称为二级指针变量。根据B的不同情况,二级指针又分为指向指针变量的指针和指向数组的指针。
二维数组:二维数组本质就是一段连续的内存,但是二维数组的头是一个一级指针,指向内存首地址。与二级指针并不等价。所以传递参数时候addr是一个指向空的一级指针。
强制转换:强制转换只是转换当前变量类型,例如 : int p; p=0xaa00; *p=100; 则 (void )p=0xaa00;只是对p进行了强制转换并不是指指针所指的变量,要分清。
函数中plink=(void * *)addr;是把addr转换成指向任意类型的空指针的二级指针。因为plink定义的就是二级指针,所以要想直接赋值必须把addr也转换为二级指针。虽然直接赋值也是传递地址,但是传递地址的意义不一样,对于cortex内核来说是32位内核,地址是4个字节的,如果只传递地址,地址所指向的内存类型位置,编译器就不知道传递的是几个字节,有可能只是一个字节。但是转换为二级指针后,传递的是指向指针的指针,即是4个字节的内存。所以强制转换是有必要的。
进入for循环前,plink存放的是内存分区的首地址,地址内存放的是一个指针。pblk存放的是下一个块的首地址,地址内存放内容占用一个单位(一个字节)就可以,方便与blksize(字节单位)相加,所以addr转换成一级指针就可以。但是两个地址是相同的。
1,进入循环后,先把pblk指针位置后移blksize,即进入plink后一个块的首地址。
2,然后把pblk强制转换为指向任意内容的指针,再把地址赋给plink所指向的内存(注意是把地址放入PLINK地址所指向的内存)。此时plink指向前一个内存块,内存块前四个字节存放的下一个内存块的首地址。
3,然后把pblk转换为二级指针,再把pblk所指向的地址赋给plink,即plink指向原来内存块的后一个内存块地址,地址内存放的内容仍然是指针(内存内并没有被赋值,下一次循环的第二步才会被赋值)。这个步骤类似plink=(void **)addr;
链接内存控制块:
*plink = (void *)0; /* Last memory block points to NULL */
pmem->OSMemAddr = addr; /* Store start address of memory partition */
pmem->OSMemFreeList = addr; /* Initialize pointer to pool of free blocks */
pmem->OSMemNFree = nblks; /* Store number of free blocks in MCB */
pmem->OSMemNBlks = nblks;
pmem->OSMemBlkSize = blksize; /* Store block size of each memory blocks */
*perr = OS_ERR_NONE;
return (pmem);
}
for循环出来后,plink所指向的地址就是内存分区最后一个块的首地址,因为是最后一个块了,把NULL赋给这个地址标志是最后一个块。
然后把内存分区首地址赋给内存控制块的OSMemAddr;
因为分区内还没有块被占用,所以空闲块的首地址与内存分区地址相同,再把首地址赋给OSMemFreeList。
分区内可用块大小与总块大小相同,所以把NBLKS赋给OSMemNFree和OSMemNBlks;
分区内每个块大小blksize赋给OSMemBlksize。
函数功能完成。