v86.01 鴻蒙核心原始碼分析 (靜態分配篇) | 很簡單的一位小朋友 | 百篇部落格分析 OpenHarmony 原始碼

2022-05-25 18:01:24

本篇關鍵詞:池頭池體節頭節塊

記憶體管理相關篇為:

靜態分配

相比動態分配,靜態記憶體池的分配就是個小弟弟,非常的簡單,兩個結構體 + 一張圖 就能說明白。

typedef struct {//靜態記憶體池資訊結構體
    UINT32 uwBlkSize;           /**< Block size | 塊大小*/
    UINT32 uwBlkNum;            /**< Block number | 塊數量*/
    UINT32 uwBlkCnt;            /**< The number of allocated blocks | 已經被分配的塊數量*/
    LOS_MEMBOX_NODE stFreeList; /**< Free list | 空閒連結串列*/
} LOS_MEMBOX_INFO;

typedef struct tagMEMBOX_NODE { //記憶體池中空閒節點的結構,是個單向的連結串列
    struct tagMEMBOX_NODE *pstNext; /**< Free node's pointer to the next node in a memory pool | 指向記憶體池中下一個空閒節點的指標*/
} LOS_MEMBOX_NODE;

下圖來源於官網

解讀

  • 靜態記憶體池在概念上由 池頭池體 兩部分組成,池體由眾多節塊組成,節塊由 節頭節體 兩部分組成
  • 在資料結構上表現為 LOS_MEMBOX_INFO(池頭) + [LOS_MEMBOX_NODE(節頭) + data(節體)] + ... + [LOS_MEMBOX_NODE(節頭) + data(節體)] ,在虛擬地址上它們是連在一起的。
  • 池頭 記錄總資訊,包括 節塊大小,總節塊數量,已分配節塊數量,空閒節塊連結串列表頭,stFreeList將所有空閒節塊連結到一起,分配記憶體根本不需要遍歷,stFreeList指向的下一個不為null代表還有空閒節塊。
  • 節頭只有一個指向下一個空閒連結串列的pstNext指標,簡單但足以。
  • 靜態分配的優缺點是很明顯的,總結下:
    • 負責管理的結構體簡單,會佔用很少的空間,這點優於動態分配。
    • 分配速度最快,一步到位。
    • 缺點是浪費嚴重,僵硬不靈活,很計劃經濟,給每一個家庭每月口糧就這麼多,高矮胖瘦都不會管。

因程式碼量不大,但很精彩,看這種程式碼是種享受,本篇詳細列出靜態記憶體程式碼層面的實現,關鍵處已新增註釋。

初始化

///初始化一個靜態記憶體池,根據入參設定其起始地址、總大小及每個記憶體塊大小
LITE_OS_SEC_TEXT_INIT 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;
    MEMBOX_LOCK(intSave);
    boxInfo->uwBlkSize = LOS_MEMBOX_ALIGNED(blkSize + OS_MEMBOX_NODE_HEAD_SIZE); //節塊總大小(節頭+節體)
    boxInfo->uwBlkNum = (poolSize - sizeof(LOS_MEMBOX_INFO)) / boxInfo->uwBlkSize;//總節塊數量
    boxInfo->uwBlkCnt = 0;	//已分配的數量
    if (boxInfo->uwBlkNum == 0) {//只有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);//按塊大小切割好,統一由pstNext指向
        node = node->pstNext;//node儲存了下一個節點的地址資訊
    }
    node->pstNext = NULL;//最後一個為null
    MEMBOX_UNLOCK(intSave);
    return LOS_OK;
}

申請

///從指定的靜態記憶體池中申請一塊靜態記憶體塊,整個核心原始碼只有 OsSwtmrScan中用到了靜態記憶體.
LITE_OS_SEC_TEXT 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;//不再空閒了,把節點摘出去了.
        OS_MEMBOX_SET_MAGIC(nodeTmp);//為已使用的節塊設定魔法數位
        boxInfo->uwBlkCnt++;//已使用塊數增加
    }
    MEMBOX_UNLOCK(intSave);
    return (nodeTmp == NULL) ? NULL : OS_MEMBOX_USER_ADDR(nodeTmp);//返回可用的虛擬地址
}

釋放

/// 釋放指定的一塊靜態記憶體塊
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;
}

使用

鴻蒙核心目前只有軟時鐘處理使用了靜態記憶體池,直接上程式碼

///軟時鐘初始化 ,注意函數在多CPU情況下會執行多次
STATIC UINT32 SwtmrBaseInit(VOID)
{
    UINT32 ret;
    UINT32 size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT;
    SWTMR_CTRL_S *swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size); /* system resident resource */
    if (swtmr == NULL) {
        return LOS_ERRNO_SWTMR_NO_MEMORY;
    }
    (VOID)memset_s(swtmr, size, 0, size);//清0
    g_swtmrCBArray = swtmr;//軟時鐘
    LOS_ListInit(&g_swtmrFreeList);//初始化空閒連結串列
    for (UINT16 index = 0; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) {
            swtmr->usTimerID = index;//按順序賦值
            LOS_ListTailInsert(&g_swtmrFreeList, &swtmr->stSortList.sortLinkNode);//通過sortLinkNode將節點掛到空閒連結串列 
    }
	//想要用靜態記憶體池管理,就必須要使用LOS_MEMBOX_SIZE來計算申請的記憶體大小,因為需要點字首記憶體承載頭部資訊.
    size = LOS_MEMBOX_SIZE(sizeof(SwtmrHandlerItem), OS_SWTMR_HANDLE_QUEUE_SIZE);//規劃一片記憶體區域作為軟時鐘處理常式的靜態記憶體池。
    g_swtmrHandlerPool = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, size); /* system resident resource */
    if (g_swtmrHandlerPool == NULL) {
        return LOS_ERRNO_SWTMR_NO_MEMORY;
    }
    ret = LOS_MemboxInit(g_swtmrHandlerPool, size, sizeof(SwtmrHandlerItem));
    if (ret != LOS_OK) {
        return LOS_ERRNO_SWTMR_HANDLER_POOL_NO_MEM;
    }
    for (UINT16 index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) {
        SwtmrRunQue *srq = &g_swtmrRunQue[index];
        /* The linked list of all cores must be initialized at core 0 startup for load balancing */
        OsSortLinkInit(&srq->swtmrSortLink);
        LOS_ListInit(&srq->swtmrHandlerQueue);
        srq->swtmrTask = NULL;
    }
    SwtmrDebugDataInit();
    return LOS_OK;
}
typedef VOID (*SWTMR_PROC_FUNC)(UINTPTR arg);	//函數指標, 賦值給 SWTMR_CTRL_S->pfnHandler,回撥處理
typedef struct {//處理軟體定時器超時的回撥函數的結構體
    SWTMR_PROC_FUNC handler;    /**< Callback function that handles software timer timeout  */	//處理軟體定時器超時的回撥函數
    UINTPTR arg;                /**< Parameter passed in when the callback function
                                     that handles software timer timeout is called */	//呼叫處理軟體計時器超時的回撥函數時傳入的引數
    LOS_DL_LIST node;
#ifdef LOSCFG_SWTMR_DEBUG
    UINT32 swtmrID;
#endif
} SwtmrHandlerItem;

關於軟定時器可以檢視系列相關篇,請想想為何軟體定時器會使用靜態記憶體。

百文說核心 | 抓住主脈絡

  • 百文相當於摸出核心的肌肉和器官系統,讓人開始豐滿有立體感,因是直接從註釋原始碼起步,在加註釋過程中,每每有心得處就整理,慢慢形成了以下文章。內容立足原始碼,常以生活場景打比方儘可能多的將核心知識點置入某種場景,具有畫面感,容易理解記憶。說別人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆詰屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切。
  • 與程式碼需不斷debug一樣,文章內容會存在不少錯漏之處,請多包涵,但會反覆修正,持續更新,v**.xx 代表文章序號和修改的次數,精雕細琢,言簡意賅,力求打造精品內容。
  • 百文在 < 鴻蒙研究站 | 開源中國 | 部落格園 | 51cto | csdn | 知乎 | 掘金 > 站點發布,鴻蒙研究站 | weharmonyos 中回覆 百文 可方便閱讀。

按功能模組:

基礎知識 程序管理 任務管理 記憶體管理
雙向連結串列
核心概念
原始碼結構
地址空間
計時單位
優雅的宏
勾點框架
點陣圖管理
POSIX
main函數
排程故事
過程控制塊
程序空間
線性區
紅黑樹
程序管理
Fork程序
程序回收
Shell編輯
Shell解析
任務控制塊
並行並行
就緒佇列
排程機制
任務管理
用棧方式
軟體定時器
控制檯
遠端登入
協定棧
記憶體規則
實體記憶體
記憶體概念
虛實對映
頁表管理
靜態分配
TLFS演演算法
記憶體池管理
原子操作
圓整對齊
通訊機制 檔案系統 硬體架構 核心組合
通訊總覽
自旋鎖
互斥鎖
快鎖使用
快鎖實現
讀寫鎖
號誌
事件機制
訊號生產
訊號消費
訊息佇列
訊息封裝
訊息對映
共用記憶體
檔案概念
檔案故事
索引節點
VFS
檔案控制程式碼
根檔案系統
掛載機制
管道檔案
檔案對映
寫時拷貝
晶片模式
ARM架構
指令集
協處理器
工作模式
暫存器
多核管理
中斷概念
中斷管理
編碼方式
組合基礎
組合傳參
連結指令碼
開機啟動
程序切換
工作切換
中斷切換
異常接管
缺頁中斷
編譯執行 調測工具
編譯過程
編譯構建
GN語法
忍者無敵
ELF格式
ELF解析
靜態連結
重定位
動態連結
程序映像
應用啟動
系統呼叫
VDSO
模組監控
紀錄檔跟蹤
系統安全
測試用例

百萬注原始碼 | 處處扣細節

  • 百萬漢字註解核心目的是要看清楚其毛細血管,細胞結構,等於在拿放大鏡看核心。核心並不神祕,帶著問題去原始碼中找答案是很容易上癮的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿著放大鏡根本不願意放手。

  • < gitee | github | coding | gitcode > 四大碼倉推播 | 同步官方原始碼,鴻蒙研究站 | weharmonyos 中回覆 百萬 可方便閱讀。

據說喜歡點贊分享的,後來都成了大神。