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

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

記憶體管理相關篇為:

靜態分配

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

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 中回復 百萬 可方便閱讀。

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