電子產(chǎn)業(yè)一站式賦能平臺(tái)

PCB聯(lián)盟網(wǎng)

搜索
查看: 1336|回復(fù): 0
收起左側(cè)

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

[復(fù)制鏈接]

2607

主題

2607

帖子

7472

積分

高級(jí)會(huì)員

Rank: 5Rank: 5

積分
7472
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2020-11-20 11:56:50 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
鴻蒙內(nèi)核源碼分析(內(nèi)存分配篇):內(nèi)存的分配方式有哪些,   
鴻蒙內(nèi)核有多少代碼
內(nèi)存部分占了整個(gè)kernel代碼量近30%,代碼多實(shí)現(xiàn)復(fù)雜,而且內(nèi)存部分還分了兩個(gè)文件夾mem,vm大書特書,為什么要分兩個(gè)文件夾?應(yīng)該是鴻蒙內(nèi)核開(kāi)發(fā)者想從目錄的名稱上區(qū)分內(nèi)存的層級(jí)概念,vm是內(nèi)存模塊的更底層實(shí)現(xiàn),mem是提供給上層使用對(duì)vm層的調(diào)用。


mem層 mem層介紹可以參考 LiteOS > 開(kāi)發(fā)指南> 內(nèi)核開(kāi)發(fā)指南> 內(nèi)存> 概述 看,有更詳細(xì)的描述,這里結(jié)合代碼說(shuō)。 Huawei LiteOS的內(nèi)存管理分為靜態(tài)內(nèi)存管理和動(dòng)態(tài)內(nèi)存管理,提供內(nèi)存初始化、分配、釋放等功能。


動(dòng)態(tài)內(nèi)存:在動(dòng)態(tài)內(nèi)存池中分配用戶指定大小的內(nèi)存塊。

  • 優(yōu)點(diǎn):按需分配。
  • 缺點(diǎn):內(nèi)存池中可能出現(xiàn)碎片。
      



靜態(tài)內(nèi)存:在靜態(tài)內(nèi)存池中分配用戶初始化時(shí)預(yù)設(shè)(固定)大小的內(nèi)存塊。

  • 優(yōu)點(diǎn):分配和釋放效率高,靜態(tài)內(nèi)存池中無(wú)碎片。
  • 缺點(diǎn):只能申請(qǐng)到初始化預(yù)設(shè)大小的內(nèi)存塊,不能按需申請(qǐng)。
      
動(dòng)態(tài)內(nèi)存管理,即在內(nèi)存資源充足的情況下,從系統(tǒng)配置的一塊比較大的連續(xù)內(nèi)存(內(nèi)存池),根據(jù)用戶需求,分配任意大小的內(nèi)存塊。當(dāng)用戶不需要該內(nèi)存塊時(shí),又可以釋放回系統(tǒng)供下一次使用。與靜態(tài)內(nèi)存相比,動(dòng)態(tài)內(nèi)存管理的好處是按需分配,缺點(diǎn)是內(nèi)存池中容易出現(xiàn)碎片。LiteOS動(dòng)態(tài)內(nèi)存支持DLINK和BEST LITTLE兩種標(biāo)準(zhǔn)算法。


動(dòng)態(tài)內(nèi)存接口函數(shù) 動(dòng)態(tài)內(nèi)存管理模塊為用戶提供下面幾種功能。
功能分類 接口名 描述
內(nèi)存初始化 LOS_MemInit 初始化一塊指定的動(dòng)態(tài)內(nèi)存池,大小為size。
申請(qǐng)動(dòng)態(tài)內(nèi)存 LOS_MemAlloc 從指定動(dòng)態(tài)內(nèi)存池中申請(qǐng)size長(zhǎng)度的內(nèi)存。
釋放動(dòng)態(tài)內(nèi)存 LOS_MemFree 釋放已申請(qǐng)的內(nèi)存。
重新申請(qǐng)內(nèi)存 LOS_MemRealloc 按size大小重新分配內(nèi)存塊,并保留原內(nèi)存塊內(nèi)容。
內(nèi)存對(duì)齊分配 LOS_MemAllocAlign 從指定動(dòng)態(tài)內(nèi)存池中申請(qǐng)長(zhǎng)度為size且地址按boundary字節(jié)對(duì)齊的內(nèi)存。
分析內(nèi)存池狀態(tài) LOS_MemStatisticsGet 獲取指定內(nèi)存池的統(tǒng)計(jì)信息。
查看內(nèi)存池中最大可用空閑塊 LOS_MemGetMaxFreeBlkSize 獲取指定內(nèi)存池的最大可用空閑塊。


這里L(fēng)OS_MemAlloc被調(diào)用的情況,太長(zhǎng)還有很多沒(méi)截出來(lái)。
OsMemAllocWithCheck 采用內(nèi)存池是嵌入式內(nèi)存管理的一種常用做法,目的是減少申請(qǐng)和釋放內(nèi)存的開(kāi)銷,簡(jiǎn)單說(shuō)就是先申請(qǐng)一大塊內(nèi)存,需要時(shí)從空閑鏈表中split,怎么切割鴻蒙就看最佳適應(yīng)算法(best fit),用完了回收,node進(jìn)入空閑鏈表,并從小到大排序,如果相鄰兩塊都是可用內(nèi)存塊就合并。直接看LOS_MemAlloc內(nèi)主要函數(shù)OsMemAllocWithCheck代碼


      
  • STATIC INLINE VOID *OsMemAllocWithCheck(VOID *pool, UINT32 size, UINT32 intSave)
      
      
  • {
      
      
  •     LosMemDynNode *allocNode = NULL;
      
      
  •     UINT32 allocSize;
      
      
  •     LosMemPoolInfo *poolInfo = (LosMemPoolInfo *)pool;
      
      
  •     const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE);
      
      
  •     INT32 ret;
      
      

  •   
      
  •     if (OsMemAllocCheck(pool, intSave) == LOS_NOK) {
      
      
  •         return NULL;
      
      
  •     }
      
      

  •   
      
  •     allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
      
      
  •     if (allocSize == 0) {
      
      
  •         return NULL;
      
      
  •     }
      
      
  • retry:
      
      

  •   
      
  •     allocNode = OsMemFindSuitableFreeBlock(pool, allocSize);//從內(nèi)存池中找到合適的內(nèi)存塊
      
      
  •     if (allocNode == NULL) {
      
      
  •         if (poolInfo->flag & MEM_POOL_EXPAND_ENABLE) {
      
      
  •             ret = OsMemPoolExpand(pool, allocSize, intSave);//木有找到就擴(kuò)展內(nèi)存池
      
      
  •             if (ret == 0) {
      
      
  •                 goto retry;
      
      
  •             }
      
      
  •         }
      
      
  •         return NULL;
      
      
  •     }
      
      
  •     if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= allocNode->selfNode.sizeAndFlag) {
      
      
  •         OsMemSplitNode(pool, allocNode, allocSize);//找到了就劈開(kāi)node
      
      
  •     }
      
      
  •     OsMemListDelete(&allocNode->selfNode.freeNodeInfo, firstNode);//從空閑雙鏈表中刪除該節(jié)點(diǎn)
      
      
  •     return (allocNode + 1);
      
      
  • }

復(fù)制代碼
很顯然,最佳適應(yīng)算法(best fit)去帶來(lái)很多極小塊內(nèi)存碎片的問(wèn)題。


vm層 vm目錄:是虛擬內(nèi)存的代碼實(shí)現(xiàn),包括物理內(nèi)存的段頁(yè)式管理,內(nèi)存虛擬地址<->物理地址映射,缺頁(yè)中斷處理,分配大塊內(nèi)存的伙伴算法,LRU置換算法,以及針對(duì)用戶態(tài)開(kāi)發(fā),提供的一套內(nèi)存系統(tǒng)調(diào)用接口等等,這部分官方?jīng)]有提供任何文檔,代碼注釋也很少,全靠硬摸。


先說(shuō)三種虛擬空間 空間(space)這個(gè)概念很重要,還記得進(jìn)程描述符(LosProcessCB)里的LosVmSpace  *vmSpace嗎?它是進(jìn)程使用內(nèi)存的方式,空間就是邊界,進(jìn)程只能在劃定的空間里運(yùn)行,任何指令都不能越界運(yùn)行。

在鴻蒙內(nèi)核源碼分析(內(nèi)存分配篇)中已講明虛擬內(nèi)存是MMU帶出來(lái)的概念,為解決物理內(nèi)存滿足不了多進(jìn)程對(duì)內(nèi)存的需要。虛擬內(nèi)存可以遠(yuǎn)大于物理內(nèi)存。虛擬空間是進(jìn)程層面的概念,每個(gè)進(jìn)程都有一個(gè),給進(jìn)程獨(dú)享整個(gè)物理內(nèi)存的假象。對(duì)鴻蒙來(lái)說(shuō)操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間(kernel space),應(yīng)用程序運(yùn)行在用戶空間(user space), 在運(yùn)行期間需動(dòng)態(tài)分配的向堆空間(heap space)申請(qǐng)內(nèi)存。具體看代碼會(huì)更清晰些。 從空間的初始化調(diào)用關(guān)系上可以看出只有這三種空間,所不同的是 內(nèi)核虛擬空間,堆虛擬空間只有一個(gè),而每一個(gè)用戶進(jìn)程都有屬于自己的用戶虛擬空間?纯此麄兂跏蓟a:

  • //內(nèi)核虛擬空間初始化
      
  • BOOL OsKernVmSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)
      
  • {
      
  •     vmSpace->base = KERNEL_ASPACE_BASE;
      
  •     vmSpace->size = KERNEL_ASPACE_SIZE;
      
  •     vmSpace->mapBase = KERNEL_VMM_BASE;
      
  •     vmSpace->mapSize = KERNEL_VMM_SIZE;
      
  • #ifdef LOSCFG_DRIVERS_TZDRIVER
      
  •     vmSpace->codeStart = 0;
      
  •     vmSpace->codeEnd = 0;
      
  • #endif
      
  •     return OsVmSpaceInitCommon(vmSpace, virtTtb);
      
  • }
      
  • //動(dòng)態(tài)分配空間初始化
      
  • BOOL OsVMallocSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)
      
  • {
      
  •     vmSpace->base = VMALLOC_START;
      
  •     vmSpace->size = VMALLOC_SIZE;
      
  •     vmSpace->mapBase = VMALLOC_START;
      
  •     vmSpace->mapSize = VMALLOC_SIZE;
      
  • #ifdef LOSCFG_DRIVERS_TZDRIVER
      
  •     vmSpace->codeStart = 0;
      
  •     vmSpace->codeEnd = 0;
      
  • #endif
      
  •     return OsVmSpaceInitCommon(vmSpace, virtTtb);
      
  • }
      
  • //用戶虛擬空間初始化
      
  • BOOL OsUserVmSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)
      
  • {
      
  •     vmSpace->base = USER_ASPACE_BASE;
      
  •     vmSpace->size = USER_ASPACE_SIZE;
      
  •     vmSpace->mapBase = USER_MAP_BASE;
      
  •     vmSpace->mapSize = USER_MAP_SIZE;
      
  •     vmSpace->heapBase = USER_HEAP_BASE;
      
  •     vmSpace->heapNow = USER_HEAP_BASE;
      
  •     vmSpace->heap = NULL;
      
  • #ifdef LOSCFG_DRIVERS_TZDRIVER
      
  •     vmSpace->codeStart = 0;
      
  •     vmSpace->codeEnd = 0;
      
  • #endif
      
  •     return OsVmSpaceInitCommon(vmSpace, virtTtb);
      
  • }

復(fù)制代碼

它們唯一的區(qū)別是虛擬地址的開(kāi)始位置和大小不一樣,但是所有用戶進(jìn)程的虛擬地址都是一樣的,注意用戶進(jìn)程是一樣的,細(xì)品。

  • STATIC BOOL OsVmSpaceInitCommon(LosVmSpace *vmSpace, VADDR_T *virtTtb)
      
  • {
      
  •     LOS_RbInitTree(&vmSpace->regionRbTree, OsRegionRbCmpKeyFn, OsRegionRbFreeFn, OsRegionRbGetKeyFn);//初始化虛擬存儲(chǔ)區(qū)域-以紅黑樹(shù)組織方式
      

  •   
  •     LOS_ListInit(&vmSpace->regions);//初始化虛擬存儲(chǔ)區(qū)域-以雙循環(huán)鏈表組織方式
      
  •     status_t retval = LOS_MuxInit(&vmSpace->regionMux, NULL);//初始化互斥量
      
  •     if (retval != LOS_OK) {
      
  •         VM_ERR(“Create mutex for vm space faiLED, status: %d“, retval);
      
  •         return FALSE;
      
  •     }
      

  •   
  •     (VOID)LOS_MuxAcquire(&g_vmSpaceListMux);
      
  •     LOS_ListAdd(&g_vmSpaceList, &vmSpace->node);//加入到虛擬空間雙循環(huán)鏈表
      
  •     (VOID)LOS_MuxRelease(&g_vmSpaceListMux);
      

  •   
  •     return OsArchMmuInit(&vmSpace->archMmu, virtTtb);//對(duì)空間mmu初始化
      
  • }
      

  •   
  • //通過(guò)虛擬地址獲取所屬空間地址
      
  • LosVmSpace *LOS_SpaceGet(VADDR_T vaddr)
      
  • {
      
  •     if (LOS_IsKernelAddress(vaddr)) {
      
  •         return LOS_GetKVmSpace();
      
  •     } else if (LOS_IsUserAddress(vaddr)) {
      
  •         return OsCurrProcessGet()->vmSpace;//當(dāng)前進(jìn)程的虛擬空間
      
  •     } else if (LOS_IsVmallocAddress(vaddr)) {
      
  •         return LOS_GetVmallocSpace();
      
  •     } else {
      
  •         return NULL;
      
  •     }
      
  • }
      


復(fù)制代碼

這些空間都掛在 g_vmSpaceList 雙循環(huán)鏈表上,LOS_SpaceGet可以通過(guò)虛擬地址反查是屬于哪種空間。每一個(gè)空間都有一張頁(yè)表和物理內(nèi)存頁(yè)表形成映射關(guān)系,虛擬內(nèi)存和物理內(nèi)存都是頁(yè)對(duì)頁(yè)的映射,兩邊每頁(yè)都是4K,也必須是一樣的!否則無(wú)法完成映射。具體如何映射的將在鴻蒙內(nèi)核源碼分析(內(nèi)存映射篇)中說(shuō)明,在調(diào)度算法切換進(jìn)程時(shí)就需要切換至該進(jìn)程自己的虛擬空間,即MMU上下文。
物理內(nèi)存初始化 物理內(nèi)存部分見(jiàn)代碼: los_vm_phys.c,到了物理內(nèi)存就沒(méi)有什么進(jìn)程,空間的概念了,只有頁(yè)的概念!一頁(yè)4K 物理內(nèi)存的管理和分配都是圍繞著頁(yè)展開(kāi)的,鴻蒙對(duì)物理內(nèi)存使用了段頁(yè)式管理,看代碼吧,關(guān)鍵處都加了注釋。

  • /* Physical memory area array */
      
  • STATIC struct VmPhysArea g_physArea[] = {
      
  •     {
      
  •         .start = SYS_MEM_BASE, //整個(gè)物理內(nèi)存基地址
      
  •         .size = SYS_MEM_SIZE_DEFAULT,//整個(gè)物理內(nèi)存總大小
      
  •     },
      
  • };
      
  • //* page初始化
      
  • 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 總頁(yè)數(shù)
      
  •     g_vmPageArraySize = nPage * sizeof(LosVmPage);//頁(yè)表總大小
      
  •     g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//申請(qǐng)頁(yè)表存放區(qū)域
      

  •   
  •     OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));// g_physArea 變小
      

  •   
  •     OsVmPhysSegAdd();// 段頁(yè)綁定
      
  •     OsVmPhysInit();// 加入空閑鏈表和設(shè)置置換算法,LRU(最近最久未使用)算法
      

  •   
  •     for (segID = 0; segID < g_vmPhysSegNum; segID++) {
      
  •         seg = &g_vmPhysSeg[segID];
      
  •         nPage = seg->size >> PAGE_SHIFT;
      
  •         for (page = seg->pageBase, pa = seg->start; page <= seg->pageBase + nPage;
      
  •              page++, pa += PAGE_SIZE) {
      
  •             OsVmPageInit(page, pa, segID);//page初始化
      
  •         }
      
  •         OsVmPageOrderListInit(seg->pageBase, nPage);// 頁(yè)面分配的排序
      
  •     }
      
  • }
      
  • UINT32 OsVmPhysPageNumGet(VOID)
      
  • {
      
  •     UINT32 nPages = 0;
      
  •     INT32 i;
      

  •   
  •     for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
      
  •         nPages += g_physArea.size >> PAGE_SHIFT;//右移12位,相當(dāng)于除以4K,得出總頁(yè)數(shù)
      
  •     }
      

  •   
  •     return nPages;
      
  • }
      
  • VOID OsVmPhysSegAdd(VOID)
      
  • {
      
  •     INT32 i, ret;
      

  •   
  •     LOS_ASSERT(g_vmPhysSegNum <= VM_PHYS_SEG_MAX);
      
  •         
      
  •     for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
      
  •         ret = OsVmPhysSegCreate(g_physArea.start, g_physArea.size);//一個(gè)區(qū)對(duì)應(yīng)一個(gè)段
      
  •         if (ret != 0) {
      
  •             VM_ERR(“create phys seg failed“);
      
  •         }
      
  •     }
      
  • }
      

  •   
  • STATIC VOID OsVmPageInit(LosVmPage *page, paddr_t pa, UINT8 segID)
      
  • {
      
  •     LOS_ListInit(&page->node);//初始化鏈表節(jié)點(diǎn)
      
  •     page->flags = FILE_PAGE_FREE;//默認(rèn)空閑
      
  •     LOS_AtomicSet(&page->refCounts, 0);//0次引用
      
  •     page->physAddr = pa;//物理地址
      
  •     page->segID = segID;//所屬段
      
  •     page->order = VM_LIST_ORDER_MAX;//伙伴算法默認(rèn)級(jí)數(shù)
      
  • }
      


復(fù)制代碼

代碼中可以看出初始化對(duì)物理內(nèi)存做了幾個(gè)動(dòng)作: 1.對(duì)整個(gè)物理內(nèi)存進(jìn)行了分頁(yè),每頁(yè)框4K,存放在大頁(yè)表數(shù)組中 g_vmPageArray 2.段頁(yè)綁定,根據(jù)g_physArea數(shù)組的大小來(lái)創(chuàng)建段,因數(shù)組里只有一條數(shù)據(jù),所以只有一個(gè)段 3.初始化了回收雙鏈表和置換算法,采用了LRU置換算法。 4.對(duì)每一頁(yè)框進(jìn)行了初始化,每個(gè)頁(yè)框可用于分配,指定了物理地址,注意這是物理內(nèi)存的頁(yè)。 5.對(duì)伙伴算法初始化。
什么是伙伴算法? 簡(jiǎn)單的說(shuō)就是把所有的空閑頁(yè)面分為10個(gè)塊組,每組中塊的大小是2的冪次方個(gè)頁(yè)面,例如,第0組中塊的大小都為2的0次方 (1個(gè)頁(yè)面),第1組中塊的大小為都為2的1次方(2個(gè)頁(yè)面),第9組中塊的大小都為2的9次方(512個(gè)頁(yè)面)。也就是說(shuō),每一組中塊的大小是相同的,且這同樣大小的塊形成一個(gè)鏈表,能看懂下面這張圖的就看懂了伙伴算法,一個(gè)方塊代表一個(gè)物理頁(yè)框。
物理內(nèi)存是怎么被分配的? 物理內(nèi)存是以頁(yè)為單位被分配的,詳細(xì)看 LOS_PhysPagesAllocContiguous,看下哪些地方調(diào)用了它。 1.初始化進(jìn)程塊會(huì)用到  2.擴(kuò)展內(nèi)存池會(huì)用到,3,用戶進(jìn)程空間初始化會(huì)用到 4,內(nèi)核動(dòng)態(tài)分配的時(shí)候會(huì)到,這個(gè)上面已經(jīng)講過(guò)了。5.動(dòng)態(tài)加載可執(zhí)行程序會(huì)用到,LOS_PhysPagesAllocContiguous 調(diào)用的主要函數(shù)是:

[quote]
  

       
  • 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;
      
       
  •     }
      
       

  •   
       
  •     order = OsVmPagesToOrder(nPages);//根據(jù)頁(yè)數(shù)計(jì)算出用哪個(gè)塊組
      
       
  •     if (order < VM_LIST_ORDER_MAX) {
      
       
  •         for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {//沒(méi)有就找更大塊
      
       
  •             list = &seg->freeList[newOrder];//從最合適的塊處開(kāi)始找
      
       
  •             if (LOS_ListEmpty(&list->node)) {//沒(méi)找到
      
       
  •                 continue;//繼續(xù)找更大塊的
      
       
  •             }
      
       
  •             page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);//找到
      
       
  •             goto DONE;
      
       
  •         }
      
       
  •     }
      
       
  •     return NULL;
      
       
  • DONE:
      
       
  •     OsVmPhysFreeListDelUnsafe(page);
      
       
  •     OsVmPhysPagesSpiltUnsafe(page, order, newOrder);
      
       
  •     return page;
      
       
  • }
      

  復(fù)制代碼

[/quote] 總結(jié)下:看到這里大家腦子里應(yīng)該浮現(xiàn)出一幅圖,內(nèi)核抽象出無(wú)數(shù)個(gè)虛擬空間頁(yè)表,但實(shí)際只有一個(gè)物理內(nèi)存頁(yè)表,每個(gè)虛擬空間都要映射到了物理內(nèi)存頁(yè)表上,他們是 1:N的關(guān)系,如何保證不會(huì)錯(cuò)呢?缺頁(yè)了怎么處理?如何置換頁(yè)面?怎么才能保證效率?請(qǐng)查看《鴻蒙內(nèi)核源碼分析:虛擬地址與物理地址之間是如何映射的》



本文來(lái)源:圖解鴻蒙源碼逐行注釋分析

發(fā)表回復(fù)

本版積分規(guī)則


聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表