los_vm_phys.c 25.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*!
 * @file    los_vm_phys.c
 * @brief 物理内存管理 - 段页式管理
 * @link physical http://weharmonyos.com/openharmony/zh-cn/device-dev/kernel/kernel-small-basic-memory-physical.html @endlink
   @verbatim
基本概念
   物理内存是计算机上最重要的资源之一,指的是实际的内存设备提供的、可以通过CPU总线直接进行寻址的内存空间,
   其主要作用是为操作系统及程序提供临时存储空间。LiteOS-A内核管理物理内存是通过分页实现的,除了内核堆占用的一部分内存外,
   其余可用内存均以4KiB为单位划分成页帧,内存分配和内存回收便是以页帧为单位进行操作。内核采用伙伴算法管理空闲页面,
   可以降低一定的内存碎片率,提高内存分配和释放的效率,但是一个很小的块往往也会阻塞一个大块的合并,导致不能分配较大的内存块。
运行机制
   LiteOS-A内核的物理内存使用分布视图,主要由内核镜像、内核堆及物理页组成。内核堆部分见堆内存管理一节。
   -----------------------------------------------------

	kernel.bin 		| heap 		| page frames
	(内核镜像)			| (内核堆)	 	| (物理页框)
   -----------------------------------------------------
   伙伴算法把所有空闲页帧分成9个内存块组,每组中内存块包含2的幂次方个页帧,例如:第0组的内存块包含2的0次方个页帧,
   即1个页帧;第8组的内存块包含2的8次方个页帧,即256个页帧。相同大小的内存块挂在同一个链表上进行管理。
   
申请内存
22 23 24
	系统申请20KiB内存,按一页帧4K算,即5个页帧时,9个内存块组中索引为2的链表挂着一块大小为8个页帧的内存块满足要求,分配出20KiB内存后还剩余12KiB内存,
	即3个页帧,将3个页帧分成2的幂次方之和,即0跟1,尝试查找伙伴进行合并。2个页帧的内存块没有伙伴则直接插到索引为1的链表上,
	继续查找1个页帧的内存块是否有伙伴,索引为0的链表上此时有1个,如果两个内存块地址连续则进行合并,并将内存块挂到索引为0的链表上,否则不做处理。
25 26 27 28 29 30 31 32 33 34 35
释放内存
    系统释放12KiB内存,即3个页帧,将3个页帧分成2的幂次方之和,即2跟1,尝试查找伙伴进行合并,索引为1的链表上有1个内存块,
    若地址连续则合并,并将合并后的内存块挂到索引为2的链表上,索引为0的链表上此时也有1个,如果地址连续则进行合并,
    并将合并后的内存块挂到索引为1的链表上,此时继续判断是否有伙伴,重复上述操作。	
   @endverbatim
 * @image html https://gitee.com/weharmonyos/resources/raw/master/17/malloc_phy.png
 * @image html https://gitee.com/weharmonyos/resources/raw/master/17/free_phy.png
 * @version 
 * @author  weharmonyos.com | 鸿蒙研究站 | 每天死磕一点点
 * @date    2021-11-25
 */
36
/*
37 38
 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
 *
 * 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"

74 75

#ifdef LOSCFG_KERNEL_VM
76

77 78
#define ONE_PAGE    1

79 80
/* Physical memory area array | 物理内存区数组 */
STATIC struct VmPhysArea g_physArea[] = {///< 这里只有一个区域,即只生成一个段
81
    {
82 83
        .start = SYS_MEM_BASE, //整个物理内存基地址,#define SYS_MEM_BASE            DDR_MEM_ADDR ,  0x80000000
        .size = SYS_MEM_SIZE_DEFAULT,//整个物理内存总大小 0x07f00000
84 85 86
    },
};

87 88 89
struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX]; ///< 最大32段
INT32 g_vmPhysSegNum = 0;	///< 段数
/// 获取段数组,全局变量,变量放在 .bbs 区
90 91 92 93
LosVmPhysSeg *OsGVmPhysSegGet()
{
    return g_vmPhysSeg;
}
94
/// 初始化Lru置换链表
95 96 97 98
STATIC VOID OsVmPhysLruInit(struct VmPhysSeg *seg)
{
    INT32 i;
    UINT32 intSave;
99
    LOS_SpinInit(&seg->lruLock);//初始化自旋锁,自旋锁用于CPU多核同步
100 101

    LOS_SpinLockSave(&seg->lruLock, &intSave);
102
    for (i = 0; i < VM_NR_LRU_LISTS; i++) { //五个双循环链表
103 104
        seg->lruSize[i] = 0;			//记录链表节点数
        LOS_ListInit(&seg->lruList[i]);	//初始化LRU链表
105 106 107
    }
    LOS_SpinUnlockRestore(&seg->lruLock, intSave);
}
108
/// 创建物理段,由区划分转成段管理
109 110 111 112 113 114 115 116
STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size)
{
    struct VmPhysSeg *seg = NULL;

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

117
    seg = &g_vmPhysSeg[g_vmPhysSegNum++];//拿到一段数据
118
    for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) {//定位到合适的段
119 120 121 122 123 124 125
        *seg = *(seg - 1);
    }
    seg->start = start;
    seg->size = size;

    return 0;
}
126
/// 添加物理段
127 128 129 130
VOID OsVmPhysSegAdd(VOID)
{
    INT32 i, ret;

131
    LOS_ASSERT(g_vmPhysSegNum < VM_PHYS_SEG_MAX);
132
	
133 134
    for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {//遍历g_physArea数组
        ret = OsVmPhysSegCreate(g_physArea[i].start, g_physArea[i].size);//由区划分转成段管理
135 136 137 138 139
        if (ret != 0) {
            VM_ERR("create phys seg failed");
        }
    }
}
140
/// 段区域大小调整
141 142
VOID OsVmPhysAreaSizeAdjust(size_t size)
{
143 144 145 146 147 148
    /*
     * The first physics memory segment is used for kernel image and kernel heap,
     * so just need to adjust the first one here.
     */
    g_physArea[0].start += size;
    g_physArea[0].size -= size;
149
}
150

151
/// 获得物理内存的总页数
152 153 154 155 156 157
UINT32 OsVmPhysPageNumGet(VOID)
{
    UINT32 nPages = 0;
    INT32 i;

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

161
    return nPages;//返回所有物理内存总页数
162
}
163
/// 初始化空闲链表,分配物理页框使用伙伴算法
164 165 166 167 168 169
STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg)
{
    int i;
    UINT32 intSave;
    struct VmFreeList *list = NULL;

170
    LOS_SpinInit(&seg->freeListLock);//初始化用于分配的自旋锁
171 172

    LOS_SpinLockSave(&seg->freeListLock, &intSave);
173 174 175
    for (i = 0; i < VM_LIST_ORDER_MAX; i++) {//遍历伙伴算法空闲块组链表
        list = &seg->freeList[i];	//一个个来
        LOS_ListInit(&list->node);	//LosVmPage.node将挂到list->node上
176
        list->listCnt = 0;			//链表上的数量默认0
177 178 179
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}
180
/// 物理段初始化
181 182 183 184 185 186 187 188
VOID OsVmPhysInit(VOID)
{
    struct VmPhysSeg *seg = NULL;
    UINT32 nPages = 0;
    int i;

    for (i = 0; i < g_vmPhysSegNum; i++) {
        seg = &g_vmPhysSeg[i];
189 190
        seg->pageBase = &g_vmPageArray[nPages];//记录本段首页物理页框地址
        nPages += seg->size >> PAGE_SHIFT;//偏移12位,按4K一页,算出本段总页数
191 192
        OsVmPhysFreeListInit(seg);	//初始化空闲链表,分配页框使用伙伴算法
        OsVmPhysLruInit(seg);		//初始化LRU置换链表
193 194
    }
}
195
/// 将页框挂入空闲链表,分配物理页框从空闲链表里拿
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
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++;
}
212
///将物理页框从空闲链表上摘除,见于物理页框被分配的情况
213 214 215 216 217
STATIC VOID OsVmPhysFreeListDelUnsafe(LosVmPage *page)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

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

222 223 224 225
    seg = &g_vmPhysSeg[page->segID];	//找到物理页框对应的段
    list = &seg->freeList[page->order];	//根据伙伴算法组序号找到空闲链表
    list->listCnt--;					//链表节点总数减一
    LOS_ListDelete(&page->node);		//将自己从链表上摘除
226
    page->order = VM_LIST_ORDER_MAX;	//告诉系统物理页框已不在空闲链表上, 用于OsVmPhysPagesSpiltUnsafe的断言
227 228 229
}


230 231 232
/**
 * @brief  本函数很像卖猪肉的,拿一大块肉剁,先把多余的放回到小块肉堆里去.
 * @param page 
233 234
 * @param oldOrder 原本要买 2^2肉
 * @param newOrder 却找到个 2^8肉块
235 236
 * @return STATIC 
 */
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 273 274 275 276 277 278 279
LosVmPage *OsVmPaddrToPage(paddr_t paddr)
{
    INT32 segID;
    LosVmPage *vmPage = NULL;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
        vmPage = OsVmPhysToPage(paddr, segID);
        if (vmPage != NULL) {
            return vmPage;
        }
    }
    return NULL;
}
280
/*!
281 282 283 284 285 286 287
 * @brief 通过page获取内核空间的虚拟地址 参考OsArchMmuInit
 \n #define SYS_MEM_BASE            DDR_MEM_ADDR /* physical memory base 物理地址的起始地址 * /
 \n 本函数非常重要,通过一个物理地址找到内核虚拟地址
 \n 内核静态映射:提升虚实转化效率,段映射减少页表项
 * @param page 
 * @return VOID* 
 */
288 289 290
VOID *OsVmPageToVaddr(LosVmPage *page)//
{
    VADDR_T vaddr;
291 292
    vaddr = KERNEL_ASPACE_BASE + page->physAddr - SYS_MEM_BASE;//表示申请的物理地址在物理空间的偏移量等于映射的虚拟地址在内核空间的偏移量
    return (VOID *)(UINTPTR)vaddr;//不需要存储映射关系,这简直就是神来之笔,拍案叫绝。@note_good 详见 鸿蒙内核源码分析(页表管理篇)
293
}
294
///通过虚拟地址找映射的物理页框
295 296 297
LosVmPage *OsVmVaddrToPage(VOID *ptr)
{
    struct VmPhysSeg *seg = NULL;
298
    PADDR_T pa = LOS_PaddrQuery(ptr);//通过空间的虚拟地址查询物理地址
299 300
    UINT32 segID;

301
    for (segID = 0; segID < g_vmPhysSegNum; segID++) {//遍历所有段
302
        seg = &g_vmPhysSeg[segID];
303
        if ((pa >= seg->start) && (pa < (seg->start + seg->size))) {//找到物理地址所在的段
304
            return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT);//段基地址+页偏移索引 得到虚拟地址经映射所在物理页框
305 306 307 308 309
        }
    }

    return NULL;
}
310
/// 回收一定范围内的页框
311 312 313 314 315 316 317 318
STATIC INLINE VOID OsVmRecycleExtraPages(LosVmPage *page, size_t startPage, size_t endPage)
{
    if (startPage >= endPage) {
        return;
    }

    OsVmPhysPagesFreeContiguous(page, endPage - startPage);
}
319
/// 大块的物理内存分配
320 321 322 323 324 325 326 327 328
STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages)
{
    struct VmFreeList *list = NULL;
    LosVmPage *page = NULL;
    LosVmPage *tmp = NULL;
    PADDR_T paStart;
    PADDR_T paEnd;
    size_t size = nPages << PAGE_SHIFT;

329 330
    list = &seg->freeList[VM_LIST_ORDER_MAX - 1];//先找伙伴算法中内存块最大的开撸
    LOS_DL_LIST_FOR_EACH_ENTRY(page, &list->node, LosVmPage, node) {//遍历链表
331 332
        paStart = page->physAddr;
        paEnd = paStart + size;
333
        if (paEnd > (seg->start + seg->size)) {//匹配物理地址范围
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
            continue;
        }

        for (;;) {
            paStart += PAGE_SIZE << (VM_LIST_ORDER_MAX - 1);
            if ((paStart >= paEnd) || (paStart < seg->start) ||
                (paStart >= (seg->start + seg->size))) {
                break;
            }
            tmp = &seg->pageBase[(paStart - seg->start) >> PAGE_SHIFT];
            if (tmp->order != (VM_LIST_ORDER_MAX - 1)) {
                break;
            }
        }
        if (paStart >= paEnd) {
            return page;
        }
    }

    return NULL;
}
355
/// 申请物理页并挂在对应的链表上
356
STATIC LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages)
357 358 359
{
    struct VmFreeList *list = NULL;
    LosVmPage *page = NULL;
360
    LosVmPage *tmp = NULL;
361 362 363
    UINT32 order;
    UINT32 newOrder;

364
    order = OsVmPagesToOrder(nPages);
365 366
    if (order < VM_LIST_ORDER_MAX) {//按正常的伙伴算法分配
        for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {//从小往大了撸
367
            list = &seg->freeList[newOrder];
368 369
            if (LOS_ListEmpty(&list->node)) {//这条链路上没有可分配的物理页框
                continue;//继续往大的找
370
            }
371
            page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);//找到了直接返回第一个节点
372 373 374 375 376 377
            goto DONE;
        }
    } else {
        newOrder = VM_LIST_ORDER_MAX - 1;
        page = OsVmPhysLargeAlloc(seg, nPages);
        if (page != NULL) {
378 379 380 381 382
            goto DONE;
        }
    }
    return NULL;
DONE:
383 384 385 386 387 388 389

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

390 391
    return page;
}
392
/// 释放物理页框,所谓释放物理页就是把页挂到空闲链表中
393 394 395 396 397 398 399 400 401 402
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]
403
        pa = VM_PAGE_TO_PHYS(page);//获取物理地址
404 405
        do {//按位异或
            pa ^= VM_ORDER_TO_PHYS(order);//@note_good 注意这里是高位和低位的 ^= ,也就是说跳到order块组物理地址处,此处处理甚妙!
406 407
            buddyPage = OsVmPhysToPage(pa, page->segID);//通过物理地址拿到页框
            if ((buddyPage == NULL) || (buddyPage->order != order)) {//页框所在组块必须要对应
408 409
                break;
            }
410
            OsVmPhysFreeListDelUnsafe(buddyPage);//注意buddypage是连续的物理页框 例如order=2时,2^2=4页就是一个块组 |_|_|_|_| 
411 412 413 414 415 416
            order++;
            pa &= ~(VM_ORDER_TO_PHYS(order) - 1);
            page = OsVmPhysToPage(pa, page->segID);
        } while (order < VM_LIST_ORDER_MAX - 1);
    }

417
    OsVmPhysFreeListAddUnsafe(page, order);//伙伴算法 空闲节点增加
418
}
419
///连续的释放物理页框, 如果8页连在一块是一起释放的
420 421 422 423 424 425
VOID OsVmPhysPagesFreeContiguous(LosVmPage *page, size_t nPages)
{
    paddr_t pa;
    UINT32 order;
    size_t n;

426
    while (TRUE) {//死循环
427 428
        pa = VM_PAGE_TO_PHYS(page);//获取页面物理地址
        order = VM_PHYS_TO_ORDER(pa);//通过物理地址找到伙伴算法的级别
429
        n = VM_ORDER_TO_PAGES(order);//通过级别找到物理页块 (1<<order),意思是如果order=3,就可以释放8个页块
430
        if (n > nPages) {//nPages只剩下小于2^order时,退出循环
431 432 433 434 435 436
            break;
        }
        OsVmPhysPagesFree(page, order);//释放伙伴算法对应块组
        nPages -= n;//总页数减少
        page += n;//释放的页数增多
    }
437
	//举例剩下 7个页框时,依次用 2^2 2^1 2^0 方式释放
438
    while (nPages > 0) {
439
        order = LOS_HighBitGet(nPages);//从高到低块组释放
440
        n = VM_ORDER_TO_PAGES(order);//2^order次方
441
        OsVmPhysPagesFree(page, order);//释放块组
442
        nPages -= n;
443
        page += n;//相当于page[n]
444 445
    }
}
446

447 448 449 450 451 452 453
/*!
 * @brief OsVmPhysPagesGet 获取一定数量的页框 LosVmPage实体是放在全局大数组中的,
 *           LosVmPage->nPages 标记了分配页数	
 * @param nPages	
 * @return	
 *
 * @see
454
 */
455 456 457 458 459 460 461 462 463 464
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);
465 466
        page = OsVmPhysPagesAlloc(seg, nPages);//分配指定页数的物理页,nPages需小于伙伴算法一次能分配的最大页数
        if (page != NULL) {//分配成功
467 468
            /*  */
            LOS_AtomicSet(&page->refCounts, 0);//设置引用次数为0
469
            page->nPages = nPages;//页数
470 471 472 473 474 475 476
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            return page;
        }
        LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    }
    return NULL;
}
477
///分配连续的物理页
478 479 480 481 482 483 484
VOID *LOS_PhysPagesAllocContiguous(size_t nPages)
{
    LosVmPage *page = NULL;

    if (nPages == 0) {
        return NULL;
    }
485
	//鸿蒙 nPages 不能大于 2^8 次方,即256个页,1M内存,仅限于内核态,用户态不限制分配大小.
486 487 488 489 490 491 492
    page = OsVmPhysPagesGet(nPages);//通过伙伴算法获取物理上连续的页
    if (page == NULL) {
        return NULL;
    }

    return OsVmPageToVaddr(page);//通过物理页找虚拟地址
}
493
/// 释放指定页数地址连续的物理内存
494 495 496 497 498 499 500 501 502 503
VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;

    if (ptr == NULL) {
        return;
    }

504
    page = OsVmVaddrToPage(ptr);//通过虚拟地址找到页框
505 506 507 508
    if (page == NULL) {
        VM_ERR("vm page of ptr(%#x) is null", ptr);
        return;
    }
509
    page->nPages = 0;//被分配的页数置为0,表示不被分配
510 511 512 513

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

514
    OsVmPhysPagesFreeContiguous(page, nPages);//具体释放实现
515 516 517

    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}
518

519 520 521 522 523 524 525
PADDR_T OsKVaddrToPaddr(VADDR_T kvaddr)
{
    if (kvaddr == 0) {
        return 0;
    }
    return (kvaddr - KERNEL_ASPACE_BASE + SYS_MEM_BASE);
}
526
/// 通过物理地址获取内核虚拟地址
527 528 529 530 531
VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr)
{
    struct VmPhysSeg *seg = NULL;
    UINT32 segID;

532 533 534 535
    if (paddr == 0) {
        return NULL;
    }

536 537 538 539 540 541
    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);
        }
    }
542 543
	//内核
    return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);//
544
}
545
///释放一个物理页框
546 547 548 549 550 551 552 553 554
VOID LOS_PhysPageFree(LosVmPage *page)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;

    if (page == NULL) {
        return;
    }

555
    if (LOS_AtomicDecRet(&page->refCounts) <= 0) {//减少引用数后不能小于0
556 557 558
        seg = &g_vmPhysSeg[page->segID];
        LOS_SpinLockSave(&seg->freeListLock, &intSave);

559 560
        OsVmPhysPagesFreeContiguous(page, ONE_PAGE);//释放一页
        LOS_AtomicSet(&page->refCounts, 0);//只要物理内存被释放了,引用数就必须得重置为 0
561 562 563 564

        LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    }
}
565
/// 申请一个物理页
566 567 568 569
LosVmPage *LOS_PhysPageAlloc(VOID)
{
    return OsVmPhysPagesGet(ONE_PAGE);//分配一页物理页
}
570 571 572 573 574 575 576 577 578 579 580

/*!
 * @brief LOS_PhysPagesAlloc 分配nPages页个物理页框,并将页框挂入list
 	\n 返回已分配的页面大小,不负责一定能分配到nPages的页框	
 *
 * @param list	
 * @param nPages	
 * @return	
 *
 * @see
 */
581 582 583 584 585 586 587 588 589 590 591 592 593 594
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;
        }
595
        LOS_ListTailInsert(list, &page->node);//从参数链表list尾部挂入新页面结点
596 597 598 599 600
        count++;
    }

    return count;
}
601
///拷贝共享页面
602 603 604 605 606 607 608 609 610 611 612 613 614
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;
    }

615
    oldPage = LOS_VmPageGet(oldPaddr);//由物理地址得到页框
616
    if (oldPage == NULL) {
617
        VM_ERR("invalid oldPaddr %p", oldPaddr);
618 619 620
        return;
    }

621
    seg = &g_vmPhysSeg[oldPage->segID];//拿到物理段
622
    LOS_SpinLockSave(&seg->freeListLock, &intSave);
623 624 625
    if (LOS_AtomicRead(&oldPage->refCounts) == 1) {//页面引用次数仅一次,说明只有一个进程在操作
        *newPaddr = oldPaddr;//新老指向同一块物理地址
    } else {//是个共享内存
626 627
        newMem = LOS_PaddrToKVaddr(*newPaddr);	//新页虚拟地址
        oldMem = LOS_PaddrToKVaddr(oldPaddr);	//老页虚拟地址
628 629 630
        if ((newMem == NULL) || (oldMem == NULL)) {
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            return;
631 632
        }//请记住,在保护模式下,物理地址只能用于计算,操作(包括拷贝)需要虚拟地址! 
        if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) {//老页内容复制给新页,需操作虚拟地址,拷贝一页数据
633 634 635
            VM_ERR("memcpy_s failed");
        }

636 637
        LOS_AtomicInc(&newPage->refCounts);//新页引用次数以原子方式自动减量 
        LOS_AtomicDec(&oldPage->refCounts);//老页引用次数以原子方式自动减量
638 639 640 641
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    return;
}
642
///获取物理页框所在段
643 644 645 646 647 648
struct VmPhysSeg *OsVmPhysSegGet(LosVmPage *page)
{
    if ((page == NULL) || (page->segID >= VM_PHYS_SEG_MAX)) {
        return NULL;
    }

649
    return (OsGVmPhysSegGet() + page->segID);//等用于OsGVmPhysSegGet()[page->segID]
650
}
651
///获取参数nPages对应的块组,例如 7 -> 2^3 返回 3
652 653 654 655 656 657 658 659
UINT32 OsVmPagesToOrder(size_t nPages)
{
    UINT32 order;

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

    return order;
}
660
///释放双链表中的所有节点内存,本质是回归到伙伴orderlist中
661 662 663 664 665 666 667 668 669 670 671 672
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;
    }

673 674 675 676 677 678 679 680
    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);//恢复锁
681
        }
682
        count++;//继续取下一个node
683 684 685 686
    }

    return count;
}
687 688 689
#else
VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr)
{
690
    if ((paddr < DDR_MEM_ADDR) || (paddr >= ((PADDR_T)DDR_MEM_ADDR + DDR_MEM_SIZE))) {
691 692
        return NULL;
    }
693

694
    return (VADDR_T *)DMA_TO_VMM_ADDR(paddr);
695
}
696 697
#endif