鸿蒙轻内核A核源码分析系列三 物理内存(1)

从本篇开始,我们分析下鸿蒙轻内核A核的内存管理部分,包括物理内存、虚拟内存、虚拟映射等部分。物理内存(Physical memory)是指通过物理内存条而获得的内存空间,相对应的概念是虚拟内存(Virtual memory)。虚拟内存使得应用进程认为它拥有一个连续完整的内存地址空间,而通常是通过虚拟内存和物理内存的映射对应着多个物理内存页。本文我们先来熟悉下OpenHarmony鸿蒙轻内核提供的物理内存(Physical memory)管理模块。

本文中所涉及的源码,以OpenHarmony LiteOS-A内核为例,均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_a 获取。如果涉及开发板,则默认以hispark_taurus为例。

我们首先了解了物理内存管理的结构体,接着阅读了物理内存如何初始化,然后分析了物理内存的申请、释放和查询等操作接口的源代码。

1、物理内存结构体介绍

1.1、物理内存页LosVmPage

鸿蒙轻内核A核的物理内存采用了段页式管理,每个物理内存段被分割为物理内存页。在头文件kernel/base/include/los_vm_page.h中定义了物理内存页结构体,以及内存页数组g_vmPageArray及数组大小g_vmPageArraySize。物理内存页结构体LosVmPage可以和物理内存页一一对应,也可以对应多个连续的内存页,此时使用nPages指定内存页的数量。

typedef struct VmPage {
    LOS_DL_LIST         node;        /**< 物理内存页节点,挂在VmFreeList空闲内存页链表上 */
    PADDR_T             physAddr;    /**< 物理内存页内存开始地址*/
    Atomic              refCounts;   /**< 物理内存页引用计数 */
    UINT32              flags;       /**< 物理内存页标记 */
    UINT8               order;       /**< 物理内存页所在的链表数组的索引,总共有9个链表 */
    UINT8               segID;       /**< 物理内存页所在的物理内存段的编号 */
    UINT16              nPages;      /**< 连续物理内存页的数量 */
} LosVmPage;

extern LosVmPage *g_vmPageArray;
extern size_t g_vmPageArraySize;

在文件kernel\base\include\los_vm_common.h中定义了内存页的大小、掩码和逻辑位移值,可以看出每个内存页的大小为4KiB。

#ifndef PAGE_SIZE
#define PAGE_SIZE                        (0x1000U)
#endif
#define PAGE_MASK                        (~(PAGE_SIZE - 1))
#define PAGE_SHIFT                       (12)

1.2、物理内存段LosVmPhysSeg

在文件kernel/base/include/los_vm_phys.h中定义了物理内存段LosVmPhysSeg等几个结构体。该文件的部分代码如下所示。⑴处的宏是物理内存伙伴算法中空闲内存页节点链表数组的大小,VM_PHYS_SEG_MAX表示系统支持的物理内存段的数量。⑵处的结构体用于伙伴算法中空闲内存页节点链表数组的元素类型,除了记录双向链表,还维护链表上节点数量。⑶就是我们要介绍的物理内存段,包含开始地址,大小,内存页基地址,空闲内存页节点链表数组,LRU链表数组等成员。

⑴  #define VM_LIST_ORDER_MAX    9
    #define VM_PHYS_SEG_MAX    32

⑵  struct VmFreeList {
        LOS_DL_LIST node;   // 空闲物理内存页节点
        UINT32 listCnt;     // 空闲物理内存页节点数量
    };

⑶  typedef struct VmPhysSeg {
        PADDR_T start;            /* 物理内存段的开始地址 */
        size_t size;              /* 物理内存段的大小,bytes */
        LosVmPage *pageBase;      /* 物理内存段第一个物理内存页结构体地址 */

        SPIN_LOCK_S freeListLock; /* 伙伴算法双向链表自旋锁 */
        struct VmFreeList freeList[VM_LIST_ORDER_MAX];  /* 空闲物理内存页的伙伴双向链表 */

        SPIN_LOCK_S lruLock;  /* LRU双向链表自旋锁 */
        size_t lruSize[VM_NR_LRU_LISTS];  /* LRU大小 */
        LOS_DL_LIST lruList[VM_NR_LRU_LISTS];/* LRU双向链表 */
    } LosVmPhysSeg;

    struct VmPhysArea {
        PADDR_T start;  // 物理内存区开始地址
        size_t size;    // 物理内存区大小
    };

kernel/base/vm/los_vm_phys.c文件中定义了物理内存区数组g_physArea[],如下代码所示,其中SYS_MEM_BASEDDR_MEM_ADDR的宏名称,DDR_MEM_ADDRSYS_MEM_SIZE_DEFAULT定义在文件./device/hisilicon/hispark_taurus/sdk_liteos/board/target_config.h中,表示开发板相关的物理内存地址和大小。

STATIC struct VmPhysArea g_physArea[] = {
    {
        .start = SYS_MEM_BASE,
        .size = SYS_MEM_SIZE_DEFAULT,
    },
};

看下物理内存区VmPhysArea和物理内存段的LosVmPhysSeg区别,前者信息教少,主要记录开始地址和大小,为一块物理内存的最简单描述;后者除了物理内存块开始地址和大小,还维护物理页开始地址,空闲物理页伙伴链表,LRU链表,相应的自旋锁等信息。

上面提到了伙伴算法,先看下伙伴算法的示意图,如下。每个物理内存段都分割为一个一个的内存页,空闲的内存页挂载在空闲内存页节点链表上。共有9个空闲内存页节点链表,这些链表组成链表数组。第一个链表上的内存页节点大小为1个内存页,第二个链表上的内存页节点大小为2个内存页,第三个链表上的内存页节点大小为4个内存页,依次下去,第9个链表上的内存页节点大小为2^8个内存页。申请内存、释放内存时会操作这些空闲内存页节点链表,后文详细分析。

1.3、物理内存伙伴位图

上文提到伙伴算法,还需要了解下伙伴位图。在伙伴算法中,每个链表的索引都对应一个位图。 位图的某位对应于两个伙伴块,为1就表示其中一块忙,为0表示两块都闲或都在使用 。系统每次分配和回收伙伴块时都要对它们的伙伴位 跟1进行异或运算 。所谓异或是指刚开始时,两个伙伴块都空闲,它们的伙伴位为0,如果其中一块被使用,异或后得1;如果另一块也被使用,异或后得0;如果前面一块回收了异或后得1;如果另一块也回收了异或后得0。位图用于在释放内存页块时,判断两块内存是否属于地址连续的伙伴内存块。

在文件kernel/base/include/los_vm_phys.h中定义了2个比较重要的和伙伴位图相关的宏,如下。⑴处的宏VM_ORDER_TO_PHYS(order)表示对应每个空闲链表都有一个位来标记伙伴内存块。⑵处宏VM_PHYS_TO_ORDER(phys)把物理内存地址转换为空闲链表索引。那么问题是,物理内存地址和索引有对应关系?物理地址已基于内存页大小进行对齐。理论上这个值可大可小,不明白为什么这么设计?TODO。

⑴  #define VM_ORDER_TO_PHYS(order)  (1 << (PAGE_SHIFT + (order)))
⑵  #define VM_PHYS_TO_ORDER(phys)   (min(LOS_LowBitGet((phys) >> PAGE_SHIFT), VM_LIST_ORDER_MAX - 1))

2、物理内存管理模块初始化

本节主要讲解物理内存管理模块是如何初始化的,核心函数是OsVmPageStartup()。在讲解之前,会先看下物理内存初始化过程中的一些内部函数。

2.1 物理内存管理初始化内部函数

2.1.1 函数OsVmPhysSegCreate

函数OsVmPhysSegCreate用于把指定的一个物理内存区VmPhysArea转换为物理内存段LosVmPhysSeg。传入的2个参数分别为物理内存区的开始内存地址和大小。⑴处表示系统支持的物理内存段的数量为32个,超过则转换错误。⑵处从物理内存段全局数组g_vmPhysSeg中获取一个可用的物理内存段。⑶处如果物理内存段seg为数组g_vmPhysSeg中的第一个元素,则跳过循环体直接执行⑸设置物理内存段的开始地址和大小。如果不为第一个元素,并且前一个物理内存段的开始地址在要转换的物理内存段的结束地址之后,则执行⑷处代码覆盖前一个物理内存段。在配置物理内存区的时候,需要注意这里的影响。

STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size)
{
    struct VmPhysSeg *seg = NULL;

⑴  if (g_vmPhysSegNum >= VM_PHYS_SEG_MAX) {
        return -1;
    }

⑵  seg = &g_vmPhysSeg[g_vmPhysSegNum++];
⑶  for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) {
⑷      *seg = *(seg - 1);
    }
⑸  seg->start = start;
    seg->size = size;

    return 0;
}

函数OsVmPhysSegAdd调用上述函数OsVmPhysSegCreate依次把配置的多个物理内存区一一进行转换,对于开发板hispark_taurus只配置了一块物理内存区域。

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[i].start, g_physArea[i].size);
        if (ret != 0) {
            VM_ERR("create phys seg failed");
        }
    }
}

2.1.2 函数OsVmPhysInit

函数OsVmPhysInit继续初始化物理内存段信息。⑴处循环物理内存段数组,这里不是循环32次,而是多少个物理段就循环遍历多少次。遍历到每一个物理内存段,然后执行⑵设置当前物理内存段的第一个物理页结构体的地址,每一个物理内存页都有自己的结构体LosVmPage,这些结构体维护在通过malloc内存堆申请的g_vmPageArray数组里,后文会详细讲述。⑶处seg->size >> PAGE_SHIFT计算当前内存段对于的内存页数量,然后更新nPages,这是后续物理内存段第一个内存页对应的的物理内存页结构体在数组g_vmPageArray中索引。⑷处开始的函数OsVmPhysFreeListInitOsVmPhysLruInit初始化伙伴双向链表和LRU双向链表,后续分析这2个函数。

VOID OsVmPhysInit(VOID)
{
    struct VmPhysSeg *seg = NULL;
    UINT32 nPages = 0;
    int i;

    for (i = 0; i < g_vmPhysSegNum; i++) {
⑴      seg = &g_vmPhysSeg[i];
⑵      seg->pageBase = &g_vmPageArray[nPages];
⑶      nPages += seg->size >> PAGE_SHIFT;
⑷      OsVmPhysFreeListInit(seg);
        OsVmPhysLruInit(seg);
    }
}

2.1.3 函数OsVmPhysFreeListInit

每个物理内存段使用9个空闲物理内存页节点链表来维护空闲物理内存页。OsVmPhysFreeListInit函数用于初始化指定物理内存段的空闲物理内存页节点链表。操作前后需要开启、关闭空闲链表自旋锁。⑴处遍历空闲物理内存页节点链表数组,然后执行⑵初始化每个双向链表。⑶处把每个链表中的空闲物理内存页的数量初始化为0。

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);
⑶      list->listCnt = 0;
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}

2.1.4 函数OsVmPhysLruInit

和上个函数类似,函数OsVmPhysLruInit初始化指定物理内存段的LRU链表数组中的LRU链表。LRU链表分五类,由枚举类型enum OsLruList定义。代码较简单,读者自行阅读代码即可。

STATIC VOID OsVmPhysLruInit(struct VmPhysSeg *seg)
{
    INT32 i;
    UINT32 intSave;
    LOS_SpinInit(&seg->lruLock);

    LOS_SpinLockSave(&seg->lruLock, &intSave);
    for (i = 0; i < VM_NR_LRU_LISTS; i++) {
        seg->lruSize[i] = 0;
        LOS_ListInit(&seg->lruList[i]);
    }
    LOS_SpinUnlockRestore(&seg->lruLock, intSave);
}

2.1.5 函数OsVmPageInit

函数OsVmPageInit用于初始化物理内存页的初始值,该函数需要3个参数,分别是物理内存页结构体地址,物理内存页的开始地址,物理内存段编号。⑴处初始化内存页的链表节点,这个链表节点通常会挂载在伙伴算法的空闲内存页节点链表上。⑵处设置内存页标记为空闲内存页FILE_PAGE_FREE,该值由枚举类型enum OsPageFlags定义。⑶处设置内存页的引用计数为0。⑷处设置内存页的开始地址。⑸处设置内存页所在的物理内存段的编号。⑹处设置内存页顺序order初始值,此时不属于任何空闲内存页节点链表。⑺处设置内存页的nPages数值为0。⑻处的宏VMPAGEINIT调用函数OsVmPageInit并自动增加内存页结构体page地址和内存页pa地址。

STATIC VOID OsVmPageInit(LosVmPage *page, paddr_t pa, UINT8 segID)
{
⑴  LOS_ListInit(&page->node);
⑵  page->flags = FILE_PAGE_FREE;
⑶  LOS_AtomicSet(&page->refCounts, 0);
⑷  page->physAddr = pa;
⑸  page->segID = segID;
⑹  page->order = VM_LIST_ORDER_MAX;
⑺  page->nPages = 0;
}

...

#define VMPAGEINIT(page, pa, segID) do {    \
⑻   OsVmPageInit(page, pa, segID);         \
    (page)++;                               \
    (pa) += PAGE_SIZE;                      \
} while (0)

2.2 物理内存页初始化函数VOID OsVmPageStartup(VOID)

了解上述几个内部函数后,我们正式开始阅读物理内存页初始化函数VOID OsVmPageStartup(VOID)。系统在启动时,该函数用于初始化物理内存,把物理内存段划分割为为物理内存页。该函数被kernel/base/vm/los_vm_boot.c中的UINT32 OsSysMemInit(VOID)调用,进一步被文件platform/los_config.c中的INT32 OsMain(VOID)函数调用。下面详细分析下函数的代码。

⑴处的g_vmBootMemBase初始值为(UINTPTR)&__bss_end,表示系统可用内存在bss段之后;ROUNDUP用于内存向上对齐。函数OsVmPhysAreaSizeAdjust()用于调整物理区的开始地址和大小。⑵处的 OsVmPhysPageNumGet()计算物理内存段可以划分多少物理内存页,此行代码重新计算物理内存页数目,此时每个物理页对应一个物理页结构体,相应结构体也占用内存空间。 ⑶处计算物理页结构体数组的大小,数组的每个元素对应每个物理页结构体LosVmPage。接下来一行调用函数OsVmBootMemAlloc为物理页结构体数组g_vmPageArray申请内存空间,申请的内存空间从地址g_vmBootMemBase截取指定的长度。⑷处再次调用函数OsVmPhysAreaSizeAdjust()用于调整物理内存区的开始地址和大小,确保基于内存页对齐。⑸处调用函数OsVmPhysSegAdd()转换为物理内存段,⑹处调用OsVmPhysInit函数初始化物理内存段的空闲物理内存页节点链表和LRU链表。上文分析过这几个内部函数。⑺处遍历每个物理内存段,获取遍历到的物理内存段的总页数nPage。⑻处为提升初始化物理内存页的性能,把页数分为8份,count为每份的内存页的数目,left为等分为8份后剩余的内存页数。⑼处循环初始化物理内存页,⑽处初始化剩余的物理内存页。⑾处的函数OsVmPageOrderListInit把物理内存页插入到空闲内存页节点链表,该函数进一步调用OsVmPhysPagesFreeContiguous函数,后续再分析该函数。初始化完成后,物理内存段上的内存页都挂载到空闲内存页节点链表上了。

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));

    /*
     * Pages getting from OsVmPhysPageNumGet() interface here contain the memory
     * struct LosVmPage occupied, which satisfies the equation:
     * nPage * sizeof(LosVmPage) + nPage * PAGE_SIZE = OsVmPhysPageNumGet() * PAGE_SIZE.
     */
⑵  nPage = OsVmPhysPageNumGet() * PAGE_SIZE / (sizeof(LosVmPage) + PAGE_SIZE);
⑶  g_vmPageArraySize = nPage * sizeof(LosVmPage);
    g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);

⑷  OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));

⑸  OsVmPhysSegAdd();
⑹  OsVmPhysInit();

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
⑺      seg = &g_vmPhysSeg[segID];
        nPage = seg->size >> PAGE_SHIFT;
⑻      UINT32 count = nPage >> 3; /* 3: 2 ^ 3, nPage / 8, cycle count */
        UINT32 left = nPage & 0x7; /* 0x7: nPage % 8, left page */

⑼      for (page = seg->pageBase, pa = seg->start; count > 0; count--) {
            /* note: process large amount of data, optimize performance */
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
            VMPAGEINIT(page, pa, segID);
        }
        for (; left > 0; left--) {
⑽          VMPAGEINIT(page, pa, segID);
        }
⑾      OsVmPageOrderListInit(seg->pageBase, nPage);
    }
}

3、物理内存管理模块接口

学习过物理内存初始化后,接下来我们会分析物理内存管理模块的接口函数,包含申请、释放、查询等功能接口。

3.1 申请物理内存页接口

3.1.1 申请物理内存页接口介绍

申请物理内存页的接口有3个,分别用于满足不同的申请需求。LOS_PhysPagesAllocContiguous函数的传入参数为要申请物理内存页的数目,返回值为申请到的物理内存页对应的内核虚拟地址空间中的虚拟内存地址。⑴处调用函数OsVmPhysPagesGet申请指定数目的物理内存页,然后⑵处调用函数OsVmPageToVaddr转换为内核虚拟内存地址。函数LOS_PhysPageAlloc申请一个物理内存页,返回值为申请到的物理页对应的物理页结构体地址。代码比较简单,见⑶处,调用函数OsVmPageToVaddr传入ONE_PAGE参数申请1个物理内存页。函数LOS_PhysPagesAlloc用于申请nPages个物理内存页,并挂在双向链表list上,返回值为实际申请到的物理页数目。⑷处循环调用函数OsVmPhysPagesGet()申请一个物理内存页,如果申请成功不为空,则插入到双向链表,申请成功的物理页的数目加1;如果申请失败则跳出循环。⑹返回实际申请到的物理页的数目。

VOID *LOS_PhysPagesAllocContiguous(size_t nPages)
{
    LosVmPage *page = NULL;

    if (nPages == 0) {
        return NULL;
    }

⑴  page = OsVmPhysPagesGet(nPages);
    if (page == NULL) {
        return NULL;
    }

⑵   return OsVmPageToVaddr(page);
}
......

LosVmPage *LOS_PhysPageAlloc(VOID)
{
⑶   return OsVmPhysPagesGet(ONE_PAGE);
}

size_t LOS_PhysPagesAlloc(size_t nPages, LOS_DL_LIST *list)
{
    LosVmPage *page = NULL;
    size_t count = 0;

    if ((list == NULL) || (nPages == 0)) {
        return 0;
    }

    while (nPages--) {
⑷      page = OsVmPhysPagesGet(ONE_PAGE);
        if (page == NULL) {
            break;
        }
⑸      LOS_ListTailInsert(list, &page->node);
        count++;
    }

⑹   return count;
}

3.1.2 申请物理内存页内部接口实现

3个内存页申请函数都调用了函数OsVmPhysPagesGet,下文会详细分析申请物理内存页内部接口实现。

3.1.2.1 函数OsVmPhysPagesGet

函数OsVmPhysPagesGet用于申请指定数量的物理内存页,返回值为物理内存页结构体地址。⑴处遍历物理内存段数组,对遍历到的物理内存段执行⑵处代码,调用函数OsVmPhysPagesAlloc()从指定的内存段中申请指定数目的物理内存页。如果申请成功,则执行⑶把内存页的引用计数初始化为0,根据注释,如果是连续的内存页,则第一个内存页持有引用计数数值。接下来以后更新内存页的数量,并返回申请到的内存页的结构体地址;如果申请失败则继续循环申请或者返回NULL。

STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;
    UINT32 segID;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
⑴      seg = &g_vmPhysSeg[segID];
        LOS_SpinLockSave(&seg->freeListLock, &intSave);
⑵      page = OsVmPhysPagesAlloc(seg, nPages);
        if (page != NULL) {
            /* the first page of continuous physical addresses holds refCounts */
⑶          LOS_AtomicSet(&page->refCounts, 0);
            page->nPages = nPages;
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            return page;
        }
        LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    }
    return NULL;
}
3.1.2.2 函数OsVmPhysPagesAlloc

从上文的介绍,我们知道物理内存段包含一个空闲内存页节点链表数组,数组大小为9。数组中的每个链表上的内存页节点的大小等于2的幂次方个内存页,例如:第0个链表上挂载的空闲内存节点的大小为2的0次方个内存页,即1个内存页;第8个链表上挂载的内存页节点的大小为2的8次方个内存页,即256个内存页。相同大小的内存块挂在同一个链表上进行管理。

分析函数OsVmPhysPagesAlloc之前,先看下函数OsVmPagesToOrder,该函数根据指定的物理页的数目计算属于空闲内存页节点链表数组中的第几个双向链表。当nPages为最小1时,order取值为0;当为2时,order取值1...等于取底为2的对数Log2(nPages)。

#define VM_ORDER_TO_PAGES(order) (1 << (order))
......
UINT32 OsVmPagesToOrder(size_t nPages)
{
    UINT32 order;

    for (order = 0; VM_ORDER_TO_PAGES(order) < nPages; order++);

    return order;
}

继续分析下函数OsVmPhysPagesAlloc(),该函数基于传入参数从指定的内存段申请指定数目的内存页。⑴处调用的函数上文已经讲述,根据内存页数目计算出链表数组索引值。如果索引值小于链表最大索引值VM_LIST_ORDER_MAX,则执行⑵从小内存页节点向大内存页节点循环各个双向链表。⑶处获取双向链表,如果空闲链表为空则继续循环;如果不为空,则执行⑷获取链表上的空闲内存页结构体。

如果根据内存页数计算出的数组索引值大于等于链表最大索引值VM_LIST_ORDER_MAX,说明空闲链表上并没有这么大块的内存页节点,需要从物理内存段上申请,需要执行⑸调用函数OsVmPhysLargeAlloc()申请大的内存页。如果申请不到内存页则申请失败,返回NULL;如果申请到合适的内存页,则继续执行后续DONE标签代码。这些代码从空闲链表中删除,拆分,多余的空闲内存页插入空闲链表等,后文继续分析调用的这些函数。先看下这些参数的实际传入参数,order为要申请的内存页对应的链表数组索引,newOrder为实际申请的内存页对应的链表数组索引。⑹处的for循环条件中,&page[nPages]为需要申请的内存页结构体的结束地址,&tmp[1 << newOrder]表示伙伴算法中空闲内存页节点链表上的内存块的结束地址。这里为啥使用for循环呢,上面申请内存时,应该申请了多个内存节点拼接起来了。看下⑺处的函数的传入参数,&page[nPages]为需要申请的内存页结构体的结束地址,往后的部分被拆分放入空闲链表。(1 << min(order, newOrder))表示实际申请的内存页的数目。

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

⑴  order = OsVmPagesToOrder(nPages);
    if (order < VM_LIST_ORDER_MAX) {
⑵      for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {
⑶          list = &seg->freeList[newOrder];
            if (LOS_ListEmpty(&list->node)) {
                continue;
            }
⑷          page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);
            goto DONE;
        }
    } else {
        newOrder = VM_LIST_ORDER_MAX - 1;
⑸      page = OsVmPhysLargeAlloc(seg, nPages);
        if (page != NULL) {
            goto DONE;
        }
    }
    return NULL;
DONE:

    for (tmp = page; tmp < &page[nPages]; tmp = &tmp[1 << newOrder]) {
⑹       OsVmPhysFreeListDelUnsafe(tmp);
    }
    OsVmPhysPagesSpiltUnsafe(page, order, newOrder);
⑺  OsVmRecycleExtraPages(&page[nPages], nPages, ROUNDUP(nPages, (1 << min(order, newOrder))));

    return page;
}

如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ......

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ......

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:https://qr21.cn/FV7h05
相关推荐
zy张起灵5 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
Random_index6 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
鸿蒙自习室9 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu711 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
lantiandianzi12 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网12 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式12 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈13 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代1361006839314 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi14 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件