建立一個內存分區的步驟是先建立一個二維數組,二維數組的第一維是塊數,第二維是塊的大小,二維數組把一塊連續的內存佔了(雖然佔了,但是並不能有詳細的管理),然後把這個二維數組的地址給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。
函數功能完成。