鸿蒙轻内核A核源码分析系列三 物理内存(1) 原创 精华
鸿蒙轻内核A核源码分析系列三 物理内存(1)
文中相关设备来源于51CTO 鸿蒙技术社区【开发板-漂流计划】
从本篇开始,我们分析下鸿蒙轻内核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_BASE
为DDR_MEM_ADDR
的宏名称,DDR_MEM_ADDR
和SYS_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
中索引。⑷处开始的函数OsVmPhysFreeListInit
和OsVmPhysLruInit
初始化伙伴双向链表和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;
}
跟着楼主了解内核少走不少弯路。