CC2541堆内存布局与合理分配其大小分析
时间:10-02
整理:3721RD
点击:
CC2541堆内存布局与合理分配其大小分析
很多人使用CC2541的时候都会遇到一个问题,CC2541的内存是有限的,不够我们使用,特别是做开发的朋友,那么我们应该如何合理分配这些有限的资源呢?
下面是引用TI的官方文档 TI_BLE_Software_Developer's_Guide.pdf 中的一段关于Heap Manager的话。 OSAL also provides basic memory management functions. The osal_mem_alloc() function serves as a memory allocation function similar to the standard C malloc function: it takes a single parameter specifying the number of bytes to allocate and returns a void pointer if successful. If no memory is available, a NULL pointer will be returned. Similarly, the osal_mem_free() function works similar to the standard C free function: it frees memory that was previously allocated using osal_mem_alloc().
The pre-processor define INT_HEAP_LEN is used to reserve memory for dynamic allocation.
To see profile the run-time memory usage, you can set the pre-processor define OSALMEM_METRICS=TRUE in the project options. After a stress test of the application where you send as many messages, have as many clients as you will in the worst case, remembering to use bonding and encryption during the test if that’s applicable, etc., you can look at the value of the variable memMax in OSAL_Memory.c to see the maximum amount memory was ever allocated at a given time. This figure can be used as a guideline for lowering INT_HEAP_LEN. However, thorough testing is needed as the heap is also used by the BLE stack.
从中可以概括出3点:
1 osal_mem_alloc()和osal_mem_free()的用法类似标准C语言中的malloc和free函数。
2 堆内存的大小可以通过INT_HEAP_LEN来配置。
3 可以在工程选项里定义OSALMEM_METRICS=TRUE,通过该宏使能的代码查看堆内存实时的使用情况,从而以此为指导配置出适合自己系统的堆大小。
如果需要详细的掌握stack里堆内存的管理细节,可以参考官方文档 Heap_Memory_Management.pdf。
在change-Id I7399c1f74ed9ba611080978bb444869736238514之前所有的代码,对于堆内存配置的大小都是3K,在工程的pre-processor可以看到下面的宏定义:
INT_HEAP_LEN=3072
对于3K的大小是否适合我们的系统呢?
下面分析堆内存布局和当前系统堆内存利用率这两个方面,来进一步解答这个问题
堆内存布局
首先,我们需要明确的是堆内存位于物理介质SRAM当中。CC2541是哈佛结构,代码空间与数据空间是独立寻址的。SRAM位于数据空间中,其映射方式下图
SRAM被映射到xdata space的前8K(当前所用的CC2541SRAM大小为8K),地址范围是0x0000 - 0x1FFF。
我们的heap配置的是3K大小,那它到底位于SRAM的哪里呢?
在OSAL_Memory.c中,找到下面的代码:
static osalMemHdr_t theHeap[MAXMEMHEAP / OSALMEM_HDRSZ];
static osalMemHdr_t *ff1; // First free block in the small-block bucket.
这个数组就是整个的堆内存空间,只要找到theHeap的地址就能知道对内存的地址范围。要获取theHeap的地址最简单的方法就是查看编译后生成的.map文件。下面是我当时摘取map文件中关于theHeap的片段:
XDATA_Z
Relative segment, address: XDATA 00000A79 - 00001678 (0xc00 bytes), align: 0
Segment part 7. Intra module refs: osal_mem_alloc
osal_mem_init
LOCAL ADDRESS
===== =======
theHeap 00000A79
十六进制的0xc00等于3072,刚好是我们配置的堆内存大小,其地址范围是0x0A79 - 0x1678。到目前就知道了如下两点:
heap位于ram当中;
heap的地址范围是0x0A79 - 0x1678,这个范围在每次修改程序后都有可能改变;
如何合理分配heap的大小
关于如何合理的分配heap的大小,这个得根据实际的情况来确定。这个在引言中有提及,那就是利用OSALMEM_METRICS使能的代码。OSALMEM_METRICS宏在stack正常的工作时,是默认为False的,这是因为该宏使能的代码回影响程序的性能。下面做具体的介绍,在OSAL_Memory.c中有如下的代码:
#if OSALMEM_METRICS
static uint16 blkMax; // Max cnt of all blocks ever seen at once.
static uint16 blkCnt; // Current cnt of all blocks.
static uint16 blkFree; // Current cnt of free blocks.
static uint16 memAlo; // Current total memory allocated.
static uint16 memMax; // Max total memory ever allocated at once.
#endif
上面几个变量就是用作统计堆内存使用情况的,详细的用法参考OSAL_Memory.c中的代码。正对目前要解答的问题,如何配置INT_HEAP_LEN的大小,最傻瓜的方式就是尽量给它一个比较大的值,只要不导致当前的程序运行奔溃。但是当系统RAM满足的不了要求时,就可以从减少堆的大小来解决问题。利用memMax变量就可以知道当前系统实时分配过的最大总内存,通过memAlo知道正在运行程序中有多少堆内存正在被占用。这样就可以通过memMax知道当前系统堆内存的上限,INT_HEAP_LEN的取值只要保证比它大一些即可。memAlo/INT_HEAP_LEN就可以表示堆内存的利用率。获取memAlo和memMax的API如下:
/*
* Return the current number of bytes allocated.
*/
uint16 osal_heap_mem_used( void );
/*
* Return the highest number of bytes ever used in the heap.
*/
uint16 osal_heap_high_water( void );
下面我就change-id为I7399c1f74ed9ba611080978bb444869736238514的代码进行的实验。我的思路是:起一个定时器周期性的调用一个函数,该函数就是专门用来获取当前堆内存的统计信息,并使用串口打印出来。在这个实验中,调用osal_heap_mem_used和osal_heap_high_water函数的时间对实验结果没有影响,因为在堆内存管理初始化好了之后有程序调用osal_mem_alloc函数,这些内存使用信息就会被记录。
我的实验代码主要分两块:
void heapMetrics(uint8 *arg)
{
uint16 memAlo, memMax, useAge;
uint8 disp[UINT16_DEC_STRING_MAX_LEN];
VOID arg;
NPI_WriteTransport("memAlo: ", 8);
memAlo = osal_heap_mem_used();
int2string(memAlo, disp);
NPI_WriteTransport(disp, osal_strlen(disp));
NPI_WriteTransport("\n", 1);
NPI_WriteTransport("memMAX: ", 8);
memMax = osal_heap_high_water();
int2string(memMax, disp);
NPI_WriteTransport(disp, osal_strlen(disp));
NPI_WriteTransport("\n", 1);
NPI_WriteTransport("useAge: ", 8);
useAge = (uint16)(((float)memAlo / 1024)*1000); // 如果useAge=119,则表示11.9%
int2string(useAge, disp);
NPI_WriteTransport(disp, osal_strlen(disp));
NPI_WriteTransport("\n", 1);
NPI_WriteTransport("**** **** **** ****\n", 17);
}
osal_CbTimerStartReload(heapMetrics, NULL, 350, NULL); // 350ms的回调定时器
下面记录了一个刚烧录程序的设备从上电——生产——正常工作整个过程的log: 【2016-03-17 17:52:54:190】Hello World
【2016-03-17 17:52:54:291】BLE Peripheral
A
【2016-03-17 17:52:54:647】memAlo: 358
memMAX: 388 useAge: 116
**** **** **** **
【2016-03-17 17:52:54:997】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:55:347】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:55:696】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:56:046】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:56:396】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:56:747】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:57:097】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:57:448】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:57:797】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:58:147】memAlo: 343 memMAX: 388 useAge: 111
**** **** **** **
【2016-03-17 17:52:58:248】Connected
【2016-03-17 17:52:58:503】memAlo: 389 memMAX: 482 useAge: 126
**** **** **** **
Disconnected
【2016-03-17 17:52:58:604】Advertising
【2016-03-17 17:52:58:853】memAlo: 368 memMAX: 482 useAge: 119
**** **** **** **
【2016-03-17 17:52:59:203】memAlo: 368 memMAX: 482 useAge: 119
**** **** **** **
【2016-03-17 17:52:59:553】memAlo: 368 memMAX: 482 useAge: 119
**** **** **** **
【2016-03-17 17:52:59:903】memAlo: 368 memMAX: 482 useAge: 119
**** **** **** **
【2016-03-17 17:53:00:253】memAlo: 368 memMAX: 482 useAge: 119
**** **** **** **
【2016-03-17 17:53:02:857】Hello World
【2016-03-17 17:53:02:960】BLE Peripheral Advertising
【2016-03-17 17:53:03:180】Disconnected
【2016-03-17 17:53:03:281】memAlo: 328 memMAX: 388 useAge: 106
**** **** **** **
【2016-03-17 17:53:03:667】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
【2016-03-17 17:53:04:015】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
【2016-03-17 17:53:04:366】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
【2016-03-17 17:53:04:715】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
【2016-03-17 17:53:05:066】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
【2016-03-17 17:53:05:417】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
【2016-03-17 17:53:05:765】memAlo: 313 memMAX: 388 useAge: 101
**** **** **** **
从log中可以看出,在标签设备与手机连接通信的时候,会增加heap内存的消耗,但是最大也不过482个字节。所以从上面来看,我们给堆分配3072个字节是很浪费的。所以,堆内存完全可以调小一些,以节约内存。当前我调到了1024字节大小,运行没有任何问题。与上面一样的实验过程,log如下
Hello World
BLE Peripheral A