解析鴻蒙輕內核靜態內存的使用

一、前言

內存管理模塊管理系統的內存資源,它是操作系統的核心模塊之一,主要包括內存的初始化、分配以及釋放。

在系統運行過程中,內存管理模塊通過對內存的申請/釋放來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。

鴻蒙輕內核的內存管理分為靜態內存管理和動態內存管理,提供內存初始化、分配、釋放等功能。

動態內存:在動態內存池中分配用戶指定大小的內存塊。

  • 優點:按需分配。
  • 缺點:內存池中可能出現碎片。

靜態內存:在靜態內存池中分配用戶初始化時預設(固定)大小的內存塊。

  • 優點:分配和釋放效率高,靜態內存池中無碎片。
  • 缺點:隻能申請到初始化預設大小的內存塊,不能按需申請。

本文主要分析鴻蒙輕內核靜態內存(Memory Box),後續系列會繼續分析動態內存。靜態內存實質上是一個靜態數組,靜態內存池內的塊大小在初始化時設定,初始化後塊大小不可變更。靜態內存池由一個控制塊和若幹相同大小的內存塊構成。控制塊位於內存池頭部,用於內存塊管理。內存塊的申請和釋放以塊大小為粒度。

本文通過分析靜態內存模塊的源碼,幫助讀者掌握靜態內存的使用。本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m獲取。

接下來,我們看下靜態內存的結構體,靜態內存初始化,靜態內存常用操作的源代碼。

二、靜態內存結構體定義和常用宏定義

2.1、靜態內存結構體定義

靜態內存結構體在文件kernel\include\los_membox.h中定義。源代碼如下,⑴處定義的是靜態內存節點LOS_MEMBOX_NODE結構體,⑵處定義的靜態內存的結構體池信息結構體為LOS_MEMBOX_INFO,,結構體成員的解釋見註釋部分。

⑴  typedef struct tagMEMBOX_NODE {
        struct tagMEMBOX_NODE *pstNext; /**< 靜態內存池中空閑節點指針,指向下一個空閑節點 */
    } LOS_MEMBOX_NODE;

⑵  typedef struct LOS_MEMBOX_INFO {
        UINT32 uwBlkSize;               /**< 靜態內存池中空閑節點指針,指向下一個空閑節點 */
        UINT32 uwBlkNum;                /**< 靜態內存池的內存塊總數量 */
        UINT32 uwBlkCnt;                /**< 靜態內存池的已分配的內存塊總數量 */
    #if (LOSCFG_PLATFORM_EXC == 1)
        struct LOS_MEMBOX_INFO *nextMemBox; /**< 指向下一個靜態內存池 */
    #endif
        LOS_MEMBOX_NODE stFreeList;     /**< 靜態內存池的空閑內存塊單向鏈表 */
    } LOS_MEMBOX_INFO;

對靜態內存使用如下示意圖進行說明,對一塊靜態內存區域,頭部是LOS_MEMBOX_INFO信息,接著是各個內存塊,每塊內存塊大小是uwBlkSize,包含內存塊節點LOS_MEMBOX_NODE和內存塊數據區。空閑內存塊節點指向下一塊空閑內存塊節點。

2.2、靜態內存常用宏定義

靜態內存頭文件中還提供瞭一些重要的宏定義。⑴處的LOS_MEMBOX_ALIGNED(memAddr)用於對齊內存地址,⑵處OS_MEMBOX_NEXT(addr, blkSize)根據當前節點內存地址addr和內存塊大小blkSize獲取下一個內存塊的內存地址。⑶處OS_MEMBOX_NODE_HEAD_SIZE表示內存塊中節點頭大小,每個內存塊包含內存節點LOS_MEMBOX_NODE和存放業務的數據區。⑷處表示靜態內存的總大小,包含內存池信息結構體占用的大小,和各個內存塊占用的大小。

⑴  #define LOS_MEMBOX_ALIGNED(memAddr) (((UINTPTR)(memAddr) + sizeof(UINTPTR) - 1) & (~(sizeof(UINTPTR) - 1)))

⑵  #define OS_MEMBOX_NEXT(addr, blkSize) (LOS_MEMBOX_NODE *)(VOID *)((UINT8 *)(addr) + (blkSize))

⑶  #define OS_MEMBOX_NODE_HEAD_SIZE sizeof(LOS_MEMBOX_NODE)

⑷  #define LOS_MEMBOX_SIZE(blkSize, blkNum) \
    (sizeof(LOS_MEMBOX_INFO) + (LOS_MEMBOX_ALIGNED((blkSize) + OS_MEMBOX_NODE_HEAD_SIZE) * (blkNum)))

在文件kernel\src\mm\los_membox.c中也定義瞭一些宏和內聯函數。⑴處定義OS_MEMBOX_MAGIC魔術字,這個32位的魔術字的後8位維護任務編號信息,任務編號位由⑵處的宏定義。⑶處宏定義任務編號的最大值,⑷處的宏從魔術字中提取任務編號信息。

⑸處內聯函數設置魔術字,在內存塊節點從靜態內存池中分配出來後,節點指針.pstNext不再指向下一個空閑內存塊節點,而是設置為魔術字。⑹處的內聯函數用於校驗魔術字。⑺處的宏根據內存塊的節點地址獲取內存塊的數據區地址,⑻處的宏根據內存塊的數據區地址獲取內存塊的節點地址。

⑴  #define OS_MEMBOX_MAGIC         0xa55a5a00

⑵  #define OS_MEMBOX_TASKID_BITS   8

⑶  #define OS_MEMBOX_MAX_TASKID    ((1 << OS_MEMBOX_TASKID_BITS) - 1)

⑷  #define OS_MEMBOX_TASKID_GET(addr) (((UINTPTR)(addr)) & OS_MEMBOX_MAX_TASKID)

⑸  STATIC INLINE VOID OsMemBoxSetMagic(LOS_MEMBOX_NODE *node)
    {
        UINT8 taskID = (UINT8)LOS_CurTaskIDGet();
        node->pstNext = (LOS_MEMBOX_NODE *)(OS_MEMBOX_MAGIC | taskID);
    }

⑹  STATIC INLINE UINT32 OsMemBoxCheckMagic(LOS_MEMBOX_NODE *node)
    {
        UINT32 taskID = OS_MEMBOX_TASKID_GET(node->pstNext);
        if (taskID > (LOSCFG_BASE_CORE_TSK_LIMIT + 1)) {
            return LOS_NOK;
        } else {
            return (node->pstNext == (LOS_MEMBOX_NODE *)(OS_MEMBOX_MAGIC | taskID)) ? LOS_OK : LOS_NOK;
        }
    }

⑺  #define OS_MEMBOX_USER_ADDR(addr) \
        ((VOID *)((UINT8 *)(addr) + OS_MEMBOX_NODE_HEAD_SIZE))

⑻  #define OS_MEMBOX_NODE_ADDR(addr) \
        ((LOS_MEMBOX_NODE *)(VOID *)((UINT8 *)(addr) - OS_MEMBOX_NODE_HEAD_SIZE))

三、靜態內存常用操作

當用戶需要使用固定長度的內存時,可以通過靜態內存分配的方式獲取內存,一旦使用完畢,通過靜態內存釋放函數歸還所占用內存,使之可以重復使用。

3.1、初始化靜態內存池

我們分析下初始化靜態內存池函數UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize)的代碼。我們先看看函數參數,VOID *pool是靜態內存池的起始地址,UINT32 poolSize是初始化的靜態內存池的總大小,poolSize需要小於等於*pool開始的內存區域的大小,否則會影響後面的內存區域。還需要大於靜態內存的頭部大小sizeof(LOS_MEMBOX_INFO)。長度UINT32 blkSize是靜態內存池中的每個內存塊的塊大小。

我們看下代碼,⑴處對傳入參數進行校驗。⑵處設置靜態內存池中每個內存塊的實際大小,已內存對齊,也算上內存塊中節點信息。⑶處計算內存池中內存塊的總數量,然後設置已用內存塊數量.uwBlkCnt為0。
⑷處如果可用的內存塊為0,返回初始化失敗。⑸處獲取內存池中的第一個空閑內存塊節點。⑹處把空閑內存塊掛載在靜態內存池信息結構體空閑內存塊鏈表stFreeList.pstNext上,然後執行⑺每個空閑內存塊依次指向下一個空閑內存塊,鏈接起來。

UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize)
{
    LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;
    LOS_MEMBOX_NODE *node = NULL;
    UINT32 index;
    UINT32 intSave;

⑴  if (pool == NULL) {
        return LOS_NOK;
    }

    if (blkSize == 0) {
        return LOS_NOK;
    }

    if (poolSize < sizeof(LOS_MEMBOX_INFO)) {
        return LOS_NOK;
    }

    MEMBOX_LOCK(intSave);
⑵  boxInfo->uwBlkSize = LOS_MEMBOX_ALIGNED(blkSize + OS_MEMBOX_NODE_HEAD_SIZE);
    if (boxInfo->uwBlkSize == 0) {
        MEMBOX_UNLOCK(intSave);
        return LOS_NOK;
    }
⑶  boxInfo->uwBlkNum = (poolSize - sizeof(LOS_MEMBOX_INFO)) / boxInfo->uwBlkSize;
    boxInfo->uwBlkCnt = 0;
⑷  if (boxInfo->uwBlkNum == 0) {
        MEMBOX_UNLOCK(intSave);
        return LOS_NOK;
    }

⑸  node = (LOS_MEMBOX_NODE *)(boxInfo + 1);

⑹  boxInfo->stFreeList.pstNext = node;

⑺  for (index = 0; index < boxInfo->uwBlkNum - 1; ++index) {
        node->pstNext = OS_MEMBOX_NEXT(node, boxInfo->uwBlkSize);
        node = node->pstNext;
    }

    node->pstNext = NULL;

#if (LOSCFG_PLATFORM_EXC == 1)
    OsMemBoxAdd(pool);
#endif

    MEMBOX_UNLOCK(intSave);

    return LOS_OK;
}

3.2、清除靜態內存塊內容

我們可以使用函數VOID LOS_MemboxClr(VOID *pool, VOID *box)來清除靜態內存塊中的數據區內容,需要2個參數,VOID *pool是初始化過的靜態內存池地址。VOID *box是需要清除內容的靜態內存塊的數據區的起始地址,註意這個不是內存塊的節點地址,每個內存塊的節點區不能清除。下面分析下源碼。

⑴處對參數進行校驗,⑵處調用memset_s()函數把內存塊的數據區寫入0。寫入的開始地址是內存塊的數據區的起始地址VOID *box,寫入長度是數據區的長度boxInfo->uwBlkSize – OS_MEMBOX_NODE_HEAD_SIZE。

VOID LOS_MemboxClr(VOID *pool, VOID *box)
{
    LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;

⑴  if ((pool == NULL) || (box == NULL)) {
        return;
    }

⑵  (VOID)memset_s(box, (boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE), 0,
                   (boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE));
}

3.3、申請、釋放靜態內存

初始化靜態內存池後,我們可以使用函數VOID *LOS_MemboxAlloc(VOID *pool)來申請靜態內存,下面分析下源碼。

⑴處獲取靜態內存池空閑內存塊鏈表頭結點,如果鏈表不為空,執行⑵,把下一個可用節點賦值給nodeTmp。⑶處把鏈表頭結點執行下一個的下一個鏈表節點,然後執行⑷把分配出來的內存塊設置魔術字,接著把內存池已用內存塊數量加1。⑸處返回時調用宏OS_MEMBOX_USER_ADDR()計算出內存塊的數據區域地質。

VOID *LOS_MemboxAlloc(VOID *pool)
{
    LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;
    LOS_MEMBOX_NODE *node = NULL;
    LOS_MEMBOX_NODE *nodeTmp = NULL;
    UINT32 intSave;

    if (pool == NULL) {
        return NULL;
    }

    MEMBOX_LOCK(intSave);
⑴  node = &(boxInfo->stFreeList);
    if (node->pstNext != NULL) {
⑵      nodeTmp = node->pstNext;
⑶      node->pstNext = nodeTmp->pstNext;
⑷      OsMemBoxSetMagic(nodeTmp);
        boxInfo->uwBlkCnt++;
    }
    MEMBOX_UNLOCK(intSave);

⑸  return (nodeTmp == NULL) ? NULL : OS_MEMBOX_USER_ADDR(nodeTmp);
}

對申請的內存塊使用完畢,我們可以使用函數UINT32 LOS_MemboxFree(VOID *pool, VOID *box)來釋放靜態內存,需要2個參數,VOID *pool是初始化過的靜態內存池地址。VOID *box是需要釋放的靜態內存塊的數據區的起始地址,註意這個不是內存塊的節點地址。下面分析下源碼。

⑴處根據待釋放的內存塊的數據區域地址獲取節點地址node,⑵對要釋放的內存塊先進行校驗。⑶處把要釋放的內存塊掛在內存池空閑內存塊鏈表上,然後執行⑷把已用數量減1。

LITE_OS_SEC_TEXT UINT32 LOS_MemboxFree(VOID *pool, VOID *box)
{
    LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;
    UINT32 ret = LOS_NOK;
    UINT32 intSave;

    if ((pool == NULL) || (box == NULL)) {
        return LOS_NOK;
    }

    MEMBOX_LOCK(intSave);
    do {
⑴      LOS_MEMBOX_NODE *node = OS_MEMBOX_NODE_ADDR(box);
⑵      if (OsCheckBoxMem(boxInfo, node) != LOS_OK) {
            break;
        }

⑶      node->pstNext = boxInfo->stFreeList.pstNext;
        boxInfo->stFreeList.pstNext = node;
⑷      boxInfo->uwBlkCnt--;
        ret = LOS_OK;
    } while (0);
    MEMBOX_UNLOCK(intSave);

    return ret;
}

接下來,我們再看看校驗函數OsCheckBoxMem()。⑴如果內存池的塊大小為0,返回校驗失敗。⑵處計算出要釋放的內存快節點相對第一個內存塊節點的偏移量offset。⑶如果偏移量除以內存塊數量餘數不為0,返回校驗失敗。⑷如果偏移量除以內存塊數量的商大於等於內存塊的數量,返回校驗失敗。⑸調用宏OsMemBoxCheckMagic校驗魔術字。

STATIC INLINE UINT32 OsCheckBoxMem(const LOS_MEMBOX_INFO *boxInfo, const VOID *node)
{
    UINT32 offset;

⑴  if (boxInfo->uwBlkSize == 0) {
        return LOS_NOK;
    }

⑵  offset = (UINT32)((UINTPTR)node - (UINTPTR)(boxInfo + 1));
⑶  if ((offset % boxInfo->uwBlkSize) != 0) {
        return LOS_NOK;
    }

⑷  if ((offset / boxInfo->uwBlkSize) >= boxInfo->uwBlkNum) {
        return LOS_NOK;
    }

⑸   return OsMemBoxCheckMagic((LOS_MEMBOX_NODE *)node);
}

四、小結

本文帶領大傢一起剖析瞭鴻蒙輕內核的靜態內存模塊的源代碼,包含靜態內存的結構體、靜態內存池初始化、靜態內存申請、釋放、清除內容等。為瞭更容易找到鴻蒙輕內核代碼倉,建議訪問https://gitee.com/openharmony/kernel_liteos_m

以上就是解析鴻蒙輕內核靜態內存的使用的詳細內容,更多關於鴻蒙輕內核靜態內存的資料請關註WalkonNet其它相關文章!

推薦閱讀: