FreeRTOS --(6)內存管理 heap5

FreeRTOS 中的 heap 5 內存管理,相對於 heap 4《FreeRTOS --(5)內存管理 heap4》 只增加了對非連續內存區域的管理,什麼叫非連續區域內存呢?比如一款芯片,它即支持了內部的 RAM,也支持了外掛 RAM,那麼這兩個內存就可能在地址上不是連續的,比如 RAM1、RAM2、RAM3,如下所示:

針對這種情況,就可以使用 heap 5 來管理;

不同於之前的 heap 管理,heap 5 引入了一個結構體來管理這些非連續的區域:

typedef struct HeapRegion
{
 /* The start address of a block of memory that will be part of the heap.*/
 uint8_t *pucStartAddress;
 /* The size of the block of memory in bytes. */
 size_t xSizeInBytes;
} HeapRegion_t;

一個塊連續的內存用一個 HeapRegion_t 表示,多個連續內存通過 HeapRegion_t 數組的形式組織成爲了所有的內存;

heap 5 要求,在調用正式的內存分配函數之前,需要定義 HeapRegion,並調用 vPortDefineHeapRegions 來初始化它;

官方給了一個 demo:

/* Define the start address and size of the three RAM regions. */
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 1024 )

#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )

#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )

/* Create an array of HeapRegion_t definitions, with an index for each of the three 
RAM regions, and terminating the array with a NULL address. The HeapRegion_t 
structures must appear in start address order, with the structure that contains the 
lowest start address appearing first. */
const HeapRegion_t xHeapRegions[] =
{
    { RAM1_START_ADDRESS, RAM1_SIZE },
    { RAM2_START_ADDRESS, RAM2_SIZE },
    { RAM3_START_ADDRESS, RAM3_SIZE },
    { NULL, 0 } /* Marks the end of the array. */
};
int main( void ) {
    /* Initialize heap_5. */
    vPortDefineHeapRegions( xHeapRegions );
    /* Add application code here. */
}

如果有 3 個 RAM 區域的話,那麼這樣去定義他們;

需要注意的幾點是:

1、定義 HeapRegion_t 數組的時候,最後一定要定義成爲 NULL 和 0,這樣接口才知道這是終點;

2、被定義的 RAM 區域,都會去參與內存管理;

那麼問題就來了,在真實使用的時候,有可能你很難去定義部分 RAM 的 Start Address!

比如,一款芯片,它告訴你,它的第一塊 RAM 有 64KB,第二塊 RAM 有 32KB,第三塊 RAM 有 32KB,那麼你顯然不能夠直接將這些內存信息,按照上面 demo 代碼的形式,定義到這個表格中,因爲在編譯階段,有可能你相關的代碼和數據等等(.text,.data,.bss,等)都會佔用一部分的 RAM,但凡是定義到這個 HeapRegion_t 數組表格的,都會參與內存管理的行爲,這顯然是我們不願意的;

也就是說,比如你芯片的 RAM 起始地址 0x2000_0000,你編譯你的 Source Code 後,相關的代碼和數據要佔用 20KB,也就是 0x5000;那麼你定義的 RAM1_START_ADDRESS 起始地址,就必須要大於 0x2000_0000+0x5000,這樣纔不會踩到你的其他數據;但是呢?你總不可能每次編譯完都去改你的這個表吧?這是很痛苦的事情;

所有考慮到實際的使用,官方給出參考 demo 的方法是:

/* Define the start address and size of the two RAM regions not used by the 
linker. */
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )

#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )

/* Declare an array that will be part of the heap used by heap_5. The array will be 
placed in RAM1 by the linker. */
#define RAM1_HEAP_SIZE ( 30 * 1024 )

static uint8_t ucHeap[ RAM1_HEAP_SIZE ];

/* Create an array of HeapRegion_t definitions. Whereas in Listing 6 the first entry 
described all of RAM1, so heap_5 will have used all of RAM1, this time the first 
entry only describes the ucHeap array, so heap_5 will only use the part of RAM1 that 
contains the ucHeap array. The HeapRegion_t structures must still appear in start 
address order, with the structure that contains the lowest start address appearing 
first. */

const HeapRegion_t xHeapRegions[] =
{
    { ucHeap, RAM1_HEAP_SIZE },
    { RAM2_START_ADDRESS, RAM2_SIZE },
    { RAM3_START_ADDRESS, RAM3_SIZE },
    { NULL, 0 } /* Marks the end of the array. */
};

即,將鏈接的代碼數據,根據鏈接器(Linker)配置後,這些都放置在第一段的區域,ucHeap 也放在一樣的地方,這樣就避免去根據 map 文件去硬編碼這個表格;

通過 beyond compare 可以知道,heap 5 和 heap 4 的代碼在分配內存的 pvPortMalloc,和釋放內存的 vPortFree,以及插入節點合併空閒內存 prvInsertBlockIntoFreeList 的部分,幾乎完全一樣,唯一不一樣的地方在於:

heap 4 的內存初始化用的是 prvHeapInit

heap 5 的內存初始化用的是 vPortDefineHeapRegions

那我們就來看看這個 vPortDefineHeapRegions 的實現:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
{
BlockLink_t *pxFirstFreeBlockInRegion = NULL, *pxPreviousFreeBlock;
size_t xAlignedHeap;
size_t xTotalRegionSize, xTotalHeapSize = 0;
BaseType_t xDefinedRegions = 0;
size_t xAddress;
const HeapRegion_t *pxHeapRegion;

	/* Can only call once! */
	configASSERT( pxEnd == NULL );

	pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );

	while( pxHeapRegion->xSizeInBytes > 0 )
	{
		xTotalRegionSize = pxHeapRegion->xSizeInBytes;

		/* Ensure the heap region starts on a correctly aligned boundary. */
		xAddress = ( size_t ) pxHeapRegion->pucStartAddress;
		if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
		{
			xAddress += ( portBYTE_ALIGNMENT - 1 );
			xAddress &= ~portBYTE_ALIGNMENT_MASK;

			/* Adjust the size for the bytes lost to alignment. */
			xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
		}

		xAlignedHeap = xAddress;

		/* Set xStart if it has not already been set. */
		if( xDefinedRegions == 0 )
		{
			/* xStart is used to hold a pointer to the first item in the list of
			free blocks.  The void cast is used to prevent compiler warnings. */
			xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;
			xStart.xBlockSize = ( size_t ) 0;
		}
		else
		{
			/* Should only get here if one region has already been added to the
			heap. */
			configASSERT( pxEnd != NULL );

			/* Check blocks are passed in with increasing start addresses. */
			configASSERT( xAddress > ( size_t ) pxEnd );
		}

		/* Remember the location of the end marker in the previous region, if
		any. */
		pxPreviousFreeBlock = pxEnd;

		/* pxEnd is used to mark the end of the list of free blocks and is
		inserted at the end of the region space. */
		xAddress = xAlignedHeap + xTotalRegionSize;
		xAddress -= xHeapStructSize;
		xAddress &= ~portBYTE_ALIGNMENT_MASK;
		pxEnd = ( BlockLink_t * ) xAddress;
		pxEnd->xBlockSize = 0;
		pxEnd->pxNextFreeBlock = NULL;

		/* To start with there is a single free block in this region that is
		sized to take up the entire heap region minus the space taken by the
		free block structure. */
		pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;
		pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion;
		pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;

		/* If this is not the first region that makes up the entire heap space
		then link the previous region to this region. */
		if( pxPreviousFreeBlock != NULL )
		{
			pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;
		}

		xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;

		/* Move onto the next HeapRegion_t structure. */
		xDefinedRegions++;
		pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
	}

	xMinimumEverFreeBytesRemaining = xTotalHeapSize;
	xFreeBytesRemaining = xTotalHeapSize;

	/* Check something was actually defined before it is accessed. */
	configASSERT( xTotalHeapSize );

	/* Work out the position of the top bit in a size_t variable. */
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

經過一些對齊操作,將 demo 中的 3 塊內存通過鏈表的方式掛接起來了,只不過內存地址不連續而已,但是對於連續的內存地址中,照樣在釋放的時候,可以進行合併操作;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章