如何初始化物理內(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ù)性!
?結(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
-
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2634瀏覽量
66344
發(fā)布評論請先 登錄
相關(guān)推薦
評論