找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

帖子
查看: 4172|回復(fù): 8
收起左側(cè)

TI OSAL內(nèi)存管理實(shí)現(xiàn)方式

[復(fù)制鏈接]
ID:351097 發(fā)表于 2020-1-15 00:53 | 顯示全部樓層 |閱讀模式
本帖最后由 沒有你 于 2020-1-16 00:25 編輯

    之前發(fā)的帖子介紹了OSAL在STC8A8K64S4A12單片機(jī)的移植方式和簡(jiǎn)單使用,今天就簡(jiǎn)單介紹一下OSAL的內(nèi)存管理方式。在介紹之前,首先要了解單片機(jī)的棧和堆的區(qū)別。    一提到“�!�,很容易想到單片機(jī)的壓棧(PUSH)和出棧(POP)中用到的棧,這個(gè)棧是系統(tǒng)自動(dòng)分配和釋放的,具體分配多少棧空間,在程序編譯后就已經(jīng)確定不變了、而且無法更改。子函數(shù)內(nèi)部的局部變量定義和使用,也是系統(tǒng)自動(dòng)分配棧空間來保存變量,等函數(shù)執(zhí)行完,系統(tǒng)就會(huì)將棧空間收回。另一個(gè)“堆”,就沒有“�!蹦敲词煜ち耍�?yàn)槌鯇W(xué)者用單片機(jī)的時(shí)候,不會(huì)想到用堆的,也比較少接觸。“堆”就是用戶自己管理的內(nèi)存空間,一般都是定義一個(gè)大數(shù)組全局變量,再基于這個(gè)數(shù)組的內(nèi)存空間做管理。自己編寫類似malloc和free函來實(shí)現(xiàn)內(nèi)存空間的申請(qǐng)和釋放,即需要用到空間,要自己申請(qǐng),等空間使用周期結(jié)束了,還要自己釋放空間。
    既然 “�!蹦敲捶奖悖上到y(tǒng)自動(dòng)申請(qǐng),自動(dòng)釋放,肯定比自己去管理方便多了,那為什么還要“堆”呢?前面講過,“�!笨臻g的使用在程序編譯后就定死了,無法自己制定使用多大的棧,這樣就很不方便,尤其是在傳輸處理動(dòng)態(tài)數(shù)據(jù)流的時(shí)候就很麻煩。那肯定有人想過,在函數(shù)里面定義一個(gè)很大的數(shù)組局部變量,那不就可以應(yīng)對(duì)各種各樣的數(shù)據(jù)流了。如果在函數(shù)里面定義很大的數(shù)組,系統(tǒng)在跑的使用,其他函數(shù)也會(huì)用到�?臻g哦,一旦�?臻g使用過大導(dǎo)致內(nèi)存溢出,那系統(tǒng)肯定奔潰了�!岸选本筒灰粯恿�,要用多少空間,完全可以動(dòng)態(tài)申請(qǐng),需要多少,就申請(qǐng)多少。由于是在自己定義全局?jǐn)?shù)組變量的地址上管理內(nèi)存操作,不怕內(nèi)存溢出。
   棧和堆的共同點(diǎn)和區(qū)別:
   1、共同點(diǎn):a:都是占用ram空間;
                     b:可使用最大ram空間在程序編譯后就確定不變了;
   2、區(qū)別:�?臻g由系統(tǒng)自動(dòng)申請(qǐng)和釋放,堆空間由用戶自己申請(qǐng)和釋放。
   接下來介紹一下OSAL的內(nèi)存管理,OSAL的內(nèi)存管理是堆管理,OSAL_Memory.c里面定義了一個(gè)很大的數(shù)組theHeap,內(nèi)存管理把堆空間分成兩個(gè)部分,第一個(gè)部分是針對(duì)小塊內(nèi)存的管理,第二部分是針對(duì)大塊內(nèi)存的管理。這樣做的好處是容易申請(qǐng)到連續(xù)的大空間,因?yàn)樾K內(nèi)存處理會(huì)使整個(gè)內(nèi)存空間碎片化,那么會(huì)導(dǎo)致內(nèi)存空間不連續(xù),不連續(xù)的空間是對(duì)申請(qǐng)大空間是非常不利的。在申請(qǐng)堆空間時(shí),會(huì)自動(dòng)合并之前釋放的free塊,等到找到合適連續(xù)塊時(shí),會(huì)自動(dòng)裁剪多余的塊空間,以免造成空間的浪費(fèi)。
  下面列舉出已去除其他無關(guān)代碼和修改部分名稱后的內(nèi)存管理代碼,代碼處理有詳細(xì)注釋,還有測(cè)試過程。另外,代碼的測(cè)試是基于STC8A8K64S4A12單片機(jī),ram空間為8K。
首先是相關(guān)宏和變量定義
//堆總空間
#define HEAP_SIZE   2048   
#define HEAPMEM_IN_USE             0x8000

//堆空間結(jié)束位置
#define HEAP_LASTBLK_IDX      ((HEAP_SIZE / HEAPMEM_HDRSZ) - 1)
//區(qū)分塊位置
#define HEAPMEM_SMALLBLK_HDRCNT   (HEAPMEM_SMALLBLK_BUCKET / HEAPMEM_HDRSZ)
//大塊起始位置
#define HEAPMEM_BIGBLK_IDX        (HEAPMEM_SMALLBLK_HDRCNT + 1)

//首部大小
#define HEAPMEM_HDRSZ              sizeof(heapMemHdr_t)  
//最小塊大小
#define HEAPMEM_MIN_BLKSZ         (HEAPMEM_ROUND((HEAPMEM_HDRSZ * 2)))
//小塊大小
#define HEAPMEM_SMALL_BLKSZ       (HEAPMEM_ROUND(16))
//默認(rèn)長(zhǎng)塊大小
#define HEAPMEM_LL_BLKSZ          (HEAPMEM_ROUND(417) + (19 * HEAPMEM_HDRSZ))

//小塊總空間
#define HEAPMEM_SMALLBLK_BUCKET  ((HEAPMEM_SMALL_BLKSZ * HEAPMEM_SMALL_BLKCNT) + HEAPMEM_LL_BLKSZ)
//大塊總空間
#define HEAPMEM_BIGBLK_SZ         (HEAP_SIZE - HEAPMEM_SMALLBLK_BUCKET - HEAPMEM_HDRSZ*2)

//默認(rèn)小塊數(shù)量
#define HEAPMEM_SMALL_BLKCNT       8
// 調(diào)整申請(qǐng)內(nèi)存大小宏操作(如申請(qǐng)17字節(jié)空間,則調(diào)整為18字節(jié))
#define HEAPMEM_ROUND(X)       ((((X) + HEAPMEM_HDRSZ - 1) / HEAPMEM_HDRSZ) * HEAPMEM_HDRSZ)

typedef struct {
  unsigned len : 15;//本快的長(zhǎng)度最大為2^16-1個(gè)字節(jié),且申請(qǐng)空間的最小粒度為2個(gè)字節(jié)
  unsigned inUse : 1;//標(biāo)志位表示本快是否已經(jīng)被使用
} heapMemHdrHdr_t;

typedef union {
  //因此,當(dāng)halDataAlign\u t小于UINT16時(shí),編譯器強(qiáng)制結(jié)構(gòu)對(duì)齊最大的元素,而不會(huì)在目標(biāo)上浪費(fèi)空間。
  uint8 alignDummy;
  uint16 val;//存儲(chǔ)上一塊長(zhǎng)度,in use信息
  heapMemHdrHdr_t hdr;//快頭指針
} heapMemHdr_t;

static __no_init heapMemHdr_t all_heap[HEAP_SIZE];//定義堆空間數(shù)組
static __no_init heapMemHdr_t *ff1;  //第一個(gè)空塊

static uint8 heapMemStat = 0x01;            // 離散狀態(tài)標(biāo)志 0x01:踢出
說明,每個(gè)all_heap元素占用2個(gè)字節(jié),即16個(gè)bit,最高位bit代表使用狀態(tài),1表示非free,0表示free。剩下15個(gè)bit可以表示32768個(gè)byte堆空間,高達(dá)32K了,應(yīng)付51單片機(jī)是完全沒有問題的。定義了一個(gè)全局?jǐn)?shù)組變量all_heap作為堆總空間,大小為2048個(gè)byte�?梢愿鶕�(jù)單片機(jī)資源自行修改堆總空間大小,宏HEAPMEM_SMALLBLK_HDRCNT 是小塊內(nèi)存和大塊內(nèi)存的分界數(shù)值。

下面是堆空間初始化函數(shù)
void heap_init(void)
{
  all_heap[HEAP_LASTBLK_IDX].val = 0;//在堆的末尾設(shè)置一個(gè)空塊,以便與零進(jìn)行快速比較
  ff1 = all_heap;//設(shè)置管理小塊空間的首部
  ff1->val = HEAPMEM_SMALLBLK_BUCKET;
   //設(shè)置劃分小塊空間與大塊空間的首部
  all_heap[HEAPMEM_SMALLBLK_HDRCNT].val = (HEAPMEM_HDRSZ | HEAPMEM_IN_USE);
  // 設(shè)置管理大塊空間首部
  all_heap[HEAPMEM_BIGBLK_IDX].val = HEAPMEM_BIGBLK_SZ;  // Set 'len' & clear 'inUse' field.
}

下面是執(zhí)行heap_init()之后的內(nèi)存地址空間示例(初始地址是某次上電隨機(jī)出現(xiàn)的)
use
value
地址
對(duì)應(yīng)數(shù)組
0
584
0x036d - 0x036e
all_heap[0]
0
-
-
-
0
-
-
-
1
2
0x05b5 - 0x05b6
all_heap[292]
0
1460
0x05b7 - 0x05b8
all_heap[293]
0
-
-
-
0
-
-
-
0
0
0x0b6b - 0x0b6c
all_heap[1023]
    說明,第一個(gè)all_heap[0]存放這個(gè)小塊空間首部,這個(gè)首部有小塊空間剩余量,584表示可以使用584個(gè)byte的小塊空間,每次有小塊空間申請(qǐng)成功,這個(gè)剩余值就會(huì)減少。藍(lán)色標(biāo)注的是小塊內(nèi)存和大塊內(nèi)存的格擋板,位置為all_heap[292],狀態(tài)標(biāo)為1表示已使用,這樣在小塊內(nèi)存在分配內(nèi)存的時(shí)候就不會(huì)合并到大塊內(nèi)存上面。all_heap[293]存放大塊空間首部,這個(gè)首部有大塊空間剩余量,1460表示可以使用1460個(gè)byte的大塊空間,每次有大塊空間申請(qǐng)成功,這個(gè)剩余值就會(huì)減少。堆末尾all_heap[1023]值被初始化為0,以便與零進(jìn)行快速比較。從0x036d-0x0b6c這2048個(gè)byte空間就是本次堆的全部空間大小。

下面是堆空間申請(qǐng)函數(shù)
void *heap_alloc(uint16 size)
{
  heapMemHdr_t *prev = NULL;
  heapMemHdr_t *hdr;
  uint8 intState;
  uint8 coal = 0;
  size += HEAPMEM_HDRSZ; //給需要申請(qǐng)的空間分配一個(gè)管理首部
//進(jìn)入臨界區(qū)

  //調(diào)整size大小,是空間對(duì)齊(與處理器和編譯器相關(guān))
  if ( sizeof( uint8 ) == 2 )//假設(shè)uint8占用2個(gè)字節(jié)
  {
    size += (size & 0x01);//假設(shè)為196個(gè),則size為196;假設(shè)為197個(gè),則size要198才滿足
  }
  else if ( sizeof( uint8 ) != 1 )
  {
    const uint8 mod = size % sizeof( uint8 );

    if ( mod != 0 )
    {
      size += (sizeof( uint8 ) - mod);
    }
  }
  //判斷小塊內(nèi)存空間是否足夠分配,否則向大塊內(nèi)存空間申請(qǐng)
  if ((heapMemStat == 0) || (size <= HEAPMEM_SMALL_BLKSZ))
  {
    hdr = ff1;//小塊內(nèi)存,從ff1開始查找

  }
  else
  {
    hdr = (all_heap + HEAPMEM_BIGBLK_IDX);//從大塊開始查找
  }
  //開始迭代的尋找適合的內(nèi)存空間
  do
  {
    if ( hdr->hdr.inUse )//遇到非free塊
    {
      coal = 0;//告訴下一塊,本塊非free
    }
    else //遇到free塊
    {
      if ( coal != 0 )//上一塊是free塊
      {
        prev->hdr.len += hdr->hdr.len;//兩個(gè)free塊合并相鄰內(nèi)存空間

        if ( prev->hdr.len >= size )  //合并后的大小滿足size
        {
          hdr = prev;  //得到塊的地址
          break;
        }
      }
      else //上一塊是非free塊
      {
        if ( hdr->hdr.len >= size )//一個(gè)快的大小就可以滿足情況,分配,跳出循環(huán)返回
        {
          break;
        }

        coal = 1;//否則,標(biāo)記coal為1,告訴下一塊,本快是free的
        prev = hdr; //保存當(dāng)前內(nèi)存地址
      }
    }
    //(uint8 *)hdr這個(gè)操作使本來2個(gè)字節(jié),強(qiáng)制轉(zhuǎn)換成1個(gè)字節(jié)
    hdr = (heapMemHdr_t *)((uint8 *)hdr + hdr->hdr.len);//經(jīng)典malloc實(shí)現(xiàn)方式,迭代下一塊

    if ( hdr->val == 0 )//已經(jīng)到達(dá)堆底部(初始化時(shí),已經(jīng)讓堆底為零,方便識(shí)別)
    {
      hdr = NULL;//空指針,表示找不到合適size塊
      break;
    }
  }while(1);

  if ( hdr != NULL )//已經(jīng)找到合適size塊
  {
    uint16 tmp = hdr->hdr.len - size;//表示塊的大小大于請(qǐng)求的大小時(shí),為了不浪費(fèi)空間,還要把塊切開
    //確定是否滿足拆分閾值
    if ( tmp >= HEAPMEM_MIN_BLKSZ )//剩下的大小可以單獨(dú)成為一個(gè)free塊
    {
      heapMemHdr_t *next = (heapMemHdr_t *)((uint8 *)hdr + size);
      next->val = tmp;                     // 告訴后一個(gè)塊自己的信息
      hdr->val = (size | HEAPMEM_IN_USE);  // value代表前一塊的大小和使用情況,這樣相當(dāng)于雙向鏈表
    }
    else
    {
      hdr->hdr.inUse = TRUE; //標(biāo)記本塊已經(jīng)被使用
    }

    if ((heapMemStat != 0) && (ff1 == hdr))
    {
      ff1 = (heapMemHdr_t *)((uint8 *)hdr + hdr->hdr.len);
    }
    hdr++;
  }
  //退出臨界區(qū)
  return (void *)hdr;
}

其中,size是申請(qǐng)空間的多少,每次申請(qǐng)空間為1個(gè)byte。比如要申請(qǐng)10個(gè)元素uint16類型的數(shù)組,代碼可以寫:
   uint16 *test;
   test = (uint16*)heap_alloc(20);

   下面寫一段代碼來測(cè)試heap_alloc函數(shù)
     heap_init(); //初始化堆
     uint8 *test1;
     uint16 *test2;
     uint32 *test3;
     uint8 *test4;
     test1 = (uint8 *)heap_alloc(1);
     test2 = (uint16 *)heap_alloc(2);  
     test3 = (uint32 *)heap_alloc(4);
     test4 = (uint8 *)heap_alloc(1);
   下面是測(cè)試代碼運(yùn)行后申請(qǐng)空間情況示例(地址:0x036d-0x037e)
0x03-前綴
6d
6e
6f
70
71
72
73
74
75
76
77
78
79
7a
7b
7c
7d
7e
      申請(qǐng)塊
test1
test2
test3
Test4
      標(biāo)識(shí)塊
584
581
577
571
568

   說明,灰色標(biāo)識(shí)前后塊信息(非free態(tài)),里面的數(shù)值是剩余byte空間,本次測(cè)試執(zhí)行后,總共用去16個(gè)byte(地址0x036d-0x037e)空間,其中8個(gè)byte用于剩余頭部信息存儲(chǔ),8個(gè)bytes才是有效空間。每個(gè)申請(qǐng)內(nèi)存塊都帶有前后信息塊,借鑒了雙向鏈表的數(shù)據(jù)結(jié)構(gòu)。568沒有顏色的表示free態(tài)。

    下面是堆空間釋放函數(shù)
void heap_free(void *ptr)
{
  //如果heapMemHdr_t為2個(gè)字節(jié),則下面指針減去1,物理地址會(huì)改變2
  heapMemHdr_t *hdr = (heapMemHdr_t *)ptr - 1;//獲取該內(nèi)存空間首部
  uint8 intState;
  //進(jìn)入中斷臨界
  hdr->hdr.inUse = FALSE; //標(biāo)記使用狀態(tài)為:未使用

  if (ff1 > hdr)
  {
    ff1 = hdr;//調(diào)整ff1位置
  }
  //退出中斷臨界
}

    比如執(zhí)行heap_free(test1),傳入地址為0x036f,執(zhí)行過程中會(huì)將0x036d的狀態(tài)標(biāo)為free態(tài),也就是釋放掉堆占用了,584那塊會(huì)由灰色變成無色。那么,下次申請(qǐng)空間的時(shí)候,可以申請(qǐng)使用這塊內(nèi)存(地址:0x036d-0x037e)
   0x03-前綴
6d
6e
6f
70
71
72
73
74
75
76
77
78
79
7a
7b
7c
7d
7e
      申請(qǐng)塊
test2
test3
Test4
      標(biāo)識(shí)塊
584
581
577
571
568
    上面的示例是小內(nèi)存的管理過程,大內(nèi)存的管理也是一樣的過程,這里就不列舉了。
  在實(shí)際項(xiàng)目中,搭載TI OSAL的ble芯片或者zibee芯片運(yùn)行都非常穩(wěn)定,這也要?dú)w功于OSAL高效可靠的內(nèi)存管理。非常值得研究和借鑒!




評(píng)分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

回復(fù)

使用道具 舉報(bào)

ID:117433 發(fā)表于 2020-1-15 16:46 | 顯示全部樓層
你寫這么多,為什么就不能讓大家都拿來就用體驗(yàn)一下
回復(fù)

使用道具 舉報(bào)

ID:351097 發(fā)表于 2020-1-15 20:43 | 顯示全部樓層
xizhe2005 發(fā)表于 2020-1-15 16:46
你寫這么多,為什么就不能讓大家都拿來就用體驗(yàn)一下

兄弟,代碼都亮出了,不就可以體驗(yàn)了嗎
回復(fù)

使用道具 舉報(bào)

ID:117433 發(fā)表于 2020-1-17 09:31 | 顯示全部樓層
給個(gè)現(xiàn)成的KEIL工程,讓我下載到我的單片機(jī)里,謝了,我不太懂原理,只是個(gè)用戶
回復(fù)

使用道具 舉報(bào)

ID:351097 發(fā)表于 2020-1-17 13:14 來自觸屏版 | 顯示全部樓層
xizhe2005 發(fā)表于 2020-1-17 09:31
給個(gè)現(xiàn)成的KEIL工程,讓我下載到我的單片機(jī)里,謝了,我不太懂原理,只是個(gè)用戶

我電腦安裝keil打開沒響應(yīng),所以沒有搞keil工程。你直接把現(xiàn)成的代碼添加到你的keil工程里面,編譯不成問題的。
回復(fù)

使用道具 舉報(bào)

ID:117433 發(fā)表于 2020-1-17 16:46 | 顯示全部樓層
你都沒試過呀,那你怎么知道沒問題
回復(fù)

使用道具 舉報(bào)

ID:351097 發(fā)表于 2020-1-17 19:09 來自觸屏版 | 顯示全部樓層
xizhe2005 發(fā)表于 2020-1-17 16:46
你都沒試過呀,那你怎么知道沒問題

我在IAR平臺(tái)調(diào)試的
回復(fù)

使用道具 舉報(bào)

ID:351097 發(fā)表于 2020-1-17 19:24 | 顯示全部樓層
xizhe2005 發(fā)表于 2020-1-17 16:46
你都沒試過呀,那你怎么知道沒問題

就算有點(diǎn)問題,應(yīng)該也是數(shù)據(jù)類型定義那邊修改一下就可以了。
回復(fù)

使用道具 舉報(bào)

ID:1063561 發(fā)表于 2025-4-26 11:45 | 顯示全部樓層
下載學(xué)習(xí),謝謝分享!
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表