新聞中心
1.內(nèi)存劃分
- 棧區(qū)(stack)有時也稱為堆棧,重點在棧字,存放函數(shù)內(nèi)部臨時變量。
- 堆區(qū)(heap)也就是動態(tài)申請(malloc)、釋放(free)的內(nèi)存區(qū)域。
- 數(shù)據(jù)區(qū)(data)初始化的全局變量和靜態(tài)變量, 占用可執(zhí)行文件空間;rodata 固定不變const修飾的全局變量,不占內(nèi)存空間。
- bss區(qū)未初始化的全局變量、靜態(tài)變量(static關(guān)鍵字描述的),初始化為全0的全局變量,不占用可執(zhí)行文件大小。
- 代碼區(qū)(text)程序二進制文件。
最終下載的可執(zhí)行文件包括代碼(text)和數(shù)據(jù)(data)。內(nèi)存的分配一般如下圖:

其中堆和棧的地址分配方向相反,棧比較特殊,下面微信公眾號【嵌入式系統(tǒng)】以??臻g異常使用為例:
#include
int main(void)
{
int a=100;
int b[3]={0};
int c=200;
printf("ori> a[%p]=%d,c[%p]=%d\r\n",&a,a,&c,c);
printf(" > b[%p]\r\n",&b);
b[0]=0;
b[1]=1;
b[2]=2;
b[3]=3;//error ->a
printf("new> a[%p]=%d,c[%p]=%d\r\n",&a,a,&c,c);
return 0;
}
運行結(jié)果:
ori> a[0028FEBC]=100,c[0028FEAC]=200
> b[0028FEB0]
new> a[0028FEBC]=3,c[0028FEAC]=200
結(jié)合打印的變量地址,??臻g分配如下圖,因為數(shù)組b的操作越界,導致了變量a的值被覆蓋。
針對個人情況,一般情況下內(nèi)存溢出都是使用數(shù)組越界,所以在異常值后或者前查看有沒數(shù)組(全局變量可以查map文件),檢查數(shù)組的操作是否正確。
除了堆區(qū),其他幾個區(qū)都是有編譯器和系統(tǒng)運行時自動處理的,而堆區(qū)由開發(fā)者來操作的。這既是便利,也是隱患,一旦操作失誤就是內(nèi)存泄漏或溢出。
2.動態(tài)內(nèi)存管理
在硬件資源固定的情況下,棧和堆的空間此消彼長,合理的定義堆的空間,為不同任務(wù)分配合適的??臻g也是至關(guān)重要的。以FreeRTOS內(nèi)核代碼為例,《FreeRTOS及其應(yīng)用》分別解讀其5種動態(tài)內(nèi)存,也就是堆的分配方式,其他系統(tǒng)的原理差不多。參考Guide文檔 https://www.freertos.org/Documentation/RTOS_book.html Guide。
FreeRTOS 內(nèi)核提供了 5 種內(nèi)存管理算法,源文件在Source\portable\MemMang 下,使用時選擇其中一個。
heap_1.c內(nèi)存管理方案簡單,它只能申請內(nèi)存而不能進行內(nèi)存釋放。
一些低端嵌入式系統(tǒng)并不會經(jīng)常動態(tài)申請與釋放內(nèi)存,在系統(tǒng)啟動后申請,一直使用下去,永不釋放,適合這種方式,也可近似理解為多個全局小數(shù)組合并的使用。
heap_2.c 方案支持申請和釋放,但是它不能把相鄰的兩個小的內(nèi)存塊合成一個大的內(nèi)存塊, 隨著不斷的申請釋放,空閑空間會分割為很多小片段,如下圖:
持續(xù)申請、釋放一定次數(shù),就會出現(xiàn)剩余空間的和較大,但卻申請不到內(nèi)存的情況,如上圖剩余空間是900,但無法申請600,因為沒有連續(xù)的600空間。如果每次申請內(nèi)存大小都是固定的,就不存在內(nèi)存碎片問題,但實際不會這樣,因此不推薦。
heap_3.c 方案只是封裝了標準 C 庫中的 malloc()和 free()函數(shù),由編譯器提供,需要通過編譯器或者啟動文件設(shè)置堆空間,封裝是為了保證線程安全。
heap_4.c 方案是在heap_2.c 基礎(chǔ)上,對內(nèi)存碎片進行了改進。
如圖E到F,用戶釋放后,把相鄰的空閑的內(nèi)存塊合并成一個更大的塊,這樣可以減少內(nèi)存碎片。
heap_5.c 方案在實現(xiàn)動態(tài)內(nèi)存分配時與 heap4.c 方案一樣,采用最佳匹配算法和合并算法,并且允許內(nèi)存堆跨越多個非連續(xù)的內(nèi)存區(qū),也就是允許在不連續(xù)的內(nèi)存堆中實現(xiàn)內(nèi)存分配,比如做圖形顯示,可能芯片內(nèi)部的 RAM 不足,額外擴展SDRAM,這種內(nèi)存管理方案則比較合適。
一般選用heap_4.c。
3.動態(tài)內(nèi)存防御性編程
內(nèi)存只申請不釋放,運行一段時間會因為內(nèi)存不足而無法運行,即內(nèi)存泄露;或者操作的內(nèi)存區(qū)域超出了申請的空間,訪問越界即內(nèi)存溢出,導致各種隨機異常。對于內(nèi)存操作的不穩(wěn)定因素,如何進行防御性編程,可以在調(diào)試階段發(fā)現(xiàn)問題?
簡單的說就是內(nèi)存分配的時候,記錄申請內(nèi)存的函數(shù)名(或者擴展加上申請時間),申請內(nèi)存大小的基礎(chǔ)上額外增加空間,在其首尾加入特殊的標志位,釋放該內(nèi)存前對標志位進行校驗;如果校驗不通過,則將申請該內(nèi)存的函數(shù)名打印出來,表示出現(xiàn)了內(nèi)存溢出。也支持隨時打印當前動態(tài)內(nèi)存的使用情況,查看某些函數(shù)申請的內(nèi)存釋放一直未被釋放,人工判斷是否內(nèi)存泄露。
下面是完整源碼:
//pal_memory.h
#ifndef _PAL_MEMORY_H
#define _PAL_MEMORY_H
//配置是否開啟內(nèi)存記錄功能
#define __MEMORY_DEBUG__
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
extern void *chengj_pal_memory_malloc(uint32_t size, const char *func);
extern void chengj_pal_memory_free(void **pv);
extern void chengj_pal_memory_record_print(void);
#define chengj_malloc(size) chengj_pal_memory_malloc(size, __FUNCTION__)
#define chengj_free(pv) chengj_pal_memory_free(&pv)
#endif /* _PAL_MEMORY_H */
具體實現(xiàn):
/**********************************************************************
*
* Copyright(c) embedded-systems rights reserved
*
* Description:
* memory management
*
* [微信公眾號: 嵌入式系統(tǒng)]
*
*********************************************************************/
#include
#include
#include "pal_memory.h"
//適配平臺內(nèi)存管理接口
#define PAL_MALLOC malloc
#define PAL_FREE free
#if defined (__MEMORY_DEBUG__)
#define MEMORY_RECORD_COUNT_MAX 100
//len[4]+head[4]+...[data]...+tail[2]
#define MEMORY_EXTRA_SIZE 10
//magic
#define MEMORY_DATA_MAGIC_HEAD 0x43
#define MEMORY_DATA_MAGIC_TAIL 0x4A
typedef struct
{
const char *func_name;
void *pointer;
//可擴展保存 時間戳 等信息
} memory_record_struct;
//記錄申請內(nèi)存的函數(shù)
static memory_record_struct chengj_memory_record[MEMORY_RECORD_COUNT_MAX] = {0};
#endif /* __MEMORY_DEBUG__ */
/*
*輸出未被釋放的申請函數(shù)名和指針地址
*/
void chengj_pal_memory_record_print(void)
{
#if defined (__MEMORY_DEBUG__)
uint32_t i = 0;
for(; i < MEMORY_RECORD_COUNT_MAX; i++)
{
if(chengj_memory_record[i].pointer != NULL)
{
printf("[%d] %s()\r\n", i, chengj_memory_record[i].func_name);
}
}
#endif /* __MEMORY_DEBUG__ */
}
/*
*malloc
*/
void *chengj_pal_memory_malloc(uint32_t size, const char *func)
{
void *pv = NULL;
uint32_t i = 0;
#if defined (__MEMORY_DEBUG__)
uint8_t *pdata;
#endif
if(size == 0 || func == NULL)
{
return NULL;
}
#if defined (__MEMORY_DEBUG__)
size = size + MEMORY_EXTRA_SIZE;
#endif
pv = PAL_MALLOC(size);
if(pv == NULL)
{
return NULL;
}
memset(pv, 0, size);
#if defined (__MEMORY_DEBUG__)
pdata = (uint8_t *)pv;
pdata[0] = (size >> 24) & 0xFF;
pdata[1] = (size >> 16) & 0xFF;
pdata[2] = (size >> 8) & 0xFF;
pdata[3] = size & 0xFF;
pdata[4] = MEMORY_DATA_MAGIC_HEAD;
pdata[5] = MEMORY_DATA_MAGIC_HEAD;
pdata[6] = MEMORY_DATA_MAGIC_HEAD;
pdata[7] = MEMORY_DATA_MAGIC_HEAD;
pdata[size - 2] = MEMORY_DATA_MAGIC_TAIL;
pdata[size - 1] = MEMORY_DATA_MAGIC_TAIL;
for(; i < MEMORY_RECORD_COUNT_MAX; i++) //過多不記錄
{
if(chengj_memory_record[i].pointer == NULL)
{
chengj_memory_record[i].func_name = func;
chengj_memory_record[i].pointer = pv;
break;
}
}
return &pdata[8];
#else
return pv;
#endif /* __MEMORY_DEBUG__ */
}
/*
*free
*/
void chengj_pal_memory_free(void **pv)
{
uint32_t i = 0;
#if defined (__MEMORY_DEBUG__)
uint32_t size;
uint8_t *pdata;
uint8_t error = 0;
#endif
if(pv == NULL || *pv == NULL)
{
return;
}
#if defined (__MEMORY_DEBUG__)
pdata = (uint8_t *)(*pv) - 8;
*pv = (void*)pdata;
size = ((pdata[0] << 24) | (pdata[1] << 16) | (pdata[2] << 8) | (pdata[3]));
if(size <= MEMORY_EXTRA_SIZE)
{
error = error | 0x01;
}
if((pdata[4] != MEMORY_DATA_MAGIC_HEAD) || (pdata[5] != MEMORY_DATA_MAGIC_HEAD)\
|| (pdata[6] != MEMORY_DATA_MAGIC_HEAD) || (pdata[7] != MEMORY_DATA_MAGIC_HEAD))
{
error = error | 0x02;
}
if((pdata[size - 2] != MEMORY_DATA_MAGIC_TAIL) || (pdata[size - 1] != MEMORY_DATA_MAGIC_TAIL))
{
error = error | 0x04;
}
for(; i < MEMORY_RECORD_COUNT_MAX; i++)
{
if(chengj_memory_record[i].pointer == *pv)
{
if(error != 0)
{
if(chengj_memory_record[i].func_name != NULL)
{
printf("memory error 0x%02X %s()\r\n", error, chengj_memory_record[i].func_name);
}
else
{
printf("memory error 0x%02X %p\r\n", error, *pv);
}
}
memset(&chengj_memory_record[i], 0, sizeof(memory_record_struct));
break;
}
}
if(error != 0)
{
//ASSERT調(diào)試
return;
}
#endif /* CY_PAL_MEMORY_DEBUG */
PAL_FREE(*pv);
*pv = NULL;
return;
}
可以測試下效果:
#include "pal_memory.h"
//微信公眾號: 嵌入式系統(tǒng)
//申請10字節(jié)但使用20字節(jié)
void test(void)
{
uint8_t *p;
uint8_t i;
p=chengj_malloc(10);
for(i=0;i<20;i++)
{
p[i]=i;
}
chengj_free(p);
}
int main(int argc, char *argv[])
{
printf("embedded-system \r\n");
test();
return 0;
}
運行結(jié)果:
embedded-system
memory error 0x04 test()
表示test函數(shù)內(nèi)申請的一段內(nèi)存使用時溢出,尾部標記數(shù)據(jù)被覆蓋。
也可以在memory_record_struct增加時間戳成員,記錄內(nèi)存申請時間,再擴展void chengj_pal_memory_record_print(void) 打印內(nèi)存使用情況,查看長時間申請未釋放的內(nèi)存使用情況。針對日志信息縮小內(nèi)存異常的范圍,關(guān)于軟件bug調(diào)試與解決,可以參考《嵌入式軟件bug從哪來,怎么去》。
4.小節(jié)
內(nèi)存記錄調(diào)試方法,浪費了一定量的內(nèi)存空間,而且不能排除問題,只是提早監(jiān)測到異常,但對軟件穩(wěn)定性仍有較大意義,可以快速解決內(nèi)存問題。建議只在debug版本啟用,正式發(fā)布的release版本關(guān)閉記錄功能。
網(wǎng)站題目:動態(tài)內(nèi)存管理及防御性編程
當前路徑:http://m.fisionsoft.com.cn/article/cdssssc.html


咨詢
建站咨詢
