los_vm_phys.c 22.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/*
 * Copyright (c) 2013-2019, Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020, Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "los_vm_phys.h"
#include "los_vm_boot.h"
#include "los_vm_common.h"
#include "los_vm_map.h"
#include "los_vm_dump.h"
#include "los_process_pri.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif /* __cplusplus */
#endif /* __cplusplus */
44 45 46 47
/******************************************************************************
鸿蒙物理内存采用段页式管理
 
******************************************************************************/
48 49 50
#define ONE_PAGE    1

/* Physical memory area array */
51
STATIC struct VmPhysArea g_physArea[] = {//这里只有一个区域,即只生成一个段
52
    {
53 54
        .start = SYS_MEM_BASE, //整个物理内存基地址,#define SYS_MEM_BASE            DDR_MEM_ADDR ,  0x80000000
        .size = SYS_MEM_SIZE_DEFAULT,//整个物理内存总大小 0x07f00000
55 56 57
    },
};

58 59 60
struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX];//最大32段
INT32 g_vmPhysSegNum = 0;	//段数
//获取段数组,全局变量,变量放在 .bbs 区
61 62 63 64
LosVmPhysSeg *OsGVmPhysSegGet()
{
    return g_vmPhysSeg;
}
65
//初始化Lru置换链表
66 67 68 69
STATIC VOID OsVmPhysLruInit(struct VmPhysSeg *seg)
{
    INT32 i;
    UINT32 intSave;
70
    LOS_SpinInit(&seg->lruLock);//初始化自旋锁,自旋锁用于CPU多核同步
71 72

    LOS_SpinLockSave(&seg->lruLock, &intSave);
73
    for (i = 0; i < VM_NR_LRU_LISTS; i++) { //五个双循环链表
74 75
        seg->lruSize[i] = 0;			//记录链表节点数
        LOS_ListInit(&seg->lruList[i]);	//初始化LRU链表
76 77 78
    }
    LOS_SpinUnlockRestore(&seg->lruLock, intSave);
}
79
//创建物理段,由区划分转成段管理
80 81 82 83 84 85 86 87
STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size)
{
    struct VmPhysSeg *seg = NULL;

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

88
    seg = &g_vmPhysSeg[g_vmPhysSegNum++];//拿到一段数据
89 90 91 92 93 94 95 96
    for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) {
        *seg = *(seg - 1);
    }
    seg->start = start;
    seg->size = size;

    return 0;
}
97
//添加物理段
98 99 100 101 102 103
VOID OsVmPhysSegAdd(VOID)
{
    INT32 i, ret;

    LOS_ASSERT(g_vmPhysSegNum <= VM_PHYS_SEG_MAX);
	
104 105
    for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {//遍历g_physArea数组
        ret = OsVmPhysSegCreate(g_physArea[i].start, g_physArea[i].size);//由区划分转成段管理
106 107 108 109 110
        if (ret != 0) {
            VM_ERR("create phys seg failed");
        }
    }
}
111
//段区域大小调整
112 113 114 115 116 117 118 119 120
VOID OsVmPhysAreaSizeAdjust(size_t size)
{
    INT32 i;

    for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
        g_physArea[i].start += size;
        g_physArea[i].size -= size;
    }
}
121
//获得物理内存的总页数
122 123 124 125 126 127
UINT32 OsVmPhysPageNumGet(VOID)
{
    UINT32 nPages = 0;
    INT32 i;

    for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
128
        nPages += g_physArea[i].size >> PAGE_SHIFT;//右移12位,相当于除以4K, 计算出总页数
129 130
    }

131
    return nPages;//返回所有物理内存总页数
132
}
133
//初始化空闲链表,分配物理页框使用伙伴算法
134 135 136 137 138 139
STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg)
{
    int i;
    UINT32 intSave;
    struct VmFreeList *list = NULL;

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

    LOS_SpinLockSave(&seg->freeListLock, &intSave);
143 144 145
    for (i = 0; i < VM_LIST_ORDER_MAX; i++) {//遍历伙伴算法空闲块组链表
        list = &seg->freeList[i];	//一个个来
        LOS_ListInit(&list->node);	//LosVmPage.node将挂到list->node上
146
        list->listCnt = 0;			//链表上的数量默认0
147 148 149
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}
150
//物理段初始化
151 152 153 154 155 156 157 158
VOID OsVmPhysInit(VOID)
{
    struct VmPhysSeg *seg = NULL;
    UINT32 nPages = 0;
    int i;

    for (i = 0; i < g_vmPhysSegNum; i++) {
        seg = &g_vmPhysSeg[i];
159 160
        seg->pageBase = &g_vmPageArray[nPages];//记录本段首页物理页框地址
        nPages += seg->size >> PAGE_SHIFT;//偏移12位,按4K一页,算出本段总页数
161 162
        OsVmPhysFreeListInit(seg);	//初始化空闲链表,分配页框使用伙伴算法
        OsVmPhysLruInit(seg);		//初始化LRU置换链表
163 164
    }
}
165
//将页框挂入空闲链表,分配物理页框从空闲链表里拿
166 167 168 169 170 171 172 173 174
STATIC VOID OsVmPhysFreeListAdd(LosVmPage *page, UINT8 order)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

    if (page->segID >= VM_PHYS_SEG_MAX) {
        LOS_Panic("The page segment id(%d) is invalid\n", page->segID);
    }

175
    page->order = order;// page记录伙伴算法的组序号
176
    seg = &g_vmPhysSeg[page->segID];//先找到所属段
177

178 179
    list = &seg->freeList[order];//找到对应List
    LOS_ListTailInsert(&list->node, &page->node);//将page节点挂入链表
180
    list->listCnt++;//链表节点总数++
181
}
182
//同上
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
STATIC VOID OsVmPhysFreeListAddUnsafe(LosVmPage *page, UINT8 order)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

    if (page->segID >= VM_PHYS_SEG_MAX) {
        LOS_Panic("The page segment id(%d) is invalid\n", page->segID);
    }

    page->order = order;
    seg = &g_vmPhysSeg[page->segID];

    list = &seg->freeList[order];
    LOS_ListTailInsert(&list->node, &page->node);
    list->listCnt++;
}
199
//将物理页框从空闲链表上摘除,见于物理页框被分配的情况
200 201 202 203 204
STATIC VOID OsVmPhysFreeListDelUnsafe(LosVmPage *page)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

205
    if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) {//等于VM_LIST_ORDER_MAX也不行,说明伙伴算法最大支持 2^8的分配
206 207 208
        LOS_Panic("The page segment id(%u) or order(%u) is invalid\n", page->segID, page->order);
    }

209 210 211 212
    seg = &g_vmPhysSeg[page->segID];	//找到物理页框对应的段
    list = &seg->freeList[page->order];	//根据伙伴算法组序号找到空闲链表
    list->listCnt--;					//链表节点总数减一
    LOS_ListDelete(&page->node);		//将自己从链表上摘除
213
    page->order = VM_LIST_ORDER_MAX;	//告诉系统物理页框已不在空闲链表上, 用于OsVmPhysPagesSpiltUnsafe的断言
214
}
215
//同上
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
STATIC VOID OsVmPhysFreeListDel(LosVmPage *page)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

    if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) {
        LOS_Panic("The page segment id(%u) or order(%u) is invalid\n", page->segID, page->order);
    }

    seg = &g_vmPhysSeg[page->segID];
    list = &seg->freeList[page->order];
    list->listCnt--;
    LOS_ListDelete(&page->node);
    page->order = VM_LIST_ORDER_MAX;
}

232 233 234 235 236
/******************************************************************************
 本函数很像卖猪肉的,拿一大块肉剁,先把多余的放回到小块肉堆里去.
 oldOrder:原本要买 2^2肉
 newOrder:却找到个 2^8肉块
******************************************************************************/
237 238 239 240 241
STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder)
{
    UINT32 order;
    LosVmPage *buddyPage = NULL;

242
    for (order = newOrder; order > oldOrder;) {//把肉剁碎的过程,把多余的肉块切成2^7,2^6...标准块,
243 244 245 246
        order--;//越切越小,逐一挂到对应的空闲链表上
        buddyPage = &page[VM_ORDER_TO_PAGES(order)];//@note_good 先把多余的肉割出来,这句代码很赞!因为LosVmPage本身是在一个大数组上,page[nPages]可直接定位
        LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX);//没挂到伙伴算法对应组块空闲链表上的物理页框的order必须是VM_LIST_ORDER_MAX
        OsVmPhysFreeListAddUnsafe(buddyPage, order);//将劈开的节点挂到对应序号的链表上,buddyPage->order = order
247 248
    }
}
249
//通过物理地址获取所属参数段的物理页框
250 251 252 253 254 255 256 257 258 259 260 261 262
LosVmPage *OsVmPhysToPage(paddr_t pa, UINT8 segID)
{
    struct VmPhysSeg *seg = NULL;
    paddr_t offset;

    if (segID >= VM_PHYS_SEG_MAX) {
        LOS_Panic("The page segment id(%d) is invalid\n", segID);
    }
    seg = &g_vmPhysSeg[segID];
    if ((pa < seg->start) || (pa >= (seg->start + seg->size))) {
        return NULL;
    }

263
    offset = pa - seg->start;//得到物理地址的偏移量
264
    return (seg->pageBase + (offset >> PAGE_SHIFT));//得到对应的物理页框
265
}
266 267 268 269 270 271 272

/******************************************************************************
 通过page获取内核空间的虚拟地址 参考OsArchMmuInit
 #define SYS_MEM_BASE            DDR_MEM_ADDR /* physical memory base 物理地址的起始地址 * /
 本函数非常重要,通过一个物理地址找到内核虚拟地址
 内核静态映射:提升虚实转化效率,段映射减少页表项
******************************************************************************/
273 274 275
VOID *OsVmPageToVaddr(LosVmPage *page)//
{
    VADDR_T vaddr;
276
    vaddr = KERNEL_ASPACE_BASE + page->physAddr - SYS_MEM_BASE;//page->physAddr - SYS_MEM_BASE 得到物理地址的偏移量
277
	//因在整个虚拟内存中内核空间和用户空间是通过地址隔离的,如此很巧妙的就把该物理页映射到了内核空间
278
	//内核空间的vmPage是不会被置换的,因为是常驻内存,内核空间初始化mmu时就映射好了L1表
279 280
    return (VOID *)(UINTPTR)vaddr;
}
281
//通过虚拟地址找映射的物理页框
282 283 284
LosVmPage *OsVmVaddrToPage(VOID *ptr)
{
    struct VmPhysSeg *seg = NULL;
285
    PADDR_T pa = LOS_PaddrQuery(ptr);//通过虚拟地址查询物理地址
286 287 288 289 290
    UINT32 segID;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
        seg = &g_vmPhysSeg[segID];
        if ((pa >= seg->start) && (pa < (seg->start + seg->size))) {
291
            return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT);//段基地址+页偏移索引 得到虚拟地址经映射所在物理页框
292 293 294 295 296
        }
    }

    return NULL;
}
297
//从参数段中分配参数页数
298 299 300 301 302 303 304 305 306 307
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;
    }
308
	//因为伙伴算法分配单元是 1,2,4,8 页,比如nPages = 3时,就需要从 4号空闲链表中分,剩余的1页需要劈开放到1号空闲链表中
309
    order = OsVmPagesToOrder(nPages);//根据页数计算出用哪个块组
310
    if (order < VM_LIST_ORDER_MAX) {//order不能大于9 即:256*4K = 1M 可理解为向内核堆申请内存一次不能超过1M
311 312
        for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {//没有就找更大块
            list = &seg->freeList[newOrder];//从最合适的块处开始找
313
            if (LOS_ListEmpty(&list->node)) {//理想情况链表为空,说明没找到
314 315
                continue;//继续找更大块的
            }
316
            page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);//找第一个节点就行,因为链表上挂的都是同样大小物理页框
317 318 319 320 321
            goto DONE;
        }
    }
    return NULL;
DONE:
322 323
    OsVmPhysFreeListDelUnsafe(page);//将物理页框从链表上摘出来
    OsVmPhysPagesSpiltUnsafe(page, order, newOrder);//将物理页框劈开,把用不了的页再挂到对应的空闲链表上
324 325
    return page;
}
326
//释放物理页框,所谓释放物理页就是把页挂到空闲链表中
327 328 329 330 331 332 333 334 335 336
VOID OsVmPhysPagesFree(LosVmPage *page, UINT8 order)
{
    paddr_t pa;
    LosVmPage *buddyPage = NULL;

    if ((page == NULL) || (order >= VM_LIST_ORDER_MAX)) {
        return;
    }

    if (order < VM_LIST_ORDER_MAX - 1) {//order[0,7]
337
        pa = VM_PAGE_TO_PHYS(page);//获取物理地址
338 339
        do {//按位异或
            pa ^= VM_ORDER_TO_PHYS(order);//@note_good 注意这里是高位和低位的 ^= ,也就是说跳到order块组物理地址处,此处处理甚妙!
340 341
            buddyPage = OsVmPhysToPage(pa, page->segID);//通过物理地址拿到页框
            if ((buddyPage == NULL) || (buddyPage->order != order)) {//页框所在组块必须要对应
342 343
                break;
            }
344
            OsVmPhysFreeListDel(buddyPage);//注意buddypage是连续的物理页框 例如order=2时,2^2=4页就是一个块组 |_|_|_|_| 
345 346 347 348 349 350 351 352
            order++;
            pa &= ~(VM_ORDER_TO_PHYS(order) - 1);
            page = OsVmPhysToPage(pa, page->segID);
        } while (order < VM_LIST_ORDER_MAX - 1);
    }

    OsVmPhysFreeListAdd(page, order);//伙伴算法 空闲节点增加
}
353
//连续的释放物理页框, 如果8页连在一块是一起释放的
354 355 356 357 358 359 360
VOID OsVmPhysPagesFreeContiguous(LosVmPage *page, size_t nPages)
{
    paddr_t pa;
    UINT32 order;
    size_t count;
    size_t n;

361
    while (TRUE) {//死循环
362 363
        pa = VM_PAGE_TO_PHYS(page);//获取页面物理地址
        order = VM_PHYS_TO_ORDER(pa);//通过物理地址找到伙伴算法的级别
364
        n = VM_ORDER_TO_PAGES(order);//通过级别找到物理页块 (1<<order),意思是如果order=3,就可以释放8个页块
365
        if (n > nPages) {//nPages只剩下小于2^order时,退出循环
366 367 368 369 370 371
            break;
        }
        OsVmPhysPagesFree(page, order);//释放伙伴算法对应块组
        nPages -= n;//总页数减少
        page += n;//释放的页数增多
    }
372
	//举例剩下 7个页框时,依次用 2^2 2^1 2^0 方式释放
373 374
    for (count = 0; count < nPages; count += n) {
        order = LOS_HighBitGet(nPages);//从高到低块组释放
375
        n = VM_ORDER_TO_PAGES(order);//2^order次方
376
        OsVmPhysPagesFree(page, order);//释放块组
377
        page += n;//相当于page[n]
378 379
    }
}
380 381 382 383 384

/******************************************************************************
 获取一定数量的页框 LosVmPage实体是放在全局大数组中的,
 LosVmPage->nPages 标记了分配页数
******************************************************************************/
385 386 387 388 389 390 391 392 393 394 395 396 397 398
STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;
    UINT32 segID;

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

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
        seg = &g_vmPhysSeg[segID];
        LOS_SpinLockSave(&seg->freeListLock, &intSave);
399 400
        page = OsVmPhysPagesAlloc(seg, nPages);//分配指定页数的物理页,nPages需小于伙伴算法一次能分配的最大页数
        if (page != NULL) {//分配成功
401 402
            /*  */
            LOS_AtomicSet(&page->refCounts, 0);//设置引用次数为0
403
            page->nPages = nPages;//页数
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            return page;
        }
        LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    }
    return NULL;
}
//分配连续的物理页
VOID *LOS_PhysPagesAllocContiguous(size_t nPages)
{
    LosVmPage *page = NULL;

    if (nPages == 0) {
        return NULL;
    }
419
	//鸿蒙 nPages 不能大于 2^8 次方,即256个页,1M内存,仅限于内核态,用户态不限制分配大小.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
    page = OsVmPhysPagesGet(nPages);//通过伙伴算法获取物理上连续的页
    if (page == NULL) {
        return NULL;
    }

    return OsVmPageToVaddr(page);//通过物理页找虚拟地址
}
//释放连续的物理页
VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;

    if (ptr == NULL) {
        return;
    }

438
    page = OsVmVaddrToPage(ptr);//通过虚拟地址找到页框
439 440 441 442
    if (page == NULL) {
        VM_ERR("vm page of ptr(%#x) is null", ptr);
        return;
    }
443
    page->nPages = 0;//被分配的页数置为0,表示不被分配
444 445 446 447

    seg = &g_vmPhysSeg[page->segID];
    LOS_SpinLockSave(&seg->freeListLock, &intSave);

448
    OsVmPhysPagesFreeContiguous(page, nPages);//具体释放实现
449 450 451

    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}
452
/******************************************************************************
453
 通过物理地址获取内核虚拟地址,内核静态映射,减少虚实地址的转换
454
******************************************************************************/
455 456 457 458 459 460 461 462 463 464 465
VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr)
{
    struct VmPhysSeg *seg = NULL;
    UINT32 segID;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
        seg = &g_vmPhysSeg[segID];
        if ((paddr >= seg->start) && (paddr < (seg->start + seg->size))) {
            return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);
        }
    }
466 467
	//内核
    return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);//
468
}
469
//释放物理页框
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
VOID LOS_PhysPageFree(LosVmPage *page)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;

    if (page == NULL) {
        return;
    }

    if (LOS_AtomicDecRet(&page->refCounts) <= 0) {
        seg = &g_vmPhysSeg[page->segID];
        LOS_SpinLockSave(&seg->freeListLock, &intSave);

        OsVmPhysPagesFreeContiguous(page, ONE_PAGE);
        LOS_AtomicSet(&page->refCounts, 0);

        LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    }
}
489
//供外部调用
490 491 492 493
LosVmPage *LOS_PhysPageAlloc(VOID)
{
    return OsVmPhysPagesGet(ONE_PAGE);//分配一页物理页
}
494 495 496 497
/******************************************************************************
 分配nPages页个物理页框,并将页框挂入list
 返回已分配的页面大小,不负责一定能分配到nPages的页框
******************************************************************************/
498 499 500 501 502 503 504 505 506 507 508 509 510 511
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;
        }
512
        LOS_ListTailInsert(list, &page->node);//从参数链表list尾部挂入新页面结点
513 514 515 516 517
        count++;
    }

    return count;
}
518
//拷贝共享页面
519 520 521 522 523 524 525 526 527 528 529 530 531
VOID OsPhysSharePageCopy(PADDR_T oldPaddr, PADDR_T *newPaddr, LosVmPage *newPage)
{
    UINT32 intSave;
    LosVmPage *oldPage = NULL;
    VOID *newMem = NULL;
    VOID *oldMem = NULL;
    LosVmPhysSeg *seg = NULL;

    if ((newPage == NULL) || (newPaddr == NULL)) {
        VM_ERR("new Page invalid");
        return;
    }

532
    oldPage = LOS_VmPageGet(oldPaddr);//由物理地址得到页框
533 534 535 536 537
    if (oldPage == NULL) {
        VM_ERR("invalid paddr %p", oldPaddr);
        return;
    }

538
    seg = &g_vmPhysSeg[oldPage->segID];//拿到物理段
539
    LOS_SpinLockSave(&seg->freeListLock, &intSave);
540 541 542
    if (LOS_AtomicRead(&oldPage->refCounts) == 1) {//页面引用次数仅一次,说明只有一个进程在操作
        *newPaddr = oldPaddr;//新老指向同一块物理地址
    } else {//是个共享内存
543 544
        newMem = LOS_PaddrToKVaddr(*newPaddr);	//新页虚拟地址
        oldMem = LOS_PaddrToKVaddr(oldPaddr);	//老页虚拟地址
545 546 547
        if ((newMem == NULL) || (oldMem == NULL)) {
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            return;
548 549
        }//请记住,在保护模式下,物理地址只能用于计算,操作(包括拷贝)需要虚拟地址! 
        if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) {//老页内容复制给新页,需操作虚拟地址,拷贝一页数据
550 551 552
            VM_ERR("memcpy_s failed");
        }

553 554
        LOS_AtomicInc(&newPage->refCounts);//新页引用次数以原子方式自动减量 
        LOS_AtomicDec(&oldPage->refCounts);//老页引用次数以原子方式自动减量
555 556 557 558
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    return;
}
559
//获取物理页框所在段
560 561 562 563 564 565
struct VmPhysSeg *OsVmPhysSegGet(LosVmPage *page)
{
    if ((page == NULL) || (page->segID >= VM_PHYS_SEG_MAX)) {
        return NULL;
    }

566
    return (OsGVmPhysSegGet() + page->segID);//等用于OsGVmPhysSegGet()[page->segID]
567
}
568
//获取参数nPages对应的块组,例如 7 -> 2^3 返回 3
569 570 571 572 573 574 575 576
UINT32 OsVmPagesToOrder(size_t nPages)
{
    UINT32 order;

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

    return order;
}
577
//释放双链表中的所有节点内存,本质是回归到伙伴orderlist中
578 579 580 581 582 583 584 585 586 587 588 589
size_t LOS_PhysPagesFree(LOS_DL_LIST *list)
{
    UINT32 intSave;
    LosVmPage *page = NULL;
    LosVmPage *nPage = NULL;
    LosVmPhysSeg *seg = NULL;
    size_t count = 0;

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

590 591 592 593 594 595 596 597
    LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(page, nPage, list, LosVmPage, node) {//宏循环
        LOS_ListDelete(&page->node);//先把自己摘出去
        if (LOS_AtomicDecRet(&page->refCounts) <= 0) {//无引用
            seg = &g_vmPhysSeg[page->segID];//获取物理段
            LOS_SpinLockSave(&seg->freeListLock, &intSave);//锁住freeList
            OsVmPhysPagesFreeContiguous(page, ONE_PAGE);//连续释放,注意这里的ONE_PAGE其实有误导,让人以为是释放4K,其实是指连续的物理页框,如果3页连在一块是一起释放的.
            LOS_AtomicSet(&page->refCounts, 0);//引用重置为0
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);//恢复锁
598
        }
599
        count++;//继续取下一个node
600 601 602 603 604 605 606 607 608 609
    }

    return count;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif /* __cplusplus */
#endif /* __cplusplus */