日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

两种内存池管理方法对比

發布時間:2024/1/1 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 两种内存池管理方法对比 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

一、問題背景

最近在調試ambiq apollo3的藍牙時,其使用了ARM Cordio WSF的藍牙協議棧。通過學習wsf_buf.c的實現,看到了一種不同于固定大小內存塊的內存池管理方式。它使用了可變大小的內存塊分配,支持內存塊大小自定義。為了學習其內存管理思想,故特此記錄下這兩種內存池管理方式的差異。本文將分別介紹了這兩種內存池管理方法的實現方式,最后對比兩種方式的優缺點和適用場景。

?

二、兩種內存池管理

2.1 固定大小內存塊分配(參考正點原子STM32F4 malloc.c)

固定大小內存塊的方式,簡單的說就是將內存按照相同大小劃分成若干個內存塊。每一個內存塊有一個對應的內存管理表。如果對應的內存管理表為0,則標志未使用。非零表示此內存已經被分配使用中。

分塊式內存管理由內存池和內存管理表兩部分組成。內存池被等分為 n塊,對應的內存管理表,大小也為 n,內存管理表的每一個項對應內存池的一塊內存。

內存管理表的項值代表的意義為:當該項值為 0 的時候,代表對應的內存塊未被占用,當該項值非零的時候,代表該項對應的內存塊已經被占用,其數值則代表被連續占用的內存塊數。比如某項值為 10,那么說明包括本項對應的內存塊在內,總共分配了 10 個內存塊給外部的某個指針。

內寸分配方向如圖所示,是從頂->底的分配方向。即首先從最末端開始找空內存。當內存管理剛初始化的時候,內存表全部清零,表示沒有任何內存塊被占用。

? ? ? ? ? ? ? ? ? ? ???

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖1 內存塊物理上分布圖

2.1.1 初始化

在移植這種內存池分配前,我們需要預先定義待分配的內存物理地址和內存塊大小。如下所示,在STM32F4 MCU中我們有內部SRAM、內部CCRAM和外部SRAM,共三處內存。通過MEMx_BLOCK_SIZE定義內存塊的大小,我們這里將這三處內存都設置為32byte。

MEMx_MAX_SIZE表示內存管理大小。而內存表由于和內存一一對應,故MEMx_MAX_SIZE/MEMx_BLOCK_SIZE就能得到實際的內存表大小(個數)。

//malloc.h 頭文件定義: //mem1內存參數設定.mem1完全處于內部SRAM里面. #define MEM1_BLOCK_SIZE 32 //內存塊大小為32字節 #define MEM1_MAX_SIZE 20*1024 //最大管理內存 100K #define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //內存表大小//mem2內存參數設定.mem2處于CCM,用于管理CCM(特別注意,這部分SRAM,僅CPU可以訪問!!) #define MEM2_BLOCK_SIZE 32 //內存塊大小為32字節 #define MEM2_MAX_SIZE 60 *1024 //最大管理內存60K #define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE //內存表大小//mem3內存參數設定.mem3的內存池處于外部SRAM里面 #define MEM3_BLOCK_SIZE 32 //內存塊大小為32字節 #define MEM3_MAX_SIZE 960 *1024 //最大管理內存960K #define MEM3_ALLOC_TABLE_SIZE MEM3_MAX_SIZE/MEM3_BLOCK_SIZE //內存表大小// malloc.c源碼定義 //內存池(32字節對齊) __align(32) u8 mem1base[MEM1_MAX_SIZE]; //內部SRAM內存池 __align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X10000000))); //內部CCM內存池 __align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X68000000))); //外部SRAM內存池 //內存管理表 u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //內部SRAM內存池MAP u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X10000000+MEM2_MAX_SIZE))); //內部CCM內存池MAP u16 mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM3_MAX_SIZE))); //外部SRAM內存池MAP //內存管理參數 const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE,MEM3_ALLOC_TABLE_SIZE};//內存表大小 const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE,MEM3_BLOCK_SIZE};//內存分塊大小 const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE,MEM3_MAX_SIZE};

初始化時,只用將內存池和內存管理表都清零即可。

//內存管理初始化 //memx:所屬內存塊 void my_mem_init(u8 memx) { mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//內存狀態表數據清零 mymemset(mallco_dev.membase[memx], 0,memsize[memx]);//內存池所有數據清零 mallco_dev.memrdy[memx]=1; //內存管理初始化OK }

2.1.2 分配原理

當指針 p 調用 malloc 申請內存的時候,先判斷 p 要分配的內存塊數(m),然后從第 n 項開始,向下查找,直到找到 m 塊連續的空內存塊(即對應內存管理表項為 0),然后將這 m 個內存管理表項的值都設置為 m(標記被占用),最后,把最后的這個空內存塊的地址返回指針 p,完成一次分配。注意,如果當內存不夠的時候(找到最后也沒找到連續的 m 塊空閑內存),則返回 NULL 給 p,表示分配失敗。

//內存分配(內部調用) //memx:所屬內存塊 //size:要分配的內存大小(字節) //返回值:0XFFFFFFFF,代表錯誤;其他,內存偏移地址 u32 my_mem_malloc(u8 memx,u32 size) { signed long offset=0; u32 nmemb; //需要的內存塊數 u32 cmemb=0;//連續空內存塊數u32 i; if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先執行初始化 if(size==0)return 0XFFFFFFFF;//不需要分配nmemb=size/memblksize[memx]; //獲取需要分配的連續內存塊數if(size%memblksize[memx])nmemb++; for(offset=memtblsize[memx]-1;offset>=0;offset--)//搜索整個內存控制區(每次都要從頭開始搜索,而且不排序,效率低--yulong) {if(!mallco_dev.memmap[memx][offset]){cmemb++;//連續空內存塊數增加(為0)}else {cmemb=0;} //連續內存塊清零if(cmemb==nmemb) //找到了連續nmemb個空內存塊{for(i=0;i<nmemb;i++) //標注內存塊非空 (每次申請,釋放都要標記,效率低.不如鏈表高效 --yulong){ mallco_dev.memmap[memx][offset+i]=nmemb; } return (offset*memblksize[memx]);//返回偏移地址 }} return 0XFFFFFFFF;//未找到符合分配條件的內存塊 } //分配內存(外部調用) //memx:所屬內存塊 //size:內存大小(字節) //返回值:分配到的內存首地址. void *mymalloc(u8 memx,u32 size) { u32 offset;offset=my_mem_malloc(memx,size); if(offset==0XFFFFFFFF)return NULL; else return (void*)((u32)mallco_dev.membase[memx]+offset); //返回字節 }

2.1.3?釋放原理

當 p 申請的內存用完,需要釋放的時候,調用 free 函數實現。 free 函數先判斷 p 指向的內存地址所對應的內存塊,然后找到對應的內存管理表項目,得到 p 所占用的內存塊數目 m(內存管理表項目的值就是所分配內存塊的數目),將這 m 個內存管理表項目的值都清零,標記釋放,完成一次內存釋放。

//釋放內存(內部調用) //memx:所屬內存塊 //offset:內存地址偏移 //返回值:0,釋放成功;1,釋放失敗; u8 my_mem_free(u8 memx,u32 offset) { int i; if(!mallco_dev.memrdy[memx])//未初始化,先執行初始化{mallco_dev.init(memx); return 1;//未初始化 } if(offset<memsize[memx])//偏移在內存池內. { int index=offset/memblksize[memx]; //找到偏移所在內存塊號碼 。int nmemb=mallco_dev.memmap[memx][index]; //得到,內存塊數量for(i=0;i<nmemb;i++) //內存塊清零{ mallco_dev.memmap[memx][index+i]=0; } return 0; }else return 2;//偏移超區了. } //釋放內存(外部調用) //memx:所屬內存塊 //ptr:內存首地址 u8 myfree(u8 memx,void *ptr) {u8 res=0;u32 offset; if(ptr==NULL) return 3;//地址為0. offset=(u32)ptr-(u32)mallco_dev.membase[memx]; res = my_mem_free(memx,offset); //釋放內存 return res; }

2.2 可變大小內存塊分配(參考WSF BLE協議棧buffer management)

可見,上面固定大小內存塊方式在查找和釋放時,都需要遍歷、置/清內存表標志位,比較費時間。而可變大小內存塊的思想是:將待分配的內存分為大、中、小這么幾類。每一類由相同大小的內存塊通過一個單鏈表鏈接在一起組成。初始化后,空閑鏈表*pFree就指向這個單鏈表頭。

當需要申請內存的時候,按照從小到大的順序,在這幾類里面找到剛好能夠容納下申請內存的buf。如果找到,就直接將這類內存塊的空閑鏈表頭返回給它即可。然后空閑鏈表頭指向next即可完成內存的申請。

在釋放的時候,按照從大到小的順序,找到和待釋放的內存塊大小相同的那類內存塊,將其重新插入那類內存塊空閑鏈表頭,即完成釋放。

這就是可變大小內存池的分配基本思路。當然我們這里舉例是只有大、中、小三類的內存塊,實際可根據自己的需求增加,當然可以更多大小不同的內存塊。

2.2.1 初始化

在實現方面,由內存低地址的內存池描述結構體poolDescriptor和跟在后面的pool buffer(數據實際存放位置)組成。一個poolDescripter數據項對應一個pool buffer。

對于可變大小的內存池分配,用戶在使用前,需要先配置內存池描述結構體。在大小上一般我們傾向于將其設置為4個字節的倍數,方便單片機內存32位對齊。然后由小變大的原則,16、32、64、280...。在數量上根據就需要根據自己的內存使用量預估,找到合適的個數。

內存池描述結構體wsfBufPool_結構如下:

/*! Buffer pool descriptor structure */ typedef struct {uint16_t len; /*! length of buffers in pool */uint8_t num; /*! number of buffers in pool */ } wsfBufPoolDesc_t;/* Internal buffer pool */ typedef struct {wsfBufPoolDesc_t desc; /* number of buffers and length */wsfBufMem_t *pStart; /* start of pool */wsfBufMem_t *pFree; /* first free buffer in pool */ } wsfBufPool_t;

如下所示,前面為每個內存池buf大小(單位為word),后面為此大小的內存池個數。一共定義是4個內存此描述結構體。即16個word的內存有8個(16*4*8byte=512byte), 32個word的內存有4個,64個word的內存有6個,280個word的內存有4個。

// Default pool descriptor. static wsfBufPoolDesc_t g_psPoolDescriptors[WSF_BUF_POOLS] = {{ 16, 8 },{ 32, 4 },{ 64, 6 },{ 280, 4 } };

在內存初始化時,初始化函數就會讀取這個用戶自定義的內存分配描述結構體,初始化內存池。

如上面設置了pool的個數為4個,依次初始化位于內存低地址的內存管理結構體poolDescriptor:

跟著其后面的是buffer storage。buffer storage需要初始化為大小相同內存塊,通過一個單向鏈表連接在一起,最后一塊內存的*pNext指向NULL。*pStart會一直指向這個pool的頭地址,用于保存每個內存池起始位置。*pFree會隨著alloc和free移動,保證始終指向空閑的內存塊。

按照上面的結構體,初始化后的整體內存分布如下:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖2 內存物理上總體分布圖

精簡后的初始化源代碼:

/*************************************************************************************************/ /*!* \fn WsfBufInit** \brief Initialize the buffer pool service. This function should only be called once* upon system initialization.** \param bufMemLen Length of free memory* \param pBufMem Free memory buffer for building buffer pools* \param numPools Number of buffer pools.* \param pDesc Array of buffer pool descriptors, one for each pool.** \return Amount of pBufMem used or 0 for failures.*/ /*************************************************************************************************/ uint16_t WsfBufInit(uint16_t bufMemLen, uint8_t *pBufMem, uint8_t numPools, wsfBufPoolDesc_t *pDesc) {wsfBufPool_t *pPool;wsfBufMem_t *pStart;uint16_t len;uint8_t i;wsfBufMem = (wsfBufMem_t *) pBufMem;pPool = (wsfBufPool_t *) wsfBufMem; //internal buffer management structure.在bufMem的最前面。 --yulong/* buffer storage starts after the pool structs */pStart = (wsfBufMem_t *) (pPool + numPools); //具體分派內存的起始地址。wsfBufNumPools = numPools; //初始化pool數目4/* create each pool; see loop exit condition below */while (TRUE){/* exit loop after verification check */if (numPools-- == 0){break;}/* adjust pool lengths for minimum size and alignment。調整最小4字節,以及對其 *///.....pPool->desc.num = pDesc->num;pDesc++; //遍歷入參Desc的每個內存池描述DescpPool->pStart = pStart; //初始化起始地址和free地址pPool->pFree = pStart;/* initialize free list */len = pPool->desc.len / sizeof(wsfBufMem_t); // 4字節for (i = pPool->desc.num; i > 1; i--){/* pointer to the next free buffer is stored in the buffer itself */pStart->pNext = pStart + len;pStart += len;}/* last one in list points to NULL */pStart->pNext = NULL;pStart += len; //跳到這個內存池的末尾/* next pool */pPool++;}wsfBufMemLen = (uint8_t *) pStart - (uint8_t *) wsfBufMem;WSF_TRACE_INFO1("Created buffer pools; using %u bytes", wsfBufMemLen);return wsfBufMemLen; }

2.2.2 分配原理

從內存池描述結構的頭開始,根據pPool->desc.len,按len從小往大的找。直到找到能夠容納下申請內存大小的那個pool,然后將這個pool的pFree賦給pBuf,同時將pFree指向下一個空閑塊pNext。最后返回pBuf即完成內存申請。核心代碼下面兩句:

/* allocation succeeded */ pBuf = pPool->pFree;/* next free buffer is stored inside current free buffer */ pPool->pFree = pBuf->pNext;

值得注意的是,雖然每個空閑內存塊為了維護鏈表,前面都有一個*pNext指針和magic num(可選),但是當分配給用戶后,由于其不再需要組織成一個鏈表,所以*pNext的值沒有任何意義,而magic num是free過后才賦值的。因此用戶在使用時,可以使用全部的內存塊,不會為了維護鏈表,造成額外的空間浪費。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖3 內存塊復用內存空間

去掉輔助功能后的精簡代碼:

/*************************************************************************************************/ /*!* \fn WsfBufAlloc* \brief Allocate a buffer.* \param len Length of buffer to allocate.* \return Pointer to allocated buffer or NULL if allocation fails.*/ /*************************************************************************************************/ void *WsfBufAlloc(uint16_t len) {wsfBufPool_t *pPool;wsfBufMem_t *pBuf;uint8_t i;WSF_CS_INIT(cs); //線程保護,準備進入臨界區pPool = (wsfBufPool_t *) wsfBufMem; // 獲得前面的內存池描述結構頭for (i = wsfBufNumPools; i > 0; i--, pPool++) //從小->大找pool池{/* if buffer is big enough */if (len <= pPool->desc.len) //如果找到了就分配,沒有找到找下一個更大的pool池{/* enter critical section */WSF_CS_ENTER(cs);/* if buffers available */if (pPool->pFree != NULL){/* allocation succeeded */pBuf = pPool->pFree;/* next free buffer is stored inside current free buffer */pPool->pFree = pBuf->pNext;/* exit critical section */WSF_CS_EXIT(cs);WSF_TRACE_ALLOC2("WsfBufAlloc len:%u pBuf:%08x", pPool->desc.len, pBuf);return pBuf;}/* exit critical section */WSF_CS_EXIT(cs);}}return NULL; }

2.2.3 釋放原理

需要釋放相應的內存塊,釋放時,直接將內存塊重新加入空閑塊鏈表即可。由于有好幾種不同大小的內存池,所以會先查找這個內存塊屬于哪一個內存池。釋放時會先初始化pPool為內存最后(內存最大)的那個內存desc pool,從后向前找。當pBuf>= pPool->pStart時,說明待釋放的內存就在這個pool里面。所以執行下面兩行即可:

/* pool found; put buffer back in free list */ // 注:*p為待free的內存地址 p->pNext = pPool->pFree; pPool->pFree = p;

如果開啟了Free檢測,這會檢查magic num,避免被多次Free,造成錯誤。

精簡后的代碼如下:

/*************************************************************************************************/ /*!* \fn WsfBufFree* \brief Free a buffer.* \param pBuf Buffer to free.* \return None.*/ /*************************************************************************************************/ void WsfBufFree(void *pBuf) {wsfBufPool_t *pPool;wsfBufMem_t *p = pBuf;WSF_CS_INIT(cs);/* iterate over pools starting from last pool */pPool = (wsfBufPool_t *) wsfBufMem + (wsfBufNumPools - 1); // 從尾部desc倒著遍歷buf。while (pPool >= (wsfBufPool_t *) wsfBufMem){/* if the buffer memory is located inside this pool */if (p >= pPool->pStart){/* enter critical section */WSF_CS_ENTER(cs);/* pool found; put buffer back in free list */p->pNext = pPool->pFree;pPool->pFree = p;/* exit critical section */WSF_CS_EXIT(cs);WSF_TRACE_FREE2("WsfBufFree len:%u pBuf:%08x", pPool->desc.len, pBuf);return;}/* next pool */pPool--;}return; }

三、對比和總結

通過對比,可以看出這兩種內存管理方式實現區別是:一種用位圖(這里用2個byte當做一個位標記),一種用鏈表。

這兩種分配方式各有優缺點,在實際應用時, 可根據需求作出相應的選擇。如當注重實時性和可靠性,而且申請內存大小范圍已知可控,那我們可以選擇可變大小的內存塊。當注重靈活性時,可以選擇固定大小的內存塊,雖然這會犧牲點效率。

當然想要兼容這兩種,可以嘗試混合使用呀。比如:如果申請和內存池大小接近的內存就使用可變大小內存池。而其他的較大的,或者較小的就在固定大小內存池里去遍歷申請。

?優點缺點
固定大小內存塊1.靈活度高,能以較少的內存碎片,分配各種大小的內存;1.效率低,每次都要從尾部遍歷一遍memmap,找合適大小的內存,置/清內存表;
可變大小內存塊1.申請和釋放速度快,效率高;

1.需提前估算大概的內存需求大小;

2.靈活度不夠,分配不好很容易內存碎片;

如有不妥之處,歡迎指正!謝謝

總結

以上是生活随笔為你收集整理的两种内存池管理方法对比的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。