0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

鴻蒙內(nèi)核如何初始化物理內(nèi)存?

鴻蒙系統(tǒng)HarmonyOS ? 來源:my.oschina ? 作者:鴻蒙內(nèi)核源碼分析 ? 2021-04-25 15:05 ? 次閱讀

如何初始化物理內(nèi)存?

鴻蒙內(nèi)核物理內(nèi)存采用了段頁式管理,先看兩個主要結(jié)構(gòu)體.結(jié)構(gòu)體的每個成員變量的含義都已經(jīng)注解出來,請結(jié)合源碼理解.

#define VM_LIST_ORDER_MAX  9 //伙伴算法分組數(shù)量,從 2^0,2^1,...,2^8 (256*4K)=1M
#define VM_PHYS_SEG_MAX  32 //最大支持32個段

typedef struct VmPhysSeg {//物理段描述符
    PADDR_T start;            /* The start of physical memory area */ //物理內(nèi)存段的開始地址
    size_t size;              /* The size of physical memory area */ //物理內(nèi)存段的大小
    LosVmPage *pageBase;      /* The first page address of this area */ //本段首個物理頁框地址
    SPIN_LOCK_S freeListLock; /* The buddy list spinlock */    //伙伴算法自旋鎖,用于操作freeList上鎖
    struct VmFreeList freeList[VM_LIST_ORDER_MAX];  /* The free pages in the buddy list */ //伙伴算法的分組,默認分成10組 2^0,2^1,...,2^VM_LIST_ORDER_MAX
    SPIN_LOCK_S lruLock;  //用于置換的自旋鎖,用于操作lruList
    size_t lruSize[VM_NR_LRU_LISTS];  //5個雙循環(huán)鏈表大小,如此方便得到size
    LOS_DL_LIST lruList[VM_NR_LRU_LISTS]; //頁面置換算法,5個雙循環(huán)鏈表頭,它們分別描述五中不同類型的鏈表
} LosVmPhysSeg;


//注意: vmPage 中并沒有虛擬地址,只有物理地址
typedef struct VmPage { //物理頁框描述符
    LOS_DL_LIST         node;        /**< vm object dl list */ //虛擬內(nèi)存節(jié)點,通過它掛/摘到全局g_vmPhysSeg[segID]->freeList[order]物理頁框鏈表上
    UINT32              index;       /**< vm page index to vm object */ //索引位置
    PADDR_T             physAddr;    /**< vm page physical addr */  //物理頁框起始物理地址,只能用于計算,不會用于操作(讀/寫數(shù)據(jù)==)
    Atomic              refCounts;   /**< vm page ref count */   //被引用次數(shù),共享內(nèi)存會被多次引用
    UINT32              flags;       /**< vm page flags */    //頁標簽,同時可以有多個標簽(共享/引用/活動/被鎖==)
    UINT8               order;       /**< vm page in which order list */ //被安置在伙伴算法的幾號序列(              2^0,2^1,2^2,...,2^order)
    UINT8               segID;       /**< the segment id of vm page */ //所屬段ID
    UINT16              nPages;      /**< the vm page is used for kernel heap */ //分配頁數(shù),標識從本頁開始連續(xù)的幾頁將一塊被分配
} LosVmPage;//注意:關(guān)于nPages和order的關(guān)系說明,當請求分配為5頁時,order是等于3的,因為只有2^3才能滿足5頁的請求

理解它們是理解物理內(nèi)存管理的關(guān)鍵,尤其是 **LosVmPage ,**鴻蒙內(nèi)存模塊代碼通篇都能看到它的影子.內(nèi)核默認最大允許管理32個段.

段頁式管理簡單說就是先將物理內(nèi)存切成一段段,每段再切成單位為 4K的物理頁框,頁是在內(nèi)核層的操作單元,物理內(nèi)存的分配,置換,缺頁,內(nèi)存共享,文件高速緩存的讀寫,都是以頁為單位的,所以LosVmPage 很重要,很重要!

結(jié)構(gòu)體的每個變量代表了一個個的功能點,結(jié)構(gòu)體中頻繁了出現(xiàn)LOS_DL_LIST的身影,雙向鏈表是鴻蒙內(nèi)核最重要的結(jié)構(gòu)體,在系列篇開篇就專門講過它的重要性.

再比如LosVmPage.refCounts頁被引用的次數(shù),可理解被進程擁有的次數(shù),當refCounts大于1時,被多個進程所擁有,說明這頁就是共享頁.當?shù)扔?時,說明沒有進程在使用了,這時就可以被釋放了.

看到這里熟悉JAVA的同學是不是似曾相識,這像是Java的內(nèi)存回收機制.在內(nèi)核層面,引用的概念不僅僅適用于內(nèi)存模塊,也適用于其他模塊,比如文件/設備模塊,同樣都存在共享的場景.這些模塊不在這里展開說,后續(xù)有專門的章節(jié)細講.

段一開始是怎么劃分的 ? 需要方案提供商手動配置,存在靜態(tài)的全局變量中,鴻蒙默認只配置了一段.

struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX];//物理段數(shù)組,最大32段
INT32 g_vmPhysSegNum = 0; //總段數(shù)
LosVmPage *g_vmPageArray = NULL;//物理頁框數(shù)組
size_t g_vmPageArraySize;//總物理頁框數(shù)


/* Physical memory area array */
STATIC struct VmPhysArea g_physArea[] = {//這里只有一個區(qū)域,即只生成一個段
    {
        .start = SYS_MEM_BASE, //整個物理內(nèi)存基地址,#define SYS_MEM_BASE            DDR_MEM_ADDR ,  0x80000000
        .size = SYS_MEM_SIZE_DEFAULT,//整個物理內(nèi)存總大小 0x07f00000
    },
};

有了段和這些全局變量,就可以對內(nèi)存初始化了. OsVmPageStartup是對物理內(nèi)存的初始化,它被整個系統(tǒng)內(nèi)存初始化 OsSysMemInit所調(diào)用. 直接上代碼.

/******************************************************************************
 完成對物理內(nèi)存整體初始化,本函數(shù)一定運行在實模式下
 1.申請大塊內(nèi)存g_vmPageArray存放LosVmPage,按4K一頁劃分物理內(nèi)存存放在數(shù)組中.
******************************************************************************/
VOID OsVmPageStartup(VOID)
{
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;
    paddr_t pa;
    UINT32 nPage;
    INT32 segID;

    OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));//校正 g_physArea size

    nPage = OsVmPhysPageNumGet();//得到 g_physArea 總頁數(shù)
    g_vmPageArraySize = nPage * sizeof(LosVmPage);//頁表總大小
    g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//實模式下申請內(nèi)存,此時還沒有初始化MMU

    OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));//

    OsVmPhysSegAdd();// 完成對段的初始化
    OsVmPhysInit();// 加入空閑鏈表和設置置換算法,LRU(最近最久未使用)算法

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {//遍歷物理段,將段切成一頁一頁
        seg = &g_vmPhysSeg[segID];
        nPage = seg->size >> PAGE_SHIFT;//本段總頁數(shù)
        for (page = seg->pageBase, pa = seg->start; page <= seg->pageBase + nPage;//遍歷,算出每個頁框的物理地址
             page++, pa += PAGE_SIZE) {
            OsVmPageInit(page, pa, segID);//對物理頁框進行初始化,注意每頁的物理地址都不一樣
        }
        OsVmPageOrderListInit(seg->pageBase, nPage);//伙伴算法初始化,將所有頁加入空閑鏈表供分配
    }
}

結(jié)合中文注釋,代碼很好理解,此番操作之后全局變量里的值就都各就各位了,可以開始工作了.

如何分配/回收物理內(nèi)存?答案是伙伴算法

伙伴算法系列篇中有說過好幾篇,這里再看圖理解下什么伙伴算法,伙伴算法注重物理內(nèi)存的連續(xù)性,注意是連續(xù)性!

pIYBAGCFFSWAesbmAADiOxzImKk196.png

?結(jié)合圖比如,要分配4(2^2)頁(16k)的內(nèi)存空間,算法會先從free_area2中查看free鏈表是否為空,如果有空閑塊,則從中分配,如果沒有空閑塊,就從它的上一級free_area3(每塊32K)中分配出16K,并將多余的內(nèi)存(16K)加入到free_area2中去。如果free_area3也沒有空閑,則從更上一級申請空間,依次遞推,直到free_area max_order,如果頂級都沒有空間,那么就報告分配失敗。

釋放是申請的逆過程,當釋放一個內(nèi)存塊時,先在其對于的free_area鏈表中查找是否有伙伴存在,如果沒有伙伴塊,直接將釋放的塊插入鏈表頭。如果有或板塊的存在,則將其從鏈表摘下,合并成一個大塊,然后繼續(xù)查找合并后的塊在更大一級鏈表中是否有伙伴的存在,直至不能合并或者已經(jīng)合并至最大塊2^max_order為止。

看過系列篇文章的可能都發(fā)現(xiàn)了,筆者喜歡用講故事和打比方來說明內(nèi)核運作機制,為了更好的理解,同樣打個比方,筆者認為伙伴算法很像是賣標準豬肉塊的算法.

物理內(nèi)存是一整頭豬,已經(jīng)切成了1斤1斤的了,但是還都連在一起,每一斤上都貼了個標號, 而且老板只按 1斤(2^0), 2斤(2^1), 4斤(2^2),...256斤(2^8)的方式來賣.售貨柜上分成了9組

張三來了要7斤豬肉,怎么辦?**給8斤,注意是給8斤啊 ,因為它要嚴格按它的標準來賣.**張三如果歸還了,查看現(xiàn)有8斤組里有沒有序號能連在一塊的,有的話2個8斤合成16斤,放到16斤組里去.如果沒有這8斤豬肉將掛到上圖中第2組(2^3)再賣.

大家腦海中有畫面了嗎? 那么問題來了,它為什么要這么賣豬肉,好處是什么? 簡單啊:至少兩個好處:

第一:賣肉速度快,效率高,標準化的東西最好賣了.

第二:可防止碎肉太多,后面的人想買大塊的豬肉買不到了.請仔細想想是不是這樣的?如果每次客戶來了要多少就割多少出去,運行一段時候后你還能買到10斤連在一塊的豬肉嗎?很可能給是一包碎肉,里面甚至還有一兩一兩的邊角肉,碎肉的結(jié)果必然是管理麻煩,效率低啊.如果按伙伴算法的結(jié)果是運行一段時候后,圖中0,1,2各組中都有可賣的豬肉啊,張三哥歸還了那8斤(其實他指向要7斤)豬肉,王五兄弟來了要6斤,直接把張三哥歸還的給王五就行了.效率極高.

那么問題又來了,凡事總有兩面性,它的壞處是什么?也簡單啊 :至少兩個壞處:

第一:浪費了!,白給的三斤對王五沒用啊,浪費的問題有其他辦法解決,但不是在這個層面去解決,而是由slab分配器解決,這里不重點說后續(xù)會專門講slab分配器是如何解決這個問題的.

第二:合并要求太嚴格了,一定得是伙伴(連續(xù))才能合并成更大的塊.這樣也會導致時間久了很難有大塊的連續(xù)性的豬肉塊.

比方打完了,鴻蒙內(nèi)核是如何實現(xiàn)賣肉算法的呢?請看代碼

LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages)
{
    struct VmFreeList *list = NULL;
    LosVmPage *page = NULL;
    UINT32 order;
    UINT32 newOrder;

    if ((seg == NULL) || (nPages == 0)) {
        return NULL;
    }
 //因為伙伴算法分配單元是 1,2,4,8 頁,比如nPages = 3時,就需要從 4號空閑鏈表中分,剩余的1頁需要劈開放到1號空閑鏈表中
    order = OsVmPagesToOrder(nPages);//根據(jù)頁數(shù)計算出用哪個塊組
    if (order < VM_LIST_ORDER_MAX) {//order不能大于9 即:256*4K = 1M 可理解為向內(nèi)核堆申請內(nèi)存一次不能超過1M
        for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {//沒有就找更大塊
            list = &seg->freeList[newOrder];//從最合適的塊處開始找
            if (LOS_ListEmpty(&list->node)) {//理想情況鏈表為空,說明沒找到
                continue;//繼續(xù)找更大塊的
            }
            page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);//找第一個節(jié)點就行,因為鏈表上掛的都是同樣大小物理頁框
            goto DONE;
        }
    }
    return NULL;
DONE:
    OsVmPhysFreeListDelUnsafe(page);//將物理頁框從鏈表上摘出來
    OsVmPhysPagesSpiltUnsafe(page, order, newOrder);//將物理頁框劈開,把用不了的頁再掛到對應的空閑鏈表上
    return page;
}

/******************************************************************************
 本函數(shù)很像賣豬肉的,拿一大塊肉剁,先把多余的放回到小塊肉堆里去.
 oldOrder:原本要買 2^2肉
 newOrder:卻找到個 2^8肉塊
******************************************************************************/
STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder)
{
    UINT32 order;
    LosVmPage *buddyPage = NULL;

    for (order = newOrder; order > oldOrder;) {//把肉剁碎的過程,把多余的肉塊切成2^7,2^6...標準塊,
        order--;//越切越小,逐一掛到對應的空閑鏈表上
        buddyPage = &page[VM_ORDER_TO_PAGES(order)];//@note_good 先把多余的肉割出來,這句代碼很贊!因為LosVmPage本身是在一個大數(shù)組上,page[nPages]可直接定位
        LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX);//沒掛到伙伴算法對應組塊空閑鏈表上的物理頁框的order必須是VM_LIST_ORDER_MAX
        OsVmPhysFreeListAddUnsafe(buddyPage, order);//將劈開的節(jié)點掛到對應序號的鏈表上,buddyPage->order = order
    }
}

為了方便理解代碼細節(jié),這里說一種情況:比如三哥要買3斤的,發(fā)現(xiàn)4斤,8斤的都沒有了,只有16斤的怎么辦?注意不會給16斤,只會給4斤.這時需要把肉劈開,劈成 8,4,4,其中4斤給張三哥,將剩下的8斤,4斤掛到對應鏈表上. OsVmPhysPagesSpiltUnsafe 干的就是劈豬肉的活.

伙伴算法的鏈表是怎么初始化的,再看段代碼

//初始化空閑鏈表,分配物理頁框使用伙伴算法
STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg)
{
    int i;
    UINT32 intSave;
    struct VmFreeList *list = NULL;

    LOS_SpinInit(&seg->freeListLock);//初始化用于分配的自旋鎖

    LOS_SpinLockSave(&seg->freeListLock, &intSave);
    for (i = 0; i < VM_LIST_ORDER_MAX; i++) {//遍歷伙伴算法空閑塊組鏈表
        list = &seg->freeList[i]; //一個個來
        LOS_ListInit(&list->node); //LosVmPage.node將掛到list->node上
        list->listCnt = 0;   //鏈表上的數(shù)量默認0
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}

鴻蒙是面向未來設計的系統(tǒng),高瞻遠矚,格局遠大,設計精良, 海量知識點,對內(nèi)核源碼加上中文注解已有三個多月,越深入精讀內(nèi)核源碼,越能感受到設計者的精巧用心,創(chuàng)新突破, 向開發(fā)者致敬. 可以毫不夸張的說鴻蒙內(nèi)核源碼可作為大學C語言,數(shù)據(jù)結(jié)構(gòu),操作系統(tǒng),匯編語言 四門課程的教學項目.如此寶庫,不深入研究實在是暴殄天物,于心不忍.

編輯:hfy

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 鴻蒙系統(tǒng)
    +關(guān)注

    關(guān)注

    183

    文章

    2634

    瀏覽量

    66344
收藏 人收藏

    評論

    相關(guān)推薦

    【HarmonyOS】鴻蒙內(nèi)核源碼分析(內(nèi)存管理篇)

    詳見:../kernel/base/vm有了上篇鴻蒙內(nèi)核源碼分析(內(nèi)存概念篇)的基礎(chǔ),本篇講內(nèi)存管理部分,本章源碼超級多,很燒腦,但筆者關(guān)鍵處都加了注釋。廢話不多說,開始吧。目錄
    發(fā)表于 10-14 12:05

    鴻蒙內(nèi)核源碼分析(內(nèi)存分配篇):內(nèi)存的分配方式有哪些

    分析(內(nèi)存映射篇)中說明,在調(diào)度算法切換進程時就需要切換至該進程自己的虛擬空間,即MMU上下文。物理內(nèi)存初始化物理
    發(fā)表于 11-20 10:07

    鴻蒙內(nèi)核源碼分析(內(nèi)存管理篇):虛擬內(nèi)存物理內(nèi)存是怎么管理的

    有了上篇鴻蒙內(nèi)核源碼分析(內(nèi)存概念篇)的基礎(chǔ),本篇講內(nèi)存管理部分,本章源碼超級多,很燒腦,但筆者關(guān)鍵處都加了注釋。廢話不多說,開始吧。初始化
    發(fā)表于 11-20 10:54

    鴻蒙內(nèi)核源碼分析(內(nèi)存管理篇):虛擬內(nèi)存物理內(nèi)存是怎么管理的

    有了上篇鴻蒙內(nèi)核源碼分析(內(nèi)存概念篇)的基礎(chǔ),本篇講內(nèi)存管理部分,本章源碼超級多,很燒腦,但筆者關(guān)鍵處都加了注釋。廢話不多說,開始吧。初始化
    發(fā)表于 11-20 16:48

    鴻蒙內(nèi)核源碼分析(內(nèi)存分配篇):內(nèi)存的分配方式有哪些

    ; 開發(fā)指南> 內(nèi)核開發(fā)指南> 內(nèi)存> 概述 看,有更詳細的描述,這里結(jié)合代碼說。Huawei LiteOS的內(nèi)存管理分為靜態(tài)內(nèi)存管理和動態(tài)內(nèi)存
    發(fā)表于 11-20 17:34

    LINUX系統(tǒng)引導和初始化-LINUX內(nèi)核解讀

    Linux 的系統(tǒng)引導和初始化 ----------Linux2.4.22內(nèi)核解讀之一 一、 系統(tǒng)引導和初始化概述 相關(guān)代碼(引導扇區(qū)的程序及其輔助程序,以 x86體系為例): \linux-2.4.22\arch\i386\b
    發(fā)表于 11-03 22:31 ?53次下載

    Linux內(nèi)存初始化

    之前有幾篇博客詳細介紹了Xen的內(nèi)存初始化,確實感覺這部分內(nèi)容蠻復雜的。這兩天在看Linux內(nèi)核啟動中內(nèi)存初始化,也是看的云里霧里的,想嘗
    發(fā)表于 10-12 11:16 ?0次下載

    解析內(nèi)核初始化時根內(nèi)存盤的加載過程

    內(nèi)存盤中作為根盤。 當同時配置了初始化內(nèi)存盤(Initail RAM Disk)時, 內(nèi)核初始化時可以在安裝主盤之前, 通過引導程序所加
    發(fā)表于 11-08 10:40 ?0次下載

    uboot和內(nèi)核里phy的初始化_內(nèi)核里的雙網(wǎng)絡配置及phy的初始化

    uboot 和內(nèi)核里 phy 的初始化,以及內(nèi)核里的雙網(wǎng)絡配置及 phy 的初始化。 本文以盈鵬飛嵌入式的CoM-335x(基于AM335x)核心板及網(wǎng)絡芯片LAN8720 為例,說明
    的頭像 發(fā)表于 05-17 08:19 ?1.2w次閱讀

    Linux內(nèi)核初始化過程中的調(diào)用順序

    所有的__init函數(shù)在區(qū)段.initcall.init中還保存了一份函數(shù)指針,在初始化內(nèi)核會通過這些函數(shù)指針調(diào)用這些__init函數(shù)指針,并在整個初始化完成后,釋放整個init區(qū)段(包括.init.text,.initcal
    發(fā)表于 05-12 08:40 ?1614次閱讀

    UCOS2系統(tǒng)內(nèi)核講述(五) _初始化TCB詳情

    UCOS2系統(tǒng)內(nèi)核講述(五)_初始化TCB詳情
    的頭像 發(fā)表于 03-25 09:39 ?2150次閱讀
    UCOS2系統(tǒng)<b class='flag-5'>內(nèi)核</b>講述(五) _<b class='flag-5'>初始化</b>TCB詳情

    UCOS2系統(tǒng)內(nèi)核講述(二)_ 初始化調(diào)用函數(shù)

    UCOS2系統(tǒng)內(nèi)核講述(二)_初始化調(diào)用函數(shù)
    的頭像 發(fā)表于 03-25 09:57 ?1772次閱讀
    UCOS2系統(tǒng)<b class='flag-5'>內(nèi)核</b>講述(二)_ <b class='flag-5'>初始化</b>調(diào)用函數(shù)

    鴻蒙內(nèi)核源碼:內(nèi)核空間是怎么初始化的?

    data段 該段用于存儲初始化的全局變量,初始化為0的全局變量出于編譯優(yōu)化的策略還是被保存在BSS段。
    的頭像 發(fā)表于 04-26 14:43 ?1862次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>源碼:<b class='flag-5'>內(nèi)核</b>空間是怎么<b class='flag-5'>初始化</b>的?

    Armlinux內(nèi)核移植及系統(tǒng)初始化過程分析

    Armlinux內(nèi)核移植及系統(tǒng)初始化過程分析說明。
    發(fā)表于 04-06 15:53 ?11次下載

    Linux內(nèi)存方面的初始化和常見的內(nèi)存分配方式

    在 start_kernel 內(nèi)核初始化函數(shù)中,一共調(diào)用 86 個函數(shù)去初始化,其中有一個 mm_init 函數(shù),用以初始化內(nèi)存。 star
    的頭像 發(fā)表于 09-28 16:13 ?790次閱讀
    Linux<b class='flag-5'>內(nèi)存</b>方面的<b class='flag-5'>初始化</b>和常見的<b class='flag-5'>內(nèi)存</b>分配方式