From 162af576ade9b99c5ff7a8456ad16b14067f243b Mon Sep 17 00:00:00 2001 From: dancer <2905764690@qq.com> Date: Fri, 19 Jan 2024 11:34:56 +0800 Subject: [PATCH] =?UTF-8?q?vm=E8=A7=A3=E6=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernel/base/core/los_process.c | 1 - .../kernel/base/vm/los_vm_dump.c | 106 ++-- .../kernel/base/vm/los_vm_fault.c | 150 ++--- .../kernel/base/vm/los_vm_filemap.c | 291 +++++---- .../kernel/base/vm/los_vm_iomap.c | 57 +- .../kernel/base/vm/los_vm_map.c | 568 ++++++++++-------- .../kernel/base/vm/los_vm_page.c | 50 +- .../kernel/base/vm/los_vm_phys.c | 344 ++++++----- .../kernel/base/vm/los_vm_scan.c | 198 +++--- .../kernel/base/vm/los_vm_syscall.c | 167 +++-- src/kernel_liteos_a/kernel/base/vm/oom.c | 83 ++- src/kernel_liteos_a/kernel/base/vm/shm.c | 380 ++++++++---- 12 files changed, 1396 insertions(+), 999 deletions(-) diff --git a/src/kernel_liteos_a/kernel/base/core/los_process.c b/src/kernel_liteos_a/kernel/base/core/los_process.c index 40fed7dd..58dd593f 100644 --- a/src/kernel_liteos_a/kernel/base/core/los_process.c +++ b/src/kernel_liteos_a/kernel/base/core/los_process.c @@ -2315,7 +2315,6 @@ LITE_OS_SEC_TEXT INT32 OsClone(UINT32 flags, UINTPTR sp, UINT32 size) return OsCopyProcess(cloneFlag & flags, NULL, sp, size); } -//著名的 fork 函数 记得前往 https://gitee.com/weharmony/kernel_liteos_a_note fork一下 :) LITE_OS_SEC_TEXT INT32 LOS_Fork(UINT32 flags, const CHAR *name, const TSK_ENTRY_FUNC entry, UINT32 stackSize) { UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | CLONE_VFORK | CLONE_FILES; diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_dump.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_dump.c index f326ea12..1fbc80b9 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_dump.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_dump.c @@ -159,26 +159,26 @@ UINT32 OsKProcessPmUsage(LosVmSpace *kSpace, UINT32 *actualPm) /* Kernel resident memory, include default heap memory */ memUsed = SYS_MEM_SIZE_DEFAULT - (totalCount << PAGE_SHIFT); - spaceList = LOS_GetVmSpaceList(); + spaceList = LOS_GetVmSpaceList();//获取虚拟空间链表,上面挂了所有虚拟空间 LosMux *vmSpaceListMux = OsGVmSpaceMuxGet(); (VOID)LOS_MuxAcquire(vmSpaceListMux); - LOS_DL_LIST_FOR_EACH_ENTRY(space, spaceList, LosVmSpace, node) { - if (space == LOS_GetKVmSpace()) { + LOS_DL_LIST_FOR_EACH_ENTRY(space, spaceList, LosVmSpace, node) {//遍历链表 + if (space == LOS_GetKVmSpace()) {//内核空间不统计 continue; } UProcessUsed += OsUProcessPmUsage(space, NULL, NULL); } (VOID)LOS_MuxRelease(vmSpaceListMux); - /* Kernel dynamic memory, include extended heap memory */ + /* Kernel dynamic memory, include extended heap memory */ //内核动态内存,包括扩展堆内存 memUsed += ((usedCount << PAGE_SHIFT) - UProcessUsed); - /* Remaining heap memory */ + /* Remaining heap memory */ //剩余堆内存 memUsed -= freeMem; *actualPm = memUsed; return memUsed; } - +///shell task 物理内存的使用情况 UINT32 OsShellCmdProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm) { if (space == NULL) { @@ -194,7 +194,7 @@ UINT32 OsShellCmdProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actu } return OsUProcessPmUsage(space, sharePm, actualPm); } - +///虚拟空间物理内存的使用情况,参数同时带走共享物理内存sharePm和actualPm单位是字节 UINT32 OsUProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm) { LosVmMapRegion *region = NULL; @@ -230,10 +230,10 @@ UINT32 OsUProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm) continue; } - shareRef = LOS_AtomicRead(&page->refCounts); + shareRef = LOS_AtomicRead(&page->refCounts);//ref 大于1 说明page被其他空间也引用了 if (shareRef > 1) { if (sharePm != NULL) { - *sharePm += PAGE_SIZE; + *sharePm += PAGE_SIZE;//一页4K字节 } pmSize += PAGE_SIZE / shareRef; } else { @@ -250,7 +250,9 @@ UINT32 OsUProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm) return pmSize; } - +/// @brief 通过虚拟空间获取进程实体 +/// @param space +/// @return LosProcessCB *OsGetPIDByAspace(const LosVmSpace *space) { UINT32 pid; @@ -258,13 +260,13 @@ LosProcessCB *OsGetPIDByAspace(const LosVmSpace *space) LosProcessCB *processCB = NULL; SCHEDULER_LOCK(intSave); - for (pid = 0; pid < g_processMaxNum; ++pid) { + for (pid = 0; pid < g_processMaxNum; ++pid) {//循环进程池,进程池本质是个数组 processCB = g_processCBArray + pid; - if (OsProcessIsUnused(processCB)) { + if (OsProcessIsUnused(processCB)) {//进程还没被分配使用 continue; } - if (processCB->vmSpace == space) { + if (processCB->vmSpace == space) {//找到了 SCHEDULER_UNLOCK(intSave); return processCB; } @@ -272,7 +274,11 @@ LosProcessCB *OsGetPIDByAspace(const LosVmSpace *space) SCHEDULER_UNLOCK(intSave); return NULL; } - +/// @brief 统计虚拟空间中某个线性区的页数 +/// @param space +/// @param region +/// @param pssPages +/// @return UINT32 OsCountRegionPages(LosVmSpace *space, LosVmMapRegion *region, UINT32 *pssPages) { UINT32 regionPages = 0; @@ -306,7 +312,7 @@ UINT32 OsCountRegionPages(LosVmSpace *space, LosVmMapRegion *region, UINT32 *pss return regionPages; } - +///统计虚拟空间的总页数 UINT32 OsCountAspacePages(LosVmSpace *space) { UINT32 spacePages = 0; @@ -390,27 +396,30 @@ VOID OsDumpRegion2(LosVmSpace *space, LosVmMapRegion *region) region->range.size, flagsStr, regionPages, pssPages); (VOID)LOS_MemFree(m_aucSysMem0, flagsStr); } - +///dump 指定虚拟空间的信息 VOID OsDumpAspace(LosVmSpace *space) { LosVmMapRegion *region = NULL; LosRbNode *pstRbNode = NULL; LosRbNode *pstRbNodeNext = NULL; UINT32 spacePages; - LosProcessCB *pcb = OsGetPIDByAspace(space); + LosProcessCB *pcb = OsGetPIDByAspace(space);//通过虚拟空间找到进程实体 if (pcb == NULL) { return; } - - spacePages = OsCountAspacePages(space); + //进程ID | 进程虚拟内存控制块地址信息 | 虚拟内存起始地址 | 虚拟内存大小 | 已使用的物理页数量 + spacePages = OsCountAspacePages(space);//获取空间的页数 PRINTK("\r\n PID aspace name base size pages \n"); PRINTK(" ---- ------ ---- ---- ----- ----\n"); PRINTK(" %-4d %#010x %-10.10s %#010x %#010x %d\n", pcb->processID, space, pcb->processName, space->base, space->size, spacePages); + + //虚拟区间控制块地址信息 | 虚拟区间类型 | 虚拟区间起始地址 | 虚拟区间大小 | 虚拟区间mmu映射属性 | 已使用的物理页数量(包括共享内存部分 | 已使用的物理页数量 + PRINTK("\r\n\t region name base size mmu_flags pages pg/ref\n"); PRINTK("\t ------ ---- ---- ---- --------- ----- -----\n"); - RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext) + RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)//按region 轮询统计 region = (LosVmMapRegion *)pstRbNode; if (region != NULL) { OsDumpRegion2(space, region); @@ -421,14 +430,14 @@ VOID OsDumpAspace(LosVmSpace *space) RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext) return; } - +///查看所有进程使用虚拟内存的情况 VOID OsDumpAllAspace(VOID) { LosVmSpace *space = NULL; - LOS_DL_LIST *aspaceList = LOS_GetVmSpaceList(); - LOS_DL_LIST_FOR_EACH_ENTRY(space, aspaceList, LosVmSpace, node) { + LOS_DL_LIST *aspaceList = LOS_GetVmSpaceList();//获取所有空间链表 + LOS_DL_LIST_FOR_EACH_ENTRY(space, aspaceList, LosVmSpace, node) {//循环取出进程虚拟 (VOID)LOS_MuxAcquire(&space->regionMux); - OsDumpAspace(space); + OsDumpAspace(space);//dump 空间 (VOID)LOS_MuxRelease(&space->regionMux); } return; @@ -447,11 +456,11 @@ STATUS_T OsRegionOverlapCheck(LosVmSpace *space, LosVmMapRegion *region) (VOID)LOS_MuxRelease(&space->regionMux); return ret; } - +///dump 页表项 VOID OsDumpPte(VADDR_T vaddr) { UINT32 l1Index = vaddr >> MMU_DESCRIPTOR_L1_SMALL_SHIFT; - LosVmSpace *space = LOS_SpaceGet(vaddr); + LosVmSpace *space = LOS_SpaceGet(vaddr);//通过虚拟地址获取空间,内核分三个空间 内核进程空间,内核堆空间,用户进程空间 UINT32 ttEntry; LosVmPage *page = NULL; PTE_T *l2Table = NULL; @@ -461,27 +470,27 @@ VOID OsDumpPte(VADDR_T vaddr) return; } - ttEntry = space->archMmu.virtTtb[l1Index]; + ttEntry = space->archMmu.virtTtb[l1Index];//找到 L1 页面项 if (ttEntry) { - l2Table = LOS_PaddrToKVaddr(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry)); - l2Index = (vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> PAGE_SHIFT; + l2Table = LOS_PaddrToKVaddr(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry));//找到L1页面项对应的 L2表 + l2Index = (vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> PAGE_SHIFT;//找到L2页面项 if (l2Table == NULL) { goto ERR; } - page = LOS_VmPageGet(l2Table[l2Index] & ~(PAGE_SIZE - 1)); + page = LOS_VmPageGet(l2Table[l2Index] & ~(PAGE_SIZE - 1));//获取物理页框 if (page == NULL) { goto ERR; } PRINTK("vaddr %p, l1Index %d, ttEntry %p, l2Table %p, l2Index %d, pfn %p count %d\n", - vaddr, l1Index, ttEntry, l2Table, l2Index, l2Table[l2Index], LOS_AtomicRead(&page->refCounts)); - } else { + vaddr, l1Index, ttEntry, l2Table, l2Index, l2Table[l2Index], LOS_AtomicRead(&page->refCounts));//打印L1 L2 页表项 + } else {//不在L1表 PRINTK("vaddr %p, l1Index %d, ttEntry %p\n", vaddr, l1Index, ttEntry); } return; ERR: PRINTK("%s, error vaddr: %#x, l2Table: %#x, l2Index: %#x\n", __FUNCTION__, vaddr, l2Table, l2Index); } - +///获取段剩余页框数 UINT32 OsVmPhySegPagesGet(LosVmPhysSeg *seg) { UINT32 intSave; @@ -489,14 +498,25 @@ UINT32 OsVmPhySegPagesGet(LosVmPhysSeg *seg) UINT32 segFreePages = 0; LOS_SpinLockSave(&seg->freeListLock, &intSave); - for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) { - segFreePages += ((1 << flindex) * seg->freeList[flindex].listCnt); + for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {//遍历块组 + segFreePages += ((1 << flindex) * seg->freeList[flindex].listCnt);//1 << flindex等于页数, * 节点数 得到组块的总页数. } LOS_SpinUnlockRestore(&seg->freeListLock, intSave); - return segFreePages; + return segFreePages;//返回剩余未分配的总物理页框 } - +///dump 物理内存 +/*********************************************************** +* phys_seg:物理页控制块地址信息 +* base:第一个物理页地址,即物理页内存起始地址 +* size:物理页内存大小 +* free_pages:空闲物理页数量 +* active anon: pagecache中,活跃的匿名页数量 +* inactive anon: pagecache中,不活跃的匿名页数量 +* active file: pagecache中,活跃的文件页数量 +* inactive file: pagecache中,不活跃的文件页数量 +* pmm pages total:总的物理页数,used:已使用的物理页数,free:空闲的物理页数 +************************************************************/ VOID OsVmPhysDump(VOID) { LosVmPhysSeg *seg = NULL; @@ -508,7 +528,7 @@ VOID OsVmPhysDump(VOID) UINT32 flindex; UINT32 listCount[VM_LIST_ORDER_MAX] = {0}; - for (segIndex = 0; segIndex < g_vmPhysSegNum; segIndex++) { + for (segIndex = 0; segIndex < g_vmPhysSegNum; segIndex++) {//循环取段 seg = &g_vmPhysSeg[segIndex]; if (seg->size > 0) { segFreePages = OsVmPhySegPagesGet(seg); @@ -538,7 +558,7 @@ VOID OsVmPhysDump(VOID) PRINTK("\n\rpmm pages: total = %u, used = %u, free = %u\n", totalPages, (totalPages - totalFreePages), totalFreePages); } - +///获取物理内存的使用信息,两个参数接走数据 VOID OsVmPhysUsedInfoGet(UINT32 *usedCount, UINT32 *totalCount) { UINT32 index; @@ -551,12 +571,12 @@ VOID OsVmPhysUsedInfoGet(UINT32 *usedCount, UINT32 *totalCount) *usedCount = 0; *totalCount = 0; - for (index = 0; index < g_vmPhysSegNum; index++) { + for (index = 0; index < g_vmPhysSegNum; index++) {//循环取段 physSeg = &g_vmPhysSeg[index]; if (physSeg->size > 0) { - *totalCount += physSeg->size >> PAGE_SHIFT; - segFreePages = OsVmPhySegPagesGet(physSeg); - *usedCount += (*totalCount - segFreePages); + *totalCount += physSeg->size >> PAGE_SHIFT;//叠加段的总页数 + segFreePages = OsVmPhySegPagesGet(physSeg);//获取段的剩余页数 + *usedCount += (*totalCount - segFreePages);//叠加段的使用页数 } } } diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_fault.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_fault.c index 86b08199..902f84ea 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_fault.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_fault.c @@ -54,7 +54,7 @@ extern char __exc_table_start[]; extern char __exc_table_end[]; - +//线性正确性检查 STATIC STATUS_T OsVmRegionPermissionCheck(LosVmMapRegion *region, UINT32 flags) { if ((region->regionFlags & VM_MAP_REGION_FLAG_PERM_READ) != VM_MAP_REGION_FLAG_PERM_READ) { @@ -62,14 +62,14 @@ STATIC STATUS_T OsVmRegionPermissionCheck(LosVmMapRegion *region, UINT32 flags) return LOS_NOK; } - if ((flags & VM_MAP_PF_FLAG_WRITE) == VM_MAP_PF_FLAG_WRITE) { + if ((flags & VM_MAP_PF_FLAG_WRITE) == VM_MAP_PF_FLAG_WRITE) {//写入许可 if ((region->regionFlags & VM_MAP_REGION_FLAG_PERM_WRITE) != VM_MAP_REGION_FLAG_PERM_WRITE) { VM_ERR("write permission check failed operation flags %x, region flags %x", flags, region->regionFlags); return LOS_NOK; } } - if ((flags & VM_MAP_PF_FLAG_INSTRUCTION) == VM_MAP_PF_FLAG_INSTRUCTION) { + if ((flags & VM_MAP_PF_FLAG_INSTRUCTION) == VM_MAP_PF_FLAG_INSTRUCTION) {//指令 if ((region->regionFlags & VM_MAP_REGION_FLAG_PERM_EXECUTE) != VM_MAP_REGION_FLAG_PERM_EXECUTE) { VM_ERR("exec permission check failed operation flags %x, region flags %x", flags, region->regionFlags); return LOS_NOK; @@ -97,7 +97,8 @@ STATIC VOID OsFaultTryFixup(ExcContext *frame, VADDR_T excVaddr, STATUS_T *statu } #ifdef LOSCFG_FS_VFS -STATIC STATUS_T OsDoReadFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) +//读页时发生缺页的处理 +STATIC STATUS_T OsDoReadFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault)//读缺页 { status_t ret; PADDR_T paddr; @@ -105,26 +106,26 @@ STATIC STATUS_T OsDoReadFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) VADDR_T vaddr = (VADDR_T)vmPgFault->vaddr; LosVmSpace *space = region->space; - ret = LOS_ArchMmuQuery(&space->archMmu, vaddr, NULL, NULL); - if (ret == LOS_OK) { - return LOS_OK; + ret = LOS_ArchMmuQuery(&space->archMmu, vaddr, NULL, NULL);//查询是否缺页 + if (ret == LOS_OK) {//注意这里时LOS_OK却返回,都OK了说明查到了物理地址,有页了。 + return LOS_OK;//查到了就说明不缺页的,缺页就是因为虚拟地址没有映射到物理地址嘛 } - if (region->unTypeData.rf.vmFOps == NULL || region->unTypeData.rf.vmFOps->fault == NULL) { + if (region->unTypeData.rf.vmFOps == NULL || region->unTypeData.rf.vmFOps->fault == NULL) {//线性区必须有实现了缺页接口 VM_ERR("region args invalid, file path: %s", region->unTypeData.rf.vnode->filePath); return LOS_ERRNO_VM_INVALID_ARGS; } (VOID)LOS_MuxAcquire(®ion->unTypeData.rf.vnode->mapping.mux_lock); - ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault); + ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault);// 函数指针,执行的是g_commVmOps.OsVmmFileFault if (ret == LOS_OK) { - paddr = LOS_PaddrQuery(vmPgFault->pageKVaddr); - page = LOS_VmPageGet(paddr); + paddr = LOS_PaddrQuery(vmPgFault->pageKVaddr);//查询物理地址 + page = LOS_VmPageGet(paddr);//获取page if (page != NULL) { /* just incase of page null */ - LOS_AtomicInc(&page->refCounts); + LOS_AtomicInc(&page->refCounts);//ref 自增 OsCleanPageLocked(page); } ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, - region->regionFlags & (~VM_MAP_REGION_FLAG_PERM_WRITE)); + region->regionFlags & (~VM_MAP_REGION_FLAG_PERM_WRITE));//重新映射为非可写 if (ret < 0) { VM_ERR("LOS_ArchMmuMap failed"); OsDelMapInfo(region, vmPgFault, false); @@ -140,7 +141,7 @@ STATIC STATUS_T OsDoReadFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) return LOS_ERRNO_VM_NO_MEMORY; } -/* unmap a page when cow happened only */ +/* unmap a page when cow happened only *///仅当写时拷贝发生时取消页面映射 STATIC LosVmPage *OsCowUnmapOrg(LosArchMmu *archMmu, LosVmMapRegion *region, LosVmPgFault *vmf) { UINT32 intSave; @@ -168,7 +169,7 @@ STATIC LosVmPage *OsCowUnmapOrg(LosArchMmu *archMmu, LosVmMapRegion *region, Los return oldPage; } #endif - +//在私有线性区写入文件时发生缺页的处理 status_t OsDoCowFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) { STATUS_T ret; @@ -186,23 +187,23 @@ status_t OsDoCowFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) } space = region->space; - ret = LOS_ArchMmuQuery(&space->archMmu, (VADDR_T)vmPgFault->vaddr, &oldPaddr, NULL); + ret = LOS_ArchMmuQuery(&space->archMmu, (VADDR_T)vmPgFault->vaddr, &oldPaddr, NULL);//查询出老物理地址 if (ret == LOS_OK) { - oldPage = OsCowUnmapOrg(&space->archMmu, region, vmPgFault); + oldPage = OsCowUnmapOrg(&space->archMmu, region, vmPgFault);//取消页面映射 } - newPage = LOS_PhysPageAlloc(); + newPage = LOS_PhysPageAlloc();//分配一个新页面 if (newPage == NULL) { VM_ERR("LOS_PhysPageAlloc failed"); ret = LOS_ERRNO_VM_NO_MEMORY; goto ERR_OUT; } - newPaddr = VM_PAGE_TO_PHYS(newPage); - kvaddr = OsVmPageToVaddr(newPage); + newPaddr = VM_PAGE_TO_PHYS(newPage);//拿到新的物理地址 + kvaddr = OsVmPageToVaddr(newPage);//拿到新的虚拟地址 (VOID)LOS_MuxAcquire(®ion->unTypeData.rf.vnode->mapping.mux_lock); - ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault); + ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault);// 函数指针 g_commVmOps.OsVmmFileFault if (ret != LOS_OK) { VM_ERR("call region->vm_ops->fault fail"); (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock); @@ -214,20 +215,20 @@ status_t OsDoCowFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) * we can take it as a normal file cow map. 2.this page has done file cow map, * we can take it as a anonymous cow map. */ - if ((oldPaddr == 0) || (LOS_PaddrToKVaddr(oldPaddr) == vmPgFault->pageKVaddr)) { - (VOID)memcpy_s(kvaddr, PAGE_SIZE, vmPgFault->pageKVaddr, PAGE_SIZE); - LOS_AtomicInc(&newPage->refCounts); - OsCleanPageLocked(LOS_VmPageGet(LOS_PaddrQuery(vmPgFault->pageKVaddr))); + if ((oldPaddr == 0) || (LOS_PaddrToKVaddr(oldPaddr) == vmPgFault->pageKVaddr)) {//没有映射或者 已在pagecache有映射 + (VOID)memcpy_s(kvaddr, PAGE_SIZE, vmPgFault->pageKVaddr, PAGE_SIZE);//直接copy到新页 + LOS_AtomicInc(&newPage->refCounts);//引用ref++ + OsCleanPageLocked(LOS_VmPageGet(LOS_PaddrQuery(vmPgFault->pageKVaddr)));//解锁 } else { - OsPhysSharePageCopy(oldPaddr, &newPaddr, newPage); + OsPhysSharePageCopy(oldPaddr, &newPaddr, newPage);//调用之前 oldPaddr肯定不等于newPaddr /* use old page free the new one */ - if (newPaddr == oldPaddr) { - LOS_PhysPageFree(newPage); + if (newPaddr == oldPaddr) {//注意这里newPaddr可能已经被改变了,参数传入的是 &newPaddr + LOS_PhysPageFree(newPage);//释放新页,别浪费的内存,内核使用内存是一分钱当十块用. newPage = NULL; } } - ret = LOS_ArchMmuMap(&space->archMmu, (VADDR_T)vmPgFault->vaddr, newPaddr, 1, region->regionFlags); + ret = LOS_ArchMmuMap(&space->archMmu, (VADDR_T)vmPgFault->vaddr, newPaddr, 1, region->regionFlags);//把新物理地址映射给缺页的虚拟地址,这样就不会缺页啦 if (ret < 0) { VM_ERR("LOS_ArchMmuMap failed"); ret = LOS_ERRNO_VM_NO_MEMORY; @@ -252,7 +253,7 @@ ERR_OUT: return ret; } - +///在共享线性区写文件操作发生缺页的情况处理,因为线性区是共享的 status_t OsDoSharedFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) { STATUS_T ret; @@ -268,10 +269,10 @@ status_t OsDoSharedFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) return LOS_ERRNO_VM_INVALID_ARGS; } - ret = LOS_ArchMmuQuery(&space->archMmu, vmPgFault->vaddr, &paddr, NULL); + ret = LOS_ArchMmuQuery(&space->archMmu, vmPgFault->vaddr, &paddr, NULL);//查询物理地址 if (ret == LOS_OK) { - LOS_ArchMmuUnmap(&space->archMmu, vmPgFault->vaddr, 1); - ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, region->regionFlags); + LOS_ArchMmuUnmap(&space->archMmu, vmPgFault->vaddr, 1);//先取消映射 + ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, region->regionFlags);//再重新映射,为啥这么干,是因为regionFlags变了, if (ret < 0) { VM_ERR("LOS_ArchMmuMap failed. ret=%d", ret); return LOS_ERRNO_VM_NO_MEMORY; @@ -279,16 +280,16 @@ status_t OsDoSharedFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) LOS_SpinLockSave(®ion->unTypeData.rf.vnode->mapping.list_lock, &intSave); fpage = OsFindGetEntry(®ion->unTypeData.rf.vnode->mapping, vmPgFault->pgoff); - if (fpage) { - OsMarkPageDirty(fpage, region, 0, 0); + if (fpage) {//在页高速缓存(page cache)中找到了 + OsMarkPageDirty(fpage, region, 0, 0);//标记为脏页 } LOS_SpinUnlockRestore(®ion->unTypeData.rf.vnode->mapping.list_lock, intSave); return LOS_OK; } - + //以下是没有映射到物理地址的处理 (VOID)LOS_MuxAcquire(®ion->unTypeData.rf.vnode->mapping.mux_lock); - ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault); + ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault);//函数指针,执行的是g_commVmOps.OsVmmFileFault if (ret == LOS_OK) { paddr = LOS_PaddrQuery(vmPgFault->pageKVaddr); page = LOS_VmPageGet(paddr); @@ -319,23 +320,36 @@ status_t OsDoSharedFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault) * For COW fault, pagecache is copied to private anonyous pages and the changes on this page * won't write through to the underlying file. For SHARED fault, pagecache is mapping with * region->arch_mmu_flags and the changes on this page will write through to the underlying file - */ + */ //操作文件时产生缺页中断 STATIC STATUS_T OsDoFileFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault, UINT32 flags) { STATUS_T ret; - if (flags & VM_MAP_PF_FLAG_WRITE) { - if (region->regionFlags & VM_MAP_REGION_FLAG_SHARED) { - ret = OsDoSharedFault(region, vmPgFault); - } else { - ret = OsDoCowFault(region, vmPgFault); + if (flags & VM_MAP_PF_FLAG_WRITE) {//写页的时候产生缺页 + if (region->regionFlags & VM_MAP_REGION_FLAG_SHARED) {//共享线性区 + ret = OsDoSharedFault(region, vmPgFault);//写操作时的共享缺页,最复杂,此页上的更改将写入磁盘文件 + } else {//非共享线性区 + ret = OsDoCowFault(region, vmPgFault);//(写时拷贝技术)写操作时的私有缺页,pagecache被复制到私有的任意一个页面上,并在此页面上进行更改,不会直接写入磁盘文件 } - } else { - ret = OsDoReadFault(region, vmPgFault); + } else {//读页的时候产生缺页 + ret = OsDoReadFault(region, vmPgFault);//页面读取操作很简单,只需共享页面缓存(节省内存)并进行读权限映射(region->arch_mmu_flags&(~arch_mmu_FLAG_PERM_WRITE)) } return ret; } +/*************************************************************** +缺页中断处理程序 +通常有两种情况导致 +第一种:由编程错误引起的异常 +第二种:属于进程的地址空间范围但还尚未分配物理页框引起的异常 +***************************************************************/ +/** + * @brief + * @param vaddr + * @param flags + * @param frame + * @return STATUS_T + */ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) { LosVmSpace *space = LOS_SpaceGet(vaddr); @@ -354,9 +368,9 @@ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) return status; } - if (((flags & VM_MAP_PF_FLAG_USER) != 0) && (!LOS_IsUserAddress(vaddr))) { + if (((flags & VM_MAP_PF_FLAG_USER) != 0) && (!LOS_IsUserAddress(vaddr))) {//地址保护,用户空间不允许跨界访问 VM_ERR("user space not allowed to access invalid address: %#x", vaddr); - return LOS_ERRNO_VM_ACCESS_DENIED; + return LOS_ERRNO_VM_ACCESS_DENIED;//拒绝访问 } #ifdef LOSCFG_KERNEL_PLIMITS @@ -366,7 +380,7 @@ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) #endif (VOID)LOS_MuxAcquire(&space->regionMux); - region = LOS_RegionFind(space, vaddr); + region = LOS_RegionFind(space, vaddr);//通过虚拟地址找到所在线性区 if (region == NULL) { VM_ERR("region not exists, vaddr: %#x", vaddr); status = LOS_ERRNO_VM_NOT_FOUND; @@ -375,11 +389,11 @@ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) status = OsVmRegionPermissionCheck(region, flags); if (status != LOS_OK) { - status = LOS_ERRNO_VM_ACCESS_DENIED; + status = LOS_ERRNO_VM_ACCESS_DENIED;//拒绝访问 goto CHECK_FAILED; } - if (OomCheckProcess()) { + if (OomCheckProcess()) {//低内存检查 /* * under low memory, when user process request memory allocation * it will fail, and result is LOS_NOK and current user process @@ -389,18 +403,18 @@ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) goto CHECK_FAILED; } - vaddr = ROUNDDOWN(vaddr, PAGE_SIZE); + vaddr = ROUNDDOWN(vaddr, PAGE_SIZE);//为啥要向下圆整,因为这一页要重新使用,需找到页面基地址 #ifdef LOSCFG_FS_VFS - if (LOS_IsRegionFileValid(region)) { + if (LOS_IsRegionFileValid(region)) {//是否为文件线性区 if (region->unTypeData.rf.vnode == NULL) { goto CHECK_FAILED; } - vmPgFault.vaddr = vaddr; - vmPgFault.pgoff = ((vaddr - region->range.base) >> PAGE_SHIFT) + region->pgOff; + vmPgFault.vaddr = vaddr;//虚拟地址 + vmPgFault.pgoff = ((vaddr - region->range.base) >> PAGE_SHIFT) + region->pgOff;//计算出文件读取位置 vmPgFault.flags = flags; - vmPgFault.pageKVaddr = NULL; + vmPgFault.pageKVaddr = NULL;//缺失页初始化没有物理地址 - status = OsDoFileFault(region, &vmPgFault, flags); + status = OsDoFileFault(region, &vmPgFault, flags);//缺页处理 if (status) { VM_ERR("vm fault error, status=%d", status); goto CHECK_FAILED; @@ -408,27 +422,27 @@ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) goto DONE; } #endif - - newPage = LOS_PhysPageAlloc(); + //请求调页:推迟到不能再推迟为止 + newPage = LOS_PhysPageAlloc();//分配一个新的物理页 if (newPage == NULL) { status = LOS_ERRNO_VM_NO_MEMORY; goto CHECK_FAILED; } - newPaddr = VM_PAGE_TO_PHYS(newPage); - (VOID)memset_s(OsVmPageToVaddr(newPage), PAGE_SIZE, 0, PAGE_SIZE); - status = LOS_ArchMmuQuery(&space->archMmu, vaddr, &oldPaddr, NULL); + newPaddr = VM_PAGE_TO_PHYS(newPage);//获取物理地址 + (VOID)memset_s(OsVmPageToVaddr(newPage), PAGE_SIZE, 0, PAGE_SIZE);//获取虚拟地址 清0 + status = LOS_ArchMmuQuery(&space->archMmu, vaddr, &oldPaddr, NULL);//通过虚拟地址查询老物理地址 if (status >= 0) { - LOS_ArchMmuUnmap(&space->archMmu, vaddr, 1); - OsPhysSharePageCopy(oldPaddr, &newPaddr, newPage); + LOS_ArchMmuUnmap(&space->archMmu, vaddr, 1);//解除映射关系 + OsPhysSharePageCopy(oldPaddr, &newPaddr, newPage);//将oldPaddr的数据拷贝到newPage /* use old page free the new one */ - if (newPaddr == oldPaddr) { - LOS_PhysPageFree(newPage); + if (newPaddr == oldPaddr) {//新老物理地址一致 + LOS_PhysPageFree(newPage);//继续使用旧页释放新页 newPage = NULL; } /* map all of the pages */ - status = LOS_ArchMmuMap(&space->archMmu, vaddr, newPaddr, 1, region->regionFlags); + status = LOS_ArchMmuMap(&space->archMmu, vaddr, newPaddr, 1, region->regionFlags);//重新映射新物理地址 if (status < 0) { VM_ERR("failed to map replacement page, status:%d", status); status = LOS_ERRNO_VM_MAP_FAILED; @@ -439,8 +453,8 @@ STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame) goto DONE; } else { /* map all of the pages */ - LOS_AtomicInc(&newPage->refCounts); - status = LOS_ArchMmuMap(&space->archMmu, vaddr, newPaddr, 1, region->regionFlags); + LOS_AtomicInc(&newPage->refCounts);//引用数自增 + status = LOS_ArchMmuMap(&space->archMmu, vaddr, newPaddr, 1, region->regionFlags);//映射新物理地址,如此下次就不会缺页了 if (status < 0) { VM_ERR("failed to map page, status:%d", status); status = LOS_ERRNO_VM_MAP_FAILED; diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_filemap.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_filemap.c index e95cd826..2da700d8 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_filemap.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_filemap.c @@ -66,98 +66,114 @@ VOID ResetPageCacheHitInfo(int *try, int *hit) #define TRACE_TRY_CACHE() #define TRACE_HIT_CACHE() #endif - #ifdef LOSCFG_KERNEL_VM +/** + * @brief + @verbatim + 增加文件页到页高速缓存(page cache) + LosFilePage将一个文件切成了一页一页,因为读文件过程随机seek,所以文件页也不会是连续的, + pgoff记录文件的位置,并确保在cache的文件数据是按顺序排列的. + @endverbatim + * @param page + * @param mapping + * @param pgoff + * @return STATIC + */ STATIC VOID OsPageCacheAdd(LosFilePage *page, struct page_mapping *mapping, VM_OFFSET_T pgoff) { LosFilePage *fpage = NULL; - LOS_DL_LIST_FOR_EACH_ENTRY(fpage, &mapping->page_list, LosFilePage, node) { - if (fpage->pgoff > pgoff) { - LOS_ListTailInsert(&fpage->node, &page->node); + LOS_DL_LIST_FOR_EACH_ENTRY(fpage, &mapping->page_list, LosFilePage, node) {//遍历page_list链表 + if (fpage->pgoff > pgoff) {//插入的条件,这样插入保证了按pgoff 从小到大排序 + LOS_ListTailInsert(&fpage->node, &page->node);//等于挂到fpage节点的前面了 goto done_add; } } - LOS_ListTailInsert(&mapping->page_list, &page->node); + LOS_ListTailInsert(&mapping->page_list, &page->node);//将页挂到文件映射的链表上,相当于挂到了最后 done_add: - mapping->nrpages++; + mapping->nrpages++; //文件在缓存中多了一个 文件页 } - +///将页面加到活动文件页LRU链表上 VOID OsAddToPageacheLru(LosFilePage *page, struct page_mapping *mapping, VM_OFFSET_T pgoff) { OsPageCacheAdd(page, mapping, pgoff); OsLruCacheAdd(page, VM_LRU_ACTIVE_FILE); } - +///从页高速缓存上删除页 VOID OsPageCacheDel(LosFilePage *fpage) { /* delete from file cache list */ - LOS_ListDelete(&fpage->node); - fpage->mapping->nrpages--; + LOS_ListDelete(&fpage->node);//将自己从链表上摘除 + fpage->mapping->nrpages--;//文件映射的页总数减少 /* unmap and remove map info */ - if (OsIsPageMapped(fpage)) { + if (OsIsPageMapped(fpage)) {//是否映射过 OsUnmapAllLocked(fpage); } - LOS_PhysPageFree(fpage->vmPage); + LOS_PhysPageFree(fpage->vmPage);//释放物理内存 - LOS_MemFree(m_aucSysMem0, fpage); + LOS_MemFree(m_aucSysMem0, fpage);//释放文件页结构体内存 } - +/************************************************************************************************** +每个进程都有自己的地址空间, 多个进程可以访问同一个LosFilePage,每个进程使用的虚拟地址都需要单独映射 +所以同一个LosFilePage会映射到多个进程空间.本函数记录页面被哪些进程映射过 +在两个地方会被被空间映射 +1.缺页中断 2.克隆地址空间 +**************************************************************************************************/ VOID OsAddMapInfo(LosFilePage *page, LosArchMmu *archMmu, VADDR_T vaddr) { LosMapInfo *info = NULL; - info = (LosMapInfo *)LOS_MemAlloc(m_aucSysMem0, sizeof(LosMapInfo)); + info = (LosMapInfo *)LOS_MemAlloc(m_aucSysMem0, sizeof(LosMapInfo));//分配一个映射信息 if (info == NULL) { VM_ERR("OsAddMapInfo alloc memory failed!"); return; } - info->page = page; - info->archMmu = archMmu; - info->vaddr = vaddr; + info->page = page; //文件页 + info->archMmu = archMmu;//进程MMU,完成虚实地址转换 + info->vaddr = vaddr; //虚拟地址 - LOS_ListAdd(&page->i_mmap, &info->node); - page->n_maps++; + LOS_ListAdd(&page->i_mmap, &info->node);//将 LosMapInfo 节点挂入链表 + page->n_maps++;//映射总数++ } - +///通过虚拟地址获取文件页映射信息,archMmu每个进程都有属于自己的mmu LosMapInfo *OsGetMapInfo(const LosFilePage *page, const LosArchMmu *archMmu, VADDR_T vaddr) { LosMapInfo *info = NULL; - const LOS_DL_LIST *immap = &page->i_mmap; + const LOS_DL_LIST *immap = &page->i_mmap;//一个文件页被多个进程映射 - LOS_DL_LIST_FOR_EACH_ENTRY(info, immap, LosMapInfo, node) { - if ((info->archMmu == archMmu) && (info->vaddr == vaddr) && (info->page == page)) { + LOS_DL_LIST_FOR_EACH_ENTRY(info, immap, LosMapInfo, node) {//遍历每个节点 + if ((info->archMmu == archMmu) && (info->vaddr == vaddr) && (info->page == page)) {//全等时返回 return info; } } - return NULL; } - +///删除页高速缓存和LRU,对应 OsAddToPageacheLru VOID OsDeletePageCacheLru(LosFilePage *page) { - /* delete from lru list */ - OsLruCacheDel(page); - /* delete from cache list and free pmm if needed */ - OsPageCacheDel(page); + /* delete form lru list */ + OsLruCacheDel(page); //将页面从lru列表中删除 + /* delete from cache lits and free pmm if need */ + OsPageCacheDel(page); //从page缓存中删除 } +//解除文件页和进程的映射关系 STATIC VOID OsPageCacheUnmap(LosFilePage *fpage, LosArchMmu *archMmu, VADDR_T vaddr) { UINT32 intSave; LosMapInfo *info = NULL; LOS_SpinLockSave(&fpage->physSeg->lruLock, &intSave); - info = OsGetMapInfo(fpage, archMmu, vaddr); + info = OsGetMapInfo(fpage, archMmu, vaddr);//获取文件页在进程的映射信息 if (info == NULL) { VM_ERR("OsPageCacheUnmap get map info failed!"); } else { - OsUnmapPageLocked(fpage, info); + OsUnmapPageLocked(fpage, info);//解除进程和文件页映射关系 } if (!(OsIsPageMapped(fpage) && ((fpage->flags & VM_MAP_REGION_FLAG_PERM_EXECUTE) || OsIsPageDirty(fpage->vmPage)))) { @@ -166,7 +182,7 @@ STATIC VOID OsPageCacheUnmap(LosFilePage *fpage, LosArchMmu *archMmu, VADDR_T va LOS_SpinUnlockRestore(&fpage->physSeg->lruLock, intSave); } - +///删除文件 VOID OsVmmFileRemove(LosVmMapRegion *region, LosArchMmu *archMmu, VM_OFFSET_T pgoff) { UINT32 intSave; @@ -179,31 +195,31 @@ VOID OsVmmFileRemove(LosVmMapRegion *region, LosArchMmu *archMmu, VM_OFFSET_T pg LosVmPage *mapPage = NULL; if (!LOS_IsRegionFileValid(region) || (region->unTypeData.rf.vnode == NULL)) { - return; + return;//判断是否为文件映射,是否已map } vnode = region->unTypeData.rf.vnode; mapping = &vnode->mapping; - vaddr = region->range.base + ((UINT32)(pgoff - region->pgOff) << PAGE_SHIFT); + vaddr = region->range.base + ((UINT32)(pgoff - region->pgOff) << PAGE_SHIFT);//得到虚拟地址 - status_t status = LOS_ArchMmuQuery(archMmu, vaddr, &paddr, NULL); + status_t status = LOS_ArchMmuQuery(archMmu, vaddr, &paddr, NULL);//获取物理地址 if (status != LOS_OK) { return; } - mapPage = LOS_VmPageGet(paddr); + mapPage = LOS_VmPageGet(paddr);//获取物理页框 /* is page is in cache list */ LOS_SpinLockSave(&mapping->list_lock, &intSave); - fpage = OsFindGetEntry(mapping, pgoff); + fpage = OsFindGetEntry(mapping, pgoff);//获取fpage /* no cache or have cache but not map(cow), free it direct */ - if ((fpage == NULL) || (fpage->vmPage != mapPage)) { - LOS_PhysPageFree(mapPage); - LOS_ArchMmuUnmap(archMmu, vaddr, 1); + if ((fpage == NULL) || (fpage->vmPage != mapPage)) {//没有缓存或有缓存但没有映射(cow),直接释放它 + LOS_PhysPageFree(mapPage);//释放物理页框 + LOS_ArchMmuUnmap(archMmu, vaddr, 1);//取消虚拟地址的映射 /* this is a page cache map! */ } else { - OsPageCacheUnmap(fpage, archMmu, vaddr); - if (OsIsPageDirty(fpage->vmPage)) { - tmpPage = OsDumpDirtyPage(fpage); + OsPageCacheUnmap(fpage, archMmu, vaddr);////取消缓存中的映射 + if (OsIsPageDirty(fpage->vmPage)) {//脏页处理 + tmpPage = OsDumpDirtyPage(fpage);//dump 脏页 } } LOS_SpinUnlockRestore(&mapping->list_lock, intSave); @@ -213,15 +229,15 @@ VOID OsVmmFileRemove(LosVmMapRegion *region, LosArchMmu *archMmu, VM_OFFSET_T pg } return; } - +///标记page为脏页 进程修改了高速缓存里的数据时,该页就被内核标记为脏页 VOID OsMarkPageDirty(LosFilePage *fpage, const LosVmMapRegion *region, INT32 off, INT32 len) { if (region != NULL) { - OsSetPageDirty(fpage->vmPage); - fpage->dirtyOff = off; - fpage->dirtyEnd = len; + OsSetPageDirty(fpage->vmPage);//设置为脏页 + fpage->dirtyOff = off;//脏页偏移位置 + fpage->dirtyEnd = len;//脏页结束位置 } else { - OsSetPageDirty(fpage->vmPage); + OsSetPageDirty(fpage->vmPage);//设置为脏页 if ((off + len) > fpage->dirtyEnd) { fpage->dirtyEnd = off + len; } @@ -258,22 +274,22 @@ STATIC UINT32 GetDirtySize(LosFilePage *fpage, struct Vnode *vnode) return PAGE_SIZE; } - +///冲洗脏页,回写磁盘 STATIC INT32 OsFlushDirtyPage(LosFilePage *fpage) { UINT32 ret; size_t len; char *buff = NULL; - struct Vnode *vnode = fpage->mapping->host; + struct Vnode *vnode = fpage->mapping->host;/* owner of this mapping */ //此映射属于哪个文件,注意是1:1的关系. if (vnode == NULL) { VM_ERR("page cache vnode error"); return LOS_NOK; } - len = fpage->dirtyEnd - fpage->dirtyOff; + len = fpage->dirtyEnd - fpage->dirtyOff;//计算出脏数据长度 len = (len == 0) ? GetDirtySize(fpage, vnode) : len; - if (len == 0) { - OsCleanPageDirty(fpage->vmPage); + if (len == 0) {//没有脏数据 + OsCleanPageDirty(fpage->vmPage);//页面取消脏标签 return LOS_OK; } @@ -290,7 +306,7 @@ STATIC INT32 OsFlushDirtyPage(LosFilePage *fpage) return ret; } - +///备份脏页,老脏页撕掉脏页标签 LosFilePage *OsDumpDirtyPage(LosFilePage *oldFPage) { LosFilePage *newFPage = NULL; @@ -302,11 +318,11 @@ LosFilePage *OsDumpDirtyPage(LosFilePage *oldFPage) } OsCleanPageDirty(oldFPage->vmPage); - (VOID)memcpy_s(newFPage, sizeof(LosFilePage), oldFPage, sizeof(LosFilePage)); + (VOID)memcpy_s(newFPage, sizeof(LosFilePage), oldFPage, sizeof(LosFilePage));//直接内存拷贝 return newFPage; } - +///冲洗脏页数据,将脏页数据回写磁盘 VOID OsDoFlushDirtyPage(LosFilePage *fpage) { if (fpage == NULL) { @@ -328,7 +344,7 @@ STATIC VOID OsReleaseFpage(struct page_mapping *mapping, LosFilePage *fpage) LOS_SpinUnlockRestore(lruLock, lruSave); LOS_SpinUnlockRestore(&mapping->list_lock, intSave); } - +///删除映射信息 VOID OsDelMapInfo(LosVmMapRegion *region, LosVmPgFault *vmf, BOOL cleanDirty) { UINT32 intSave; @@ -349,9 +365,9 @@ VOID OsDelMapInfo(LosVmMapRegion *region, LosVmPgFault *vmf, BOOL cleanDirty) } if (cleanDirty) { - OsCleanPageDirty(fpage->vmPage); + OsCleanPageDirty(fpage->vmPage);//恢复干净页 } - info = OsGetMapInfo(fpage, ®ion->space->archMmu, (vaddr_t)vmf->vaddr); + info = OsGetMapInfo(fpage, ®ion->space->archMmu, (vaddr_t)vmf->vaddr);//通过虚拟地址获取映射信息 if (info != NULL) { fpage->n_maps--; LOS_ListDelete(&info->node); @@ -362,7 +378,10 @@ VOID OsDelMapInfo(LosVmMapRegion *region, LosVmPgFault *vmf, BOOL cleanDirty) } LOS_SpinUnlockRestore(&mapping->list_lock, intSave); } - +/*! +文件缺页时的处理,先读入磁盘数据,再重新读页数据 +被 OsDoReadFault(...),OsDoCowFault(...),OsDoSharedFault(...) 等调用 +*/ INT32 OsVmmFileFault(LosVmMapRegion *region, LosVmPgFault *vmf) { INT32 ret; @@ -374,7 +393,7 @@ INT32 OsVmmFileFault(LosVmMapRegion *region, LosVmPgFault *vmf) struct page_mapping *mapping = NULL; LosFilePage *fpage = NULL; - if (!LOS_IsRegionFileValid(region) || (region->unTypeData.rf.vnode == NULL) || (vmf == NULL)) { + if (!LOS_IsRegionFileValid(region) || (region->unTypeData.rf.vnode == NULL) || (vmf == NULL)) {//文件是否映射到了内存 VM_ERR("Input param is NULL"); return LOS_NOK; } @@ -383,26 +402,26 @@ INT32 OsVmmFileFault(LosVmMapRegion *region, LosVmPgFault *vmf) /* get or create a new cache node */ LOS_SpinLockSave(&mapping->list_lock, &intSave); - fpage = OsFindGetEntry(mapping, vmf->pgoff); + fpage = OsFindGetEntry(mapping, vmf->pgoff);//获取文件页 TRACE_TRY_CACHE(); - if (fpage != NULL) { + if (fpage != NULL) {//找到了,说明该页已经在页高速缓存中 TRACE_HIT_CACHE(); OsPageRefIncLocked(fpage); - } else { - fpage = OsPageCacheAlloc(mapping, vmf->pgoff); + } else {//真的缺页了,页高速缓存中没找到 + fpage = OsPageCacheAlloc(mapping, vmf->pgoff);//分配一个文件页,将数据初始化好,包括vmpage(物理页框) if (fpage == NULL) { LOS_SpinUnlockRestore(&mapping->list_lock, intSave); VM_ERR("Failed to alloc a page frame"); return LOS_NOK; } - newCache = true; + newCache = true;//分配了新文件页 } - OsSetPageLocked(fpage->vmPage); + OsSetPageLocked(fpage->vmPage);//对vmpage上锁 LOS_SpinUnlockRestore(&mapping->list_lock, intSave); - kvaddr = OsVmPageToVaddr(fpage->vmPage); + kvaddr = OsVmPageToVaddr(fpage->vmPage);//获取该页框在内核空间的虚拟地址,因为 page cache本身就是在内核空间, /* read file to new page cache */ - if (newCache) { + if (newCache) {//新cache ret = vnode->vop->ReadPage(vnode, kvaddr, fpage->pgoff << PAGE_SHIFT); if (ret == 0) { VM_ERR("Failed to read from file!"); @@ -410,32 +429,32 @@ INT32 OsVmmFileFault(LosVmMapRegion *region, LosVmPgFault *vmf) return LOS_NOK; } LOS_SpinLockSave(&mapping->list_lock, &intSave); - OsAddToPageacheLru(fpage, mapping, vmf->pgoff); + OsAddToPageacheLru(fpage, mapping, vmf->pgoff);//将fpage挂入pageCache 和 LruCache LOS_SpinUnlockRestore(&mapping->list_lock, intSave); } LOS_SpinLockSave(&mapping->list_lock, &intSave); /* cow fault case no need to save mapinfo */ if (!((vmf->flags & VM_MAP_PF_FLAG_WRITE) && !(region->regionFlags & VM_MAP_REGION_FLAG_SHARED))) { - OsAddMapInfo(fpage, ®ion->space->archMmu, (vaddr_t)vmf->vaddr); + OsAddMapInfo(fpage, ®ion->space->archMmu, (vaddr_t)vmf->vaddr);//添加<虚拟地址,文件页>的映射关系,如此进程以后就能通过虚拟地址操作文件页了. fpage->flags = region->regionFlags; } /* share page fault, mark the page dirty */ - if ((vmf->flags & VM_MAP_PF_FLAG_WRITE) && (region->regionFlags & VM_MAP_REGION_FLAG_SHARED)) { - OsMarkPageDirty(fpage, region, 0, 0); + if ((vmf->flags & VM_MAP_PF_FLAG_WRITE) && (region->regionFlags & VM_MAP_REGION_FLAG_SHARED)) {//有过写操作或者为共享线性区 + OsMarkPageDirty(fpage, region, 0, 0);//标记为脏页,要回写磁盘,内核会在适当的时候回写磁盘 } - vmf->pageKVaddr = kvaddr; + vmf->pageKVaddr = kvaddr;//缺陷页记录文件页的虚拟地址 LOS_SpinUnlockRestore(&mapping->list_lock, intSave); return LOS_OK; } - +///文件缓存冲洗,把所有fpage冲洗一边,把脏页洗到dirtyList中,配合OsFileCacheRemove理解 VOID OsFileCacheFlush(struct page_mapping *mapping) { UINT32 intSave; UINT32 lruLock; - LOS_DL_LIST_HEAD(dirtyList); + LOS_DL_LIST_HEAD(dirtyList);//LOS_DL_LIST list = { &(list), &(list) }; LosFilePage *ftemp = NULL; LosFilePage *fpage = NULL; @@ -443,70 +462,77 @@ VOID OsFileCacheFlush(struct page_mapping *mapping) return; } LOS_SpinLockSave(&mapping->list_lock, &intSave); - LOS_DL_LIST_FOR_EACH_ENTRY(fpage, &mapping->page_list, LosFilePage, node) { + LOS_DL_LIST_FOR_EACH_ENTRY(fpage, &mapping->page_list, LosFilePage, node) {//循环从page_list中取node给fpage LOS_SpinLockSave(&fpage->physSeg->lruLock, &lruLock); - if (OsIsPageDirty(fpage->vmPage)) { - ftemp = OsDumpDirtyPage(fpage); + if (OsIsPageDirty(fpage->vmPage)) {//是否为脏页 + ftemp = OsDumpDirtyPage(fpage);//这里挺妙的,copy出一份新页,老页变成了非脏页继续用 if (ftemp != NULL) { - LOS_ListTailInsert(&dirtyList, &ftemp->node); + LOS_ListTailInsert(&dirtyList, &ftemp->node);//将新页插入脏页List,等待回写磁盘 } } LOS_SpinUnlockRestore(&fpage->physSeg->lruLock, lruLock); } LOS_SpinUnlockRestore(&mapping->list_lock, intSave); - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, ftemp, &dirtyList, LosFilePage, node) { - OsDoFlushDirtyPage(fpage); + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, ftemp, &dirtyList, LosFilePage, node) {//仔细看这个宏,关键在 &(item)->member != (list); + OsDoFlushDirtyPage(fpage);//立马洗掉,所以dirtyList可以不是全局变量 } } +/****************************************************************************** + 删除文件缓存,清空文件在page cache的痕迹 + 参数 mapping 可理解为文件在内存的身份证 +******************************************************************************/ VOID OsFileCacheRemove(struct page_mapping *mapping) { UINT32 intSave; UINT32 lruSave; SPIN_LOCK_S *lruLock = NULL; - LOS_DL_LIST_HEAD(dirtyList); + LOS_DL_LIST_HEAD(dirtyList);//定义一个叫dirtyList的双循环链表并初始化,用于挂脏页 LosFilePage *ftemp = NULL; LosFilePage *fpage = NULL; LosFilePage *fnext = NULL; - LOS_SpinLockSave(&mapping->list_lock, &intSave); - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, &mapping->page_list, LosFilePage, node) { + LOS_SpinLockSave(&mapping->list_lock, &intSave);//多进程操作,必须上锁. + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, &mapping->page_list, LosFilePage, node) {//遍历文件在内存中产生的所有文件页(例如1,4,8页)不一定连续,取决于用户的读取顺序 lruLock = &fpage->physSeg->lruLock; - LOS_SpinLockSave(lruLock, &lruSave); - if (OsIsPageDirty(fpage->vmPage)) { - ftemp = OsDumpDirtyPage(fpage); - if (ftemp != NULL) { - LOS_ListTailInsert(&dirtyList, &ftemp->node); + LOS_SpinLockSave(lruLock, &lruSave);//@note_why 自旋锁有必要从这里开始上锁吗? + if (OsIsPageDirty(fpage->vmPage)) {//数据是脏页吗,脏页就是被修改过数据的页 + ftemp = OsDumpDirtyPage(fpage);//做这个拷贝动作是为了fpage的统一下线,因为数据回写磁盘的速度是很慢的,如果直接在这里处理脏数据 + if (ftemp != NULL) {//会导致函数持有mapping->list_lock自旋锁的时间太长了,影响其他CPU的处理效率 + LOS_ListTailInsert(&dirtyList, &ftemp->node);//将临时脏页挂到记录脏页链表上 } } - OsDeletePageCacheLru(fpage); + OsDeletePageCacheLru(fpage);//删除高速缓存和从置换链表中下线 LOS_SpinUnlockRestore(lruLock, lruSave); } - LOS_SpinUnlockRestore(&mapping->list_lock, intSave); + LOS_SpinUnlockRestore(&mapping->list_lock, intSave);//恢复自旋锁,不能让别的CPU等太久 - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, &dirtyList, LosFilePage, node) { - OsDoFlushDirtyPage(fpage); + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, &dirtyList, LosFilePage, node) {//到这里,再来慢慢的统一处理脏页数据 + OsDoFlushDirtyPage(fpage);//遍历脏页链表,一页一页处理 } } - -LosVmFileOps g_commVmOps = { +///虚拟内存文件操作实现类 +LosVmFileOps g_commVmOps = {// .open = NULL, .close = NULL, - .fault = OsVmmFileFault, - .remove = OsVmmFileRemove, + .fault = OsVmmFileFault, //缺页中断处理 + .remove = OsVmmFileRemove,//删除页 }; - +//文件映射 INT32 OsVfsFileMmap(struct file *filep, LosVmMapRegion *region) { - region->unTypeData.rf.vmFOps = &g_commVmOps; + region->unTypeData.rf.vmFOps = &g_commVmOps;//文件操作 region->unTypeData.rf.vnode = filep->f_vnode; region->unTypeData.rf.f_oflags = filep->f_oflags; return ENOERR; } - +/*! + 有名映射,可理解为文件映射,跟匿名映射相对应 + 参数filep是广义的文件,在鸿蒙内核,目录/普通文件/字符设备/块设备/网络套接字/管道/链接 都是文件 +*/ STATUS_T OsNamedMMap(struct file *filep, LosVmMapRegion *region) { struct Vnode *vnode = NULL; @@ -519,10 +545,10 @@ STATUS_T OsNamedMMap(struct file *filep, LosVmMapRegion *region) vnode->useCount++; VnodeDrop(); if (filep->ops != NULL && filep->ops->mmap != NULL) { - if (vnode->type == VNODE_TYPE_CHR || vnode->type == VNODE_TYPE_BLK) { - LOS_SetRegionTypeDev(region); + if (vnode->type == VNODE_TYPE_CHR || vnode->type == VNODE_TYPE_BLK) {//块设备或者字符设备 /dev/.. + LOS_SetRegionTypeDev(region);//设置为设备类型 } else { - LOS_SetRegionTypeFile(region); + LOS_SetRegionTypeFile(region);//设置为文件类型 } int ret = filep->ops->mmap(filep, region); if (ret != LOS_OK) { @@ -538,17 +564,21 @@ STATUS_T OsNamedMMap(struct file *filep, LosVmMapRegion *region) return LOS_OK; } +/************************************************************************************************** + 通过位置从文件映射页中找到的指定的文件页 + 举例:mapping->page_list上节点的数据可能只有是文件 1,3,4,6 页的数据,此时来找文件第5页的数据就会没有 +**************************************************************************************************/ LosFilePage *OsFindGetEntry(struct page_mapping *mapping, VM_OFFSET_T pgoff) { LosFilePage *fpage = NULL; - LOS_DL_LIST_FOR_EACH_ENTRY(fpage, &mapping->page_list, LosFilePage, node) { - if (fpage->pgoff == pgoff) { + LOS_DL_LIST_FOR_EACH_ENTRY(fpage, &mapping->page_list, LosFilePage, node) {//遍历文件页 + if (fpage->pgoff == pgoff) {//找到指定的页, return fpage; } - if (fpage->pgoff > pgoff) { - break; + if (fpage->pgoff > pgoff) {//大于之前还没有找到,说明不在链表中,往后的也不用找了, + break;//因为 mapping->page_list节点上的数据都是按 fpage->pgoff 从小到大的顺序排列的. } } @@ -556,6 +586,11 @@ LosFilePage *OsFindGetEntry(struct page_mapping *mapping, VM_OFFSET_T pgoff) } /* need mutex & change memory to dma zone. */ +/*! +以页高速缓存方式分配一个文件页 LosFilePage + Direct Memory Access(存储器直接访问)指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据。 + 整个数据传输操作在一个称为"DMA控制器"的控制下进行的。CPU只需在数据传输开始和结束时做一点处理(开始和结束时候要做中断处理) +*/ LosFilePage *OsPageCacheAlloc(struct page_mapping *mapping, VM_OFFSET_T pgoff) { VOID *kvaddr = NULL; @@ -563,39 +598,39 @@ LosFilePage *OsPageCacheAlloc(struct page_mapping *mapping, VM_OFFSET_T pgoff) LosVmPage *vmPage = NULL; LosFilePage *fpage = NULL; - vmPage = LOS_PhysPageAlloc(); + vmPage = LOS_PhysPageAlloc(); //先分配一个物理页 if (vmPage == NULL) { VM_ERR("alloc vm page failed"); return NULL; } - physSeg = OsVmPhysSegGet(vmPage); - kvaddr = OsVmPageToVaddr(vmPage); + physSeg = OsVmPhysSegGet(vmPage);//通过页获取所在seg + kvaddr = OsVmPageToVaddr(vmPage);//获取内核空间的虚拟地址,具体点进去看函数说明,这里一定要理解透彻! if ((physSeg == NULL) || (kvaddr == NULL)) { - LOS_PhysPageFree(vmPage); + LOS_PhysPageFree(vmPage); //异常情况要释放vmPage VM_ERR("alloc vm page failed!"); return NULL; } - fpage = (LosFilePage *)LOS_MemAlloc(m_aucSysMem0, sizeof(LosFilePage)); + fpage = (LosFilePage *)LOS_MemAlloc(m_aucSysMem0, sizeof(LosFilePage));//从内存池中分配一个filePage if (fpage == NULL) { - LOS_PhysPageFree(vmPage); + LOS_PhysPageFree(vmPage); //异常情况要释放vmPage VM_ERR("Failed to allocate for page!"); return NULL; } - (VOID)memset_s((VOID *)fpage, sizeof(LosFilePage), 0, sizeof(LosFilePage)); + (VOID)memset_s((VOID *)fpage, sizeof(LosFilePage), 0, sizeof(LosFilePage));//调标准库函数 置0 - LOS_ListInit(&fpage->i_mmap); - LOS_ListInit(&fpage->node); - LOS_ListInit(&fpage->lru); - fpage->n_maps = 0; - fpage->dirtyOff = PAGE_SIZE; - fpage->dirtyEnd = 0; - fpage->physSeg = physSeg; - fpage->vmPage = vmPage; - fpage->mapping = mapping; - fpage->pgoff = pgoff; - (VOID)memset_s(kvaddr, PAGE_SIZE, 0, PAGE_SIZE); + LOS_ListInit(&fpage->i_mmap); //初始化映射,链表上挂 MapInfo + LOS_ListInit(&fpage->node); //节点初始化 + LOS_ListInit(&fpage->lru); //LRU初始化 + fpage->n_maps = 0; //映射次数 + fpage->dirtyOff = PAGE_SIZE; //默认页尾部,相当于没有脏数据 + fpage->dirtyEnd = 0; //脏页结束位置 + fpage->physSeg = physSeg; //页框所属段.其中包含了 LRU LIST == + fpage->vmPage = vmPage; //物理页框 + fpage->mapping = mapping; //记录所有文件页映射 + fpage->pgoff = pgoff; //将文件切成一页页,页标 + (VOID)memset_s(kvaddr, PAGE_SIZE, 0, PAGE_SIZE);//页内数据清0 return fpage; } @@ -609,4 +644,4 @@ INT32 OsVfsFileMmap(struct file *filep, LosVmMapRegion *region) } #endif -#endif +#endif \ No newline at end of file diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_iomap.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_iomap.c index a313da14..0d076a53 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_iomap.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_iomap.c @@ -1,3 +1,42 @@ +/* + 直接内存访问 + 直接内存访问(Direct Memory Access,DMA)是计算机科学中的一种内存访问技术。它允许某些电脑内部的 + 硬件子系统(电脑外设),可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理 + 在同等程度的处理器负担下,DMA是一种快速的数据传送方式。很多硬件的系统会使用DMA,包含硬盘控制器、 + 绘图显卡、网卡和声卡。 + + DMA是所有现代电脑的重要特色,它允许不同速度的硬件设备来沟通,而不需要依于中央处理器的大量中断负载。 + 否则,中央处理器需要从来源把每一片段的资料复制到寄存器,然后把它们再次写回到新的地方。在这个时间中, + 中央处理器对于其他的工作来说就无法使用。 + + DMA传输常使用在将一个内存区从一个设备复制到另外一个。当中央处理器初始化这个传输动作,传输动作本身是 + 由DMA控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存去。像是这样的操作 + 并没有让处理器工作拖延,使其可以被重新调度去处理其他的工作。DMA传输对于高性能嵌入式系统算法和网络是 + 很重要的。 举个例子,个人电脑的ISA DMA控制器拥有8个DMA通道,其中的7个通道是可以让计算机的中央处理器所利用。 + 每一个DMA通道有一个16位地址寄存器和一个16位计数寄存器。要初始化资料传输时,设备驱动程序一起设置DMA通道的 + 地址和计数寄存器,以及资料传输的方向,读取或写入。然后指示DMA硬件开始这个传输动作。当传输结束的时候, + 设备就会以中断的方式通知中央处理器。 + + "分散-收集"(Scatter-gather)DMA允许在一次单一的DMA处理中传输资料到多个内存区域。相当于把多个简单的DMA要求 + 串在一起。同样,这样做的目的是要减轻中央处理器的多次输出输入中断和资料复制任务。 + DRQ意为DMA要求;DACK意为DMA确认。这些符号一般在有DMA功能的电脑系统硬件概要上可以看到。 + 它们表示了介于中央处理器和DMA控制器之间的电子信号传输线路。 + + 缓存一致性问题 + DMA会导致缓存一致性问题。想像中央处理器带有缓存与外部内存的情况,DMA的运作则是去访问外部内存, + 当中央处理器访问外部内存某个地址的时候,暂时先将新的值写入缓存中,但并未将外部内存的资料更新, + 若在缓存中的资料尚未更新到外部内存前发生了DMA,则DMA过程将会读取到未更新的资料。 + 相同的,如果外部设备写入新的值到外部内存内,则中央处理器若访问缓存时则会访问到尚未更新的资料。 + 这些问题可以用两种方法来解决: + + 缓存同调系统(Cache-coherent system):以硬件方法来完成,当外部设备写入内存时以一个信号来通知 + 缓存控制器某内存地址的值已经过期或是应该更新资料。 + 非同调系统(Non-coherent system):以软件方法来完成,操作系统必须确认缓存读取时,DMA程序已经 + 开始或是禁止DMA发生。 + 第二种的方法会造成DMA的系统负担。 + */ + + /* * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved. * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved. @@ -36,7 +75,7 @@ #include "los_vm_map.h" #include "los_memory.h" - +///分配DMA空间 VOID *LOS_DmaMemAlloc(DMA_ADDR_T *dmaAddr, size_t size, size_t align, enum DmaMemType type) { VOID *kVaddr = NULL; @@ -53,24 +92,24 @@ VOID *LOS_DmaMemAlloc(DMA_ADDR_T *dmaAddr, size_t size, size_t align, enum DmaMe #ifdef LOSCFG_KERNEL_VM kVaddr = LOS_KernelMallocAlign(size, align); #else - kVaddr = LOS_MemAllocAlign(OS_SYS_MEM_ADDR, size, align); + kVaddr = LOS_MemAllocAlign(OS_SYS_MEM_ADDR, size, align);//不走内存池方式, 直接申请物理页 #endif if (kVaddr == NULL) { - VM_ERR("failed, size = %u, align = %u", size, align); + VM_ERR("failed, size = %u, align = %u", size, align);//从内存池中申请 return NULL; } if (dmaAddr != NULL) { - *dmaAddr = (DMA_ADDR_T)LOS_PaddrQuery(kVaddr); + *dmaAddr = (DMA_ADDR_T)LOS_PaddrQuery(kVaddr);//查询物理地址, DMA直接将数据灌到物理地址 } - if (type == DMA_NOCACHE) { + if (type == DMA_NOCACHE) {//无缓存模式 , 计算新的虚拟地址 kVaddr = (VOID *)VMM_TO_UNCACHED_ADDR((UINTPTR)kVaddr); } return kVaddr; } - +///释放DMA指针 VOID LOS_DmaMemFree(VOID *vaddr) { UINTPTR addr; @@ -79,13 +118,13 @@ VOID LOS_DmaMemFree(VOID *vaddr) return; } addr = (UINTPTR)vaddr; - + // 未缓存区 if ((addr >= UNCACHED_VMM_BASE) && (addr < UNCACHED_VMM_BASE + UNCACHED_VMM_SIZE)) { - addr = UNCACHED_TO_VMM_ADDR(addr); + addr = UNCACHED_TO_VMM_ADDR(addr);//转换成未缓存区地址 #ifdef LOSCFG_KERNEL_VM LOS_KernelFree((VOID *)addr); #else - LOS_MemFree(OS_SYS_MEM_ADDR, (VOID *)addr); + LOS_MemFree(OS_SYS_MEM_ADDR, (VOID *)addr);//内存池方式释放 #endif } else if ((addr >= KERNEL_VMM_BASE) && (addr < KERNEL_VMM_BASE + KERNEL_VMM_SIZE)) { #ifdef LOSCFG_KERNEL_VM diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_map.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_map.c index 5d4f62b3..4aeef716 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_map.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_map.c @@ -1,3 +1,43 @@ +/* +基本概念 + 虚拟内存管理是计算机系统管理内存的一种技术。每个进程都有连续的虚拟地址空间,虚拟地址空间的大小由CPU的位数决定, + 32位的硬件平台可以提供的最大的寻址空间为0-4GiB。整个4GiB空间分成两部分,LiteOS-A内核占据3GiB的高地址空间, + 1GiB的低地址空间留给进程使用。各个进程空间的虚拟地址空间是独立的,代码、数据互不影响。 + + 系统将虚拟内存分割为称为虚拟页的内存块,大小一般为4KiB或64KiB,LiteOS-A内核默认的页的大小是4KiB, + 根据需要可以对MMU(Memory Management Units)进行配置。虚拟内存管理操作的最小单位就是一个页, + LiteOS-A内核中一个虚拟地址区间region包含地址连续的多个虚拟页,也可只有一个页。同样,物理内存也会按照页大小进行分割, + 分割后的每个内存块称为页帧。虚拟地址空间划分:内核态占高地址3GiB(0x40000000 ~ 0xFFFFFFFF), + 用户态占低地址1GiB(0x01000000 ~ 0x3F000000),具体见下表,详细可以查看或配置los_vm_zone.h。 + +内核态地址规划: + Zone名称 描述 属性 + ---------------------------------------------------------------------------- + DMA zone 供IO设备的DMA使用。 Uncache + + Normal zone 加载内核代码段、数据段、堆和栈的地址区间。 Cache + + high mem zone可以分配连续的虚拟内存,但其所映射的物理内存不一定连续。Cache + +用户态地址规划: + Zone名称 描述 属性 + ---------------------------------------------------------------------------- + 代码段 用户态代码段地址区间。 Cache + 堆 用户态堆地址区间。 Cache + 栈 用户态栈地址区间。 Cache + 共享库 用于加载用户态共享库的地址区间,包括mmap所映射的区间。 Cache + +运行机制 + 虚拟内存管理中,虚拟地址空间是连续的,但是其映射的物理内存并不一定是连续的,如下图所示。 + 可执行程序加载运行,CPU访问虚拟地址空间的代码或数据时存在两种情况: + + 1. CPU访问的虚拟地址所在的页,如V0,已经与具体的物理页P0做映射,CPU通过找到进程对应的页表条目(详见虚实映射), + 根据页表条目中的物理地址信息访问物理内存中的内容并返回。 + 2. CPU访问的虚拟地址所在的页,如V2,没有与具体的物理页做映射,系统会触发缺页异常,系统申请一个物理页, + 并把相应的信息拷贝到物理页中,并且把物理页的起始地址更新到页表条目中。此时CPU重新执行访问虚拟内存的指令 + 便能够访问到具体的代码或数据。 + */ + /* * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved. * Copyright (c) 2020-2023 Huawei Device Co., Ltd. All rights reserved. @@ -51,57 +91,65 @@ #ifdef LOSCFG_KERNEL_VM -#define VM_MAP_WASTE_MEM_LEVEL (PAGE_SIZE >> 2) -LosMux g_vmSpaceListMux; -LOS_DL_LIST_HEAD(g_vmSpaceList); -LosVmSpace g_kVmSpace; -LosVmSpace g_vMallocSpace; +#define VM_MAP_WASTE_MEM_LEVEL (PAGE_SIZE >> 2) ///< 浪费内存大小(1K) +LosMux g_vmSpaceListMux; ///< 用于锁g_vmSpaceList的互斥量 +LOS_DL_LIST_HEAD(g_vmSpaceList); ///< 初始化全局虚拟空间节点,所有虚拟空间都挂到此节点上. +LosVmSpace g_kVmSpace; ///< 内核非分配空间,用于内核运行栈,代码区,数据区 +LosVmSpace g_vMallocSpace; ///< 内核分配空间,用于内核分配内存 +/************************************************************ +* 获取进程空间系列接口 +************************************************************/ +/// 获取当前进程空间结构体指针 LosVmSpace *LOS_CurrSpaceGet(VOID) { return OsCurrProcessGet()->vmSpace; } - +/// 获取虚拟地址对应的进程空间结构体指针 LosVmSpace *LOS_SpaceGet(VADDR_T vaddr) { - if (LOS_IsKernelAddress(vaddr)) { - return LOS_GetKVmSpace(); - } else if (LOS_IsUserAddress(vaddr)) { + if (LOS_IsKernelAddress(vaddr)) { //是否为内核空间 + return LOS_GetKVmSpace(); //获取内核空间 + } else if (LOS_IsUserAddress(vaddr)) {//是否为用户空间 return LOS_CurrSpaceGet(); - } else if (LOS_IsVmallocAddress(vaddr)) { - return LOS_GetVmallocSpace(); + } else if (LOS_IsVmallocAddress(vaddr)) {//是否为内核分配空间 + return LOS_GetVmallocSpace();//获取内核分配空间 } else { return NULL; } } - +///内核空间只有g_kVmSpace一个,所有的内核进程都共用一个内核空间 LosVmSpace *LOS_GetKVmSpace(VOID) { return &g_kVmSpace; } - +///获取进程空间链表指针 g_vmSpaceList中挂的是进程空间 g_kVmSpace, g_vMallocSpace,所有用户进程的空间(独有一个进程空间) LOS_DL_LIST *LOS_GetVmSpaceList(VOID) { return &g_vmSpaceList; } - +///获取内核堆空间的全局变量 LosVmSpace *LOS_GetVmallocSpace(VOID) { return &g_vMallocSpace; } +/************************************************************ +* 虚拟地址区间region相关的操作 +************************************************************/ +///释放挂在红黑树上节点,等于释放了线性区 ULONG_T OsRegionRbFreeFn(LosRbNode *pstNode) { LOS_MemFree(m_aucSysMem0, pstNode); return LOS_OK; } - +///通过红黑树节点找到对应的线性区 VOID *OsRegionRbGetKeyFn(LosRbNode *pstNode) { LosVmMapRegion *region = (LosVmMapRegion *)LOS_DL_LIST_ENTRY(pstNode, LosVmMapRegion, rbNode); return (VOID *)®ion->range; } - +///比较两个红黑树节点 ULONG_T OsRegionRbCmpKeyFn(const VOID *pNodeKeyA, const VOID *pNodeKeyB) { LosVmMapRange rangeA = *(LosVmMapRange *)pNodeKeyA; @@ -111,129 +159,146 @@ ULONG_T OsRegionRbCmpKeyFn(const VOID *pNodeKeyA, const VOID *pNodeKeyB) UINT32 startB = rangeB.base; UINT32 endB = rangeB.base + rangeB.size - 1; - if (startA > endB) { - return RB_BIGGER; + if (startA > endB) {// A基地址大于B的结束地址 + return RB_BIGGER; //说明线性区A更大,在右边 } else if (startA >= startB) { if (endA <= endB) { - return RB_EQUAL; + return RB_EQUAL; //相等,说明 A在B中 } else { - return RB_BIGGER; + return RB_BIGGER; //说明 A的结束地址更大 } - } else if (startA <= startB) { + } else if (startA <= startB) { //A基地址小于等于B的基地址 if (endA >= endB) { - return RB_EQUAL; + return RB_EQUAL; //相等 说明 B在A中 } else { - return RB_SMALLER; + return RB_SMALLER;//说明A的结束地址更小 } - } else if (endA < startB) { - return RB_SMALLER; + } else if (endA < startB) {//A结束地址小于B的开始地址 + return RB_SMALLER;//说明A在 } return RB_EQUAL; } +/*! + * @brief OsVmSpaceInitCommon 初始化进程虚拟空间,必须提供L1表的虚拟内存地址 + * + * @param virtTtb L1表的地址,TTB表地址 + * @param vmSpace + * @return + * + * @see + */ STATIC BOOL OsVmSpaceInitCommon(LosVmSpace *vmSpace, VADDR_T *virtTtb) { - LOS_RbInitTree(&vmSpace->regionRbTree, OsRegionRbCmpKeyFn, OsRegionRbFreeFn, OsRegionRbGetKeyFn); + LOS_RbInitTree(&vmSpace->regionRbTree, OsRegionRbCmpKeyFn, OsRegionRbFreeFn, OsRegionRbGetKeyFn);//初始化虚拟存储空间-以红黑树组织方式 - status_t retval = LOS_MuxInit(&vmSpace->regionMux, NULL); + 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); + LOS_ListAdd(&g_vmSpaceList, &vmSpace->node);//将虚拟空间挂入全局虚拟空间双循环链表上 (VOID)LOS_MuxRelease(&g_vmSpaceListMux); - return OsArchMmuInit(&vmSpace->archMmu, virtTtb); + return OsArchMmuInit(&vmSpace->archMmu, virtTtb);//对mmu初始化 } - +///@note_thinking 这个函数名称和内容不太搭 VOID OsVmMapInit(VOID) { - status_t retval = LOS_MuxInit(&g_vmSpaceListMux, NULL); + status_t retval = LOS_MuxInit(&g_vmSpaceListMux, NULL);//初始化虚拟空间的互斥量 if (retval != LOS_OK) { VM_ERR("Create mutex for g_vmSpaceList failed, status: %d", retval); } } - -BOOL OsKernVmSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb) +///初始化内核虚拟空间 +BOOL OsKernVmSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)//内核空间页表是编译时放在bbs段指定的,共用 L1表 { - vmSpace->base = KERNEL_ASPACE_BASE; - vmSpace->size = KERNEL_ASPACE_SIZE; - vmSpace->mapBase = KERNEL_VMM_BASE; - vmSpace->mapSize = KERNEL_VMM_SIZE; + 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; + vmSpace->codeStart = 0; //代码区开始地址 + vmSpace->codeEnd = 0; //代码区结束地址 #endif - return OsVmSpaceInitCommon(vmSpace, virtTtb); + return OsVmSpaceInitCommon(vmSpace, virtTtb);//virtTtb 用于初始化 mmu } - -BOOL OsVMallocSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb) +///初始化内核堆空间 +BOOL OsVMallocSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)//内核动态空间的页表是动态申请得来,共用 L1表 { - vmSpace->base = VMALLOC_START; - vmSpace->size = VMALLOC_SIZE; - vmSpace->mapBase = VMALLOC_START; - vmSpace->mapSize = VMALLOC_SIZE; + 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); + return OsVmSpaceInitCommon(vmSpace, virtTtb);//创建MMU,为后续的虚实映射做好初始化的工作 } - +///内核虚拟空间初始化 VOID OsKSpaceInit(VOID) { - OsVmMapInit(); - OsKernVmSpaceInit(&g_kVmSpace, OsGFirstTableGet()); - OsVMallocSpaceInit(&g_vMallocSpace, OsGFirstTableGet()); + OsVmMapInit();//初始化后续操作 g_vmSpaceList 的互斥锁 + OsKernVmSpaceInit(&g_kVmSpace, OsGFirstTableGet()); //初始化内核进程虚拟空间 + OsVMallocSpaceInit(&g_vMallocSpace, OsGFirstTableGet());//初始化内核动态分配空间 } - +/*! + * @brief OsUserVmSpaceInit 用户空间的TTB表是动态申请得来,每个进程有属于自己的L1,L2表 + * 初始化用户进程虚拟空间,主要划分数据区,堆区,映射区和创建mmu + * @param virtTtb + * @param vmSpace + * @return + * + * @see + */ 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; + 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;//堆区最新指向地址,用户堆空间大小可通过系统调用 do_brk()扩展 + vmSpace->heap = NULL; //最近分配的一个堆线性区 #ifdef LOSCFG_DRIVERS_TZDRIVER vmSpace->codeStart = 0; vmSpace->codeEnd = 0; #endif - return OsVmSpaceInitCommon(vmSpace, virtTtb); + return OsVmSpaceInitCommon(vmSpace, virtTtb);//创建MMU,为后续的虚实映射做好初始化的工作 } - +/// 创建用户进程空间 LosVmSpace *OsCreateUserVmSpace(VOID) { BOOL retVal = FALSE; - LosVmSpace *space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace)); + LosVmSpace *space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace));//在内核空间申请用户进程空间 if (space == NULL) { return NULL; } - - VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1); - if (ttb == NULL) { + //此处为何直接申请物理页帧存放用户进程的页表,大概是因为所有页表都被存放在内核空间(g_kVmSpace)而非内核分配空间(g_vMallocSpace) + VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);//分配一个物理页用于存放虚实映射关系表, 即:L1表 + if (ttb == NULL) {//若连页表都没有,剩下的也别玩了. (VOID)LOS_MemFree(m_aucSysMem0, space); return NULL; } - - (VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE); - retVal = OsUserVmSpaceInit(space, ttb); - LosVmPage *vmPage = OsVmVaddrToPage(ttb); + + (VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);//4K空间置0 + retVal = OsUserVmSpaceInit(space, ttb);//初始化用户空间,mmu + LosVmPage *vmPage = OsVmVaddrToPage(ttb);//找到所在物理页框 if ((retVal == FALSE) || (vmPage == NULL)) { (VOID)LOS_MemFree(m_aucSysMem0, space); LOS_PhysPagesFreeContiguous(ttb, 1); return NULL; } - LOS_ListAdd(&space->archMmu.ptList, &(vmPage->node)); + LOS_ListAdd(&space->archMmu.ptList, &(vmPage->node));//页表链表,先挂上L1,后续还会挂上 N个L2表 return space; } -STATIC BOOL OsVmSpaceParamCheck(const LosVmSpace *vmSpace) +STATIC BOOL OsVmSpaceParamCheck(const LosVmSpace *vmSpace)//这么简单也要写个函数? { if (vmSpace == NULL) { return FALSE; @@ -241,6 +306,7 @@ STATIC BOOL OsVmSpaceParamCheck(const LosVmSpace *vmSpace) return TRUE; } +//虚拟内存空间克隆,被用于fork进程 STATUS_T LOS_VmSpaceClone(UINT32 cloneFlags, LosVmSpace *oldVmSpace, LosVmSpace *newVmSpace) { LosRbNode *pstRbNode = NULL; @@ -255,23 +321,23 @@ STATUS_T LOS_VmSpaceClone(UINT32 cloneFlags, LosVmSpace *oldVmSpace, LosVmSpace return LOS_ERRNO_VM_INVALID_ARGS; } - if ((OsIsVmRegionEmpty(oldVmSpace) == TRUE) || (oldVmSpace == &g_kVmSpace)) { + if ((OsIsVmRegionEmpty(oldVmSpace) == TRUE) || (oldVmSpace == &g_kVmSpace)) {//不允许clone内核空间,内核空间是独一无二的. return LOS_ERRNO_VM_INVALID_ARGS; } - + //空间克隆的主体实现是:线性区重新一个个分配物理内存,重新映射. /* search the region list */ - newVmSpace->mapBase = oldVmSpace->mapBase; - newVmSpace->heapBase = oldVmSpace->heapBase; - newVmSpace->heapNow = oldVmSpace->heapNow; + newVmSpace->mapBase = oldVmSpace->mapBase; //复制映射区基址 + newVmSpace->heapBase = oldVmSpace->heapBase; //复制堆区基址 + newVmSpace->heapNow = oldVmSpace->heapNow; //复制堆区当前使用到哪了 (VOID)LOS_MuxAcquire(&oldVmSpace->regionMux); - RB_SCAN_SAFE(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext) + RB_SCAN_SAFE(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext)//红黑树循环开始 LosVmMapRegion *oldRegion = (LosVmMapRegion *)pstRbNode; #if defined(LOSCFG_KERNEL_SHM) && defined(LOSCFG_IPC_CONTAINER) if ((oldRegion->regionFlags & VM_MAP_REGION_FLAG_SHM) && (cloneFlags & CLONE_NEWIPC)) { continue; } #endif - LosVmMapRegion *newRegion = OsVmRegionDup(newVmSpace, oldRegion, oldRegion->range.base, oldRegion->range.size); + LosVmMapRegion *newRegion = OsVmRegionDup(newVmSpace, oldRegion, oldRegion->range.base, oldRegion->range.size);//复制线性区 if (newRegion == NULL) { VM_ERR("dup new region failed"); ret = LOS_ERRNO_VM_NO_MEMORY; @@ -279,50 +345,50 @@ STATUS_T LOS_VmSpaceClone(UINT32 cloneFlags, LosVmSpace *oldVmSpace, LosVmSpace } #ifdef LOSCFG_KERNEL_SHM - if (oldRegion->regionFlags & VM_MAP_REGION_FLAG_SHM) { - OsShmFork(newVmSpace, oldRegion, newRegion); - continue; + if (oldRegion->regionFlags & VM_MAP_REGION_FLAG_SHM) {//如果老线性区是共享内存 + OsShmFork(newVmSpace, oldRegion, newRegion);//fork共享线性区,如此新虚拟空间也能用那个线性区 + continue;//不往下走了,因为共享内存不需要重新映射,下面无非就是需要MMU映射虚拟地址<-->物理地址 } #endif - if (oldRegion == oldVmSpace->heap) { - newVmSpace->heap = newRegion; + if (oldRegion == oldVmSpace->heap) {//如果这个线性区是堆区 + newVmSpace->heap = newRegion;//那么新的线性区也是新虚拟空间的堆区 } - numPages = newRegion->range.size >> PAGE_SHIFT; - for (i = 0; i < numPages; i++) { + numPages = newRegion->range.size >> PAGE_SHIFT;//计算线性区页数 + for (i = 0; i < numPages; i++) {//一页一页进行重新映射 vaddr = newRegion->range.base + (i << PAGE_SHIFT); - if (LOS_ArchMmuQuery(&oldVmSpace->archMmu, vaddr, &paddr, &flags) != LOS_OK) { + if (LOS_ArchMmuQuery(&oldVmSpace->archMmu, vaddr, &paddr, &flags) != LOS_OK) {//先查物理地址 continue; } - page = LOS_VmPageGet(paddr); + page = LOS_VmPageGet(paddr);//通过物理页获取物理内存的页框 if (page != NULL) { - LOS_AtomicInc(&page->refCounts); + LOS_AtomicInc(&page->refCounts);//refCounts 自增 } - if (flags & VM_MAP_REGION_FLAG_PERM_WRITE) { - LOS_ArchMmuUnmap(&oldVmSpace->archMmu, vaddr, 1); - LOS_ArchMmuMap(&oldVmSpace->archMmu, vaddr, paddr, 1, flags & ~VM_MAP_REGION_FLAG_PERM_WRITE); + if (flags & VM_MAP_REGION_FLAG_PERM_WRITE) {//可写入区标签 + LOS_ArchMmuUnmap(&oldVmSpace->archMmu, vaddr, 1);//先删除老空间映射 + LOS_ArchMmuMap(&oldVmSpace->archMmu, vaddr, paddr, 1, flags & ~VM_MAP_REGION_FLAG_PERM_WRITE);//老空间重新映射 } - LOS_ArchMmuMap(&newVmSpace->archMmu, vaddr, paddr, 1, flags & ~VM_MAP_REGION_FLAG_PERM_WRITE); + LOS_ArchMmuMap(&newVmSpace->archMmu, vaddr, paddr, 1, flags & ~VM_MAP_REGION_FLAG_PERM_WRITE);//映射新空间 -#ifdef LOSCFG_FS_VFS - if (LOS_IsRegionFileValid(oldRegion)) { +#ifdef LOSCFG_FS_VFS //文件系统开关 + if (LOS_IsRegionFileValid(oldRegion)) {//是都是一个文件映射线性区 LosFilePage *fpage = NULL; LOS_SpinLockSave(&oldRegion->unTypeData.rf.vnode->mapping.list_lock, &intSave); fpage = OsFindGetEntry(&oldRegion->unTypeData.rf.vnode->mapping, newRegion->pgOff + i); if ((fpage != NULL) && (fpage->vmPage == page)) { /* cow page no need map */ - OsAddMapInfo(fpage, &newVmSpace->archMmu, vaddr); + OsAddMapInfo(fpage, &newVmSpace->archMmu, vaddr);//添加文件页映射,记录页面被进程映射过 } LOS_SpinUnlockRestore(&oldRegion->unTypeData.rf.vnode->mapping.list_lock, intSave); } #endif } - RB_SCAN_SAFE_END(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext) + RB_SCAN_SAFE_END(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext)//红黑树循环结束 (VOID)LOS_MuxRelease(&oldVmSpace->regionMux); return ret; } - +///通过虚拟(线性)地址查找所属线性区,红黑树 LosVmMapRegion *OsFindRegion(LosRbTree *regionRbTree, VADDR_T vaddr, size_t len) { LosVmMapRegion *regionRst = NULL; @@ -336,18 +402,18 @@ LosVmMapRegion *OsFindRegion(LosRbTree *regionRbTree, VADDR_T vaddr, size_t len) } return regionRst; } - +/// 查找线性区 根据起始地址在进程空间内查找是否存在 LosVmMapRegion *LOS_RegionFind(LosVmSpace *vmSpace, VADDR_T addr) { LosVmMapRegion *region = NULL; - (VOID)LOS_MuxAcquire(&vmSpace->regionMux); + (VOID)LOS_MuxAcquire(&vmSpace->regionMux);//因进程空间是隔离的,所以此处只会涉及到任务(线程)之间的竞争,故使用互斥锁,而自旋锁则用于CPU核间的竞争 region = OsFindRegion(&vmSpace->regionRbTree, addr, 1); (VOID)LOS_MuxRelease(&vmSpace->regionMux); return region; } - +/// 查找线性区 根据地址区间在进程空间内查找是否存在 LosVmMapRegion *LOS_RegionRangeFind(LosVmSpace *vmSpace, VADDR_T addr, size_t len) { LosVmMapRegion *region = NULL; @@ -358,14 +424,14 @@ LosVmMapRegion *LOS_RegionRangeFind(LosVmSpace *vmSpace, VADDR_T addr, size_t le return region; } - +/// 分配指定长度的线性区 VADDR_T OsAllocRange(LosVmSpace *vmSpace, size_t len) { LosVmMapRegion *curRegion = NULL; LosRbNode *pstRbNode = NULL; LosRbNode *pstRbNodeTmp = NULL; LosRbTree *regionRbTree = &vmSpace->regionRbTree; - VADDR_T curEnd = vmSpace->mapBase; + VADDR_T curEnd = vmSpace->mapBase;//获取映射区基地址 VADDR_T nextStart; curRegion = LOS_RegionFind(vmSpace, vmSpace->mapBase); @@ -384,7 +450,7 @@ VADDR_T OsAllocRange(LosVmSpace *vmSpace, size_t len) curEnd = curRegion->range.base + curRegion->range.size; } RB_MID_SCAN_END(regionRbTree, pstRbNode) - } else { + } else {//红黑树扫描排序,从小到大 /* rbtree scan is sorted, from small to big */ RB_SCAN_SAFE(regionRbTree, pstRbNode, pstRbNodeTmp) curRegion = (LosVmMapRegion *)pstRbNode; @@ -407,34 +473,34 @@ VADDR_T OsAllocRange(LosVmSpace *vmSpace, size_t len) return 0; } - +/// 分配指定开始地址和长度的线性区 VADDR_T OsAllocSpecificRange(LosVmSpace *vmSpace, VADDR_T vaddr, size_t len, UINT32 regionFlags) { STATUS_T status; - if (LOS_IsRangeInSpace(vmSpace, vaddr, len) == FALSE) { + if (LOS_IsRangeInSpace(vmSpace, vaddr, len) == FALSE) {//虚拟地址是否在进程空间范围内 return 0; } if ((LOS_RegionFind(vmSpace, vaddr) != NULL) || (LOS_RegionFind(vmSpace, vaddr + len - 1) != NULL) || - (LOS_RegionRangeFind(vmSpace, vaddr, len - 1) != NULL)) { + (LOS_RegionRangeFind(vmSpace, vaddr, len - 1) != NULL)) {//没找到的情况 if ((regionFlags & VM_MAP_REGION_FLAG_FIXED_NOREPLACE) != 0) { return 0; - } else if ((regionFlags & VM_MAP_REGION_FLAG_FIXED) != 0) { - status = LOS_UnMMap(vaddr, len); + } else if ((regionFlags & VM_MAP_REGION_FLAG_FIXED) != 0) {//线性区未填满,则解除这部分空间的映射 + status = LOS_UnMMap(vaddr, len);//解除映射 if (status != LOS_OK) { VM_ERR("unmap specific range va: %#x, len: %#x failed, status: %d", vaddr, len, status); return 0; } } else { - return OsAllocRange(vmSpace, len); + return OsAllocRange(vmSpace, len);//默认分配一个 } } return vaddr; } - +///映射类型为文件的线性区是否有效 BOOL LOS_IsRegionFileValid(LosVmMapRegion *region) { if ((region != NULL) && (LOS_IsRegionTypeFile(region)) && @@ -443,7 +509,7 @@ BOOL LOS_IsRegionFileValid(LosVmMapRegion *region) } return FALSE; } - +///向红黑树中插入线性区 BOOL OsInsertRegion(LosRbTree *regionRbTree, LosVmMapRegion *region) { if (LOS_RbAddNode(regionRbTree, (LosRbNode *)region) == FALSE) { @@ -453,46 +519,46 @@ BOOL OsInsertRegion(LosRbTree *regionRbTree, LosVmMapRegion *region) } return TRUE; } - +///创建一个线性区 LosVmMapRegion *OsCreateRegion(VADDR_T vaddr, size_t len, UINT32 regionFlags, unsigned long offset) { - LosVmMapRegion *region = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmMapRegion)); + LosVmMapRegion *region = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmMapRegion));//只是分配一个线性区结构体 if (region == NULL) { VM_ERR("memory allocate for LosVmMapRegion failed"); return region; } - + //创建线性区的本质就是在画饼,见如下操作: (void)memset_s(region, sizeof(LosVmMapRegion), 0, sizeof(LosVmMapRegion)); - region->range.base = vaddr; - region->range.size = len; - region->pgOff = offset; - region->regionFlags = regionFlags; - region->regionType = VM_MAP_REGION_TYPE_NONE; - region->forkFlags = 0; - region->shmid = -1; + region->range.base = vaddr; //虚拟地址作为线性区的基地址 + region->range.size = len; //线性区大小,这是线性区构思最巧妙的地方,只要不过分,蓝图随便画。 + region->pgOff = offset; //页标 + region->regionFlags = regionFlags;//标识,可读/可写/可执行 + region->regionType = VM_MAP_REGION_TYPE_NONE;//未映射 + region->forkFlags = 0; // + region->shmid = -1; //默认线性区为不共享,无共享资源ID return region; } - +///通过虚拟地址查询映射的物理地址 PADDR_T LOS_PaddrQuery(VOID *vaddr) { PADDR_T paddr = 0; STATUS_T status; LosVmSpace *space = NULL; LosArchMmu *archMmu = NULL; - - if (LOS_IsKernelAddress((VADDR_T)(UINTPTR)vaddr)) { + //先取出对应空间的mmu + if (LOS_IsKernelAddress((VADDR_T)(UINTPTR)vaddr)) {//是否内核空间地址 archMmu = &g_kVmSpace.archMmu; - } else if (LOS_IsUserAddress((VADDR_T)(UINTPTR)vaddr)) { + } else if (LOS_IsUserAddress((VADDR_T)(UINTPTR)vaddr)) {//是否为用户空间地址 space = OsCurrProcessGet()->vmSpace; archMmu = &space->archMmu; - } else if (LOS_IsVmallocAddress((VADDR_T)(UINTPTR)vaddr)) { + } else if (LOS_IsVmallocAddress((VADDR_T)(UINTPTR)vaddr)) {//是否为分配空间地址,堆区地址 archMmu = &g_vMallocSpace.archMmu; } else { VM_ERR("vaddr is beyond range"); return 0; } - status = LOS_ArchMmuQuery(archMmu, (VADDR_T)(UINTPTR)vaddr, &paddr, 0); + status = LOS_ArchMmuQuery(archMmu, (VADDR_T)(UINTPTR)vaddr, &paddr, 0);//查询物理地址 if (status == LOS_OK) { return paddr; } else { @@ -500,6 +566,10 @@ PADDR_T LOS_PaddrQuery(VOID *vaddr) } } +/*! + * 这里不是真的分配物理内存,而是逻辑上画一个连续的区域,标记这个区域可以拿用,表示内存已经归你了。 + 但真正的物理内存的占用会延迟到使用的时候才由缺页中断调入内存 +*/ LosVmMapRegion *LOS_RegionAlloc(LosVmSpace *vmSpace, VADDR_T vaddr, size_t len, UINT32 regionFlags, VM_OFFSET_T pgoff) { VADDR_T rstVaddr; @@ -510,37 +580,40 @@ LosVmMapRegion *LOS_RegionAlloc(LosVmSpace *vmSpace, VADDR_T vaddr, size_t len, * this is the most portable method of creating a new mapping. If addr is not NULL, * then the kernel takes it as where to place the mapping; */ - (VOID)LOS_MuxAcquire(&vmSpace->regionMux); - if (vaddr == 0) { + (VOID)LOS_MuxAcquire(&vmSpace->regionMux);//获得互斥锁 + if (vaddr == 0) {//如果地址是0,根据线性区管理的实际情况,自动创建虚拟地址, 这是创建新映射的最便捷的方法。 rstVaddr = OsAllocRange(vmSpace, len); } else { - /* if it is already mmapped here, we unmmap it */ - rstVaddr = OsAllocSpecificRange(vmSpace, vaddr, len, regionFlags); + /* if it is already mmapped here, we unmmap it | 如果已经被映射了, 则解除映射关系*/ + rstVaddr = OsAllocSpecificRange(vmSpace, vaddr, len, regionFlags);//创建包含指定虚拟地址的线性区, rstVaddr != vaddr || rstVaddr == vaddr if (rstVaddr == 0) { VM_ERR("alloc specific range va: %#x, len: %#x failed", vaddr, len); goto OUT; } } - if (rstVaddr == 0) { + if (rstVaddr == 0) {//没有可供映射的虚拟地址 goto OUT; } - newRegion = OsCreateRegion(rstVaddr, len, regionFlags, pgoff); + newRegion = OsCreateRegion(rstVaddr, len, regionFlags, pgoff);//创建一个线性区,指定线性区的开始地址rstVaddr ... if (newRegion == NULL) { goto OUT; } newRegion->space = vmSpace; - isInsertSucceed = OsInsertRegion(&vmSpace->regionRbTree, newRegion); - if (isInsertSucceed == FALSE) { - (VOID)LOS_MemFree(m_aucSysMem0, newRegion); + isInsertSucceed = OsInsertRegion(&vmSpace->regionRbTree, newRegion);//插入红黑树和双循环链表中管理 + if (isInsertSucceed == FALSE) {//插入失败 + (VOID)LOS_MemFree(m_aucSysMem0, newRegion);//从内存池中释放 newRegion = NULL; } OUT: - (VOID)LOS_MuxRelease(&vmSpace->regionMux); + (VOID)LOS_MuxRelease(&vmSpace->regionMux);//释放互斥锁 return newRegion; } - +/*! + * 删除匿名页,匿名页就是内存映射页 + * 1.解除映射关系 2.释放物理内存 +*/ STATIC VOID OsAnonPagesRemove(LosArchMmu *archMmu, VADDR_T vaddr, UINT32 count) { status_t status; @@ -552,20 +625,20 @@ STATIC VOID OsAnonPagesRemove(LosArchMmu *archMmu, VADDR_T vaddr, UINT32 count) return; } - while (count > 0) { + while (count > 0) {//一页页操作 count--; - status = LOS_ArchMmuQuery(archMmu, vaddr, &paddr, NULL); - if (status != LOS_OK) { + status = LOS_ArchMmuQuery(archMmu, vaddr, &paddr, NULL);//通过虚拟地址拿到物理地址 + if (status != LOS_OK) {//失败,拿下一页的物理地址 vaddr += PAGE_SIZE; continue; } - LOS_ArchMmuUnmap(archMmu, vaddr, 1); + LOS_ArchMmuUnmap(archMmu, vaddr, 1);//解除一页的映射 - page = LOS_VmPageGet(paddr); - if (page != NULL) { - if (!OsIsPageShared(page)) { - LOS_PhysPageFree(page); + page = LOS_VmPageGet(paddr);//通过物理地址获取所在物理页框的起始地址 + if (page != NULL) {//获取成功 + if (!OsIsPageShared(page)) {//不是共享页,共享页会有专门的共享标签,共享本质是有无多个进程对该页的引用 + LOS_PhysPageFree(page);//释放物理页框 } } vaddr += PAGE_SIZE; @@ -609,7 +682,7 @@ STATIC VOID OsFilePagesRemove(LosVmSpace *space, LosVmMapRegion *region) } } #endif - +/// 释放进程空间指定线性区 STATUS_T LOS_RegionFree(LosVmSpace *space, LosVmMapRegion *region) { if ((space == NULL) || (region == NULL)) { @@ -619,35 +692,34 @@ STATUS_T LOS_RegionFree(LosVmSpace *space, LosVmMapRegion *region) (VOID)LOS_MuxAcquire(&space->regionMux); -#ifdef LOSCFG_FS_VFS - if (LOS_IsRegionFileValid(region)) { - OsFilePagesRemove(space, region); +#ifdef LOSCFG_FS_VFS //文件开关 + if (LOS_IsRegionFileValid(region)) {//是否为文件线性区 + OsFilePagesRemove(space, region);//删除文件页 VnodeHold(); region->unTypeData.rf.vnode->useCount--; VnodeDrop(); } else #endif - -#ifdef LOSCFG_KERNEL_SHM - if (OsIsShmRegion(region)) { - OsShmRegionFree(space, region); +#ifdef LOSCFG_KERNEL_SHM //共享内存开关 + if (OsIsShmRegion(region)) { //是否为共享内存线性区 + OsShmRegionFree(space, region);//释放共享线性区 } else if (LOS_IsRegionTypeDev(region)) { #else - if (LOS_IsRegionTypeDev(region)) { + if (LOS_IsRegionTypeDev(region)) {//如果是设备线性区 #endif - OsDevPagesRemove(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT); + OsDevPagesRemove(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);//删除映射设备 } else { - OsAnonPagesRemove(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT); + OsAnonPagesRemove(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);//删除匿名映射 } /* remove it from space */ - LOS_RbDelNode(&space->regionRbTree, ®ion->rbNode); + LOS_RbDelNode(&space->regionRbTree, ®ion->rbNode);//从红黑树中删除线性区 /* free it */ - LOS_MemFree(m_aucSysMem0, region); + LOS_MemFree(m_aucSysMem0, region);//释放线性区结构体占用的内存 (VOID)LOS_MuxRelease(&space->regionMux); return LOS_OK; } - +/// 复制线性区 LosVmMapRegion *OsVmRegionDup(LosVmSpace *space, LosVmMapRegion *oldRegion, VADDR_T vaddr, size_t size) { LosVmMapRegion *newRegion = NULL; @@ -655,31 +727,31 @@ LosVmMapRegion *OsVmRegionDup(LosVmSpace *space, LosVmMapRegion *oldRegion, VADD (VOID)LOS_MuxAcquire(&space->regionMux); regionFlags = oldRegion->regionFlags; - if (vaddr == 0) { - regionFlags &= ~(VM_MAP_REGION_FLAG_FIXED | VM_MAP_REGION_FLAG_FIXED_NOREPLACE); + if (vaddr == 0) {//不指定地址 + regionFlags &= ~(VM_MAP_REGION_FLAG_FIXED | VM_MAP_REGION_FLAG_FIXED_NOREPLACE); //撕掉两个标签 } else { - regionFlags |= VM_MAP_REGION_FLAG_FIXED; + regionFlags |= VM_MAP_REGION_FLAG_FIXED; //贴上填满线性区标签 } - newRegion = LOS_RegionAlloc(space, vaddr, size, regionFlags, oldRegion->pgOff); + newRegion = LOS_RegionAlloc(space, vaddr, size, regionFlags, oldRegion->pgOff); //分配一个线性区 if (newRegion == NULL) { VM_ERR("LOS_RegionAlloc failed"); goto REGIONDUPOUT; } - newRegion->regionType = oldRegion->regionType; + newRegion->regionType = oldRegion->regionType;//复制线性区类型(文件,设备,匿名) #ifdef LOSCFG_KERNEL_SHM - if (OsIsShmRegion(oldRegion)) { - newRegion->shmid = oldRegion->shmid; + if (OsIsShmRegion(oldRegion)) {//如果是共享内存 + newRegion->shmid = oldRegion->shmid;//复制共享ID } #endif -#ifdef LOSCFG_FS_VFS - if (LOS_IsRegionTypeFile(oldRegion)) { - newRegion->unTypeData.rf.vmFOps = oldRegion->unTypeData.rf.vmFOps; - newRegion->unTypeData.rf.vnode = oldRegion->unTypeData.rf.vnode; - newRegion->unTypeData.rf.f_oflags = oldRegion->unTypeData.rf.f_oflags; +#ifdef LOSCFG_FS_VFS //文件开关 + if (LOS_IsRegionTypeFile(oldRegion)) {//如果是文件线性区 + newRegion->unTypeData.rf.vmFOps = oldRegion->unTypeData.rf.vmFOps; //文件操作接口 + newRegion->unTypeData.rf.vnode = oldRegion->unTypeData.rf.vnode; //文件索引节点 + newRegion->unTypeData.rf.f_oflags = oldRegion->unTypeData.rf.f_oflags;//读写标签 VnodeHold(); - newRegion->unTypeData.rf.vnode->useCount++; + newRegion->unTypeData.rf.vnode->useCount++;//索引节点使用数增加 VnodeDrop(); } #endif @@ -688,14 +760,14 @@ REGIONDUPOUT: (VOID)LOS_MuxRelease(&space->regionMux); return newRegion; } - +/// 劈开线性区 STATIC LosVmMapRegion *OsVmRegionSplit(LosVmMapRegion *oldRegion, VADDR_T newRegionStart) { LosVmMapRegion *newRegion = NULL; LosVmSpace *space = oldRegion->space; - size_t size = LOS_RegionSize(newRegionStart, LOS_RegionEndAddr(oldRegion)); + size_t size = LOS_RegionSize(newRegionStart, LOS_RegionEndAddr(oldRegion));//获取线性区大小 - oldRegion->range.size = LOS_RegionSize(oldRegion->range.base, newRegionStart - 1); + oldRegion->range.size = LOS_RegionSize(oldRegion->range.base, newRegionStart - 1);//获取旧线性区大小 if (oldRegion->range.size == 0) { LOS_RbDelNode(&space->regionRbTree, &oldRegion->rbNode); } @@ -710,14 +782,14 @@ STATIC LosVmMapRegion *OsVmRegionSplit(LosVmMapRegion *oldRegion, VADDR_T newReg #endif return newRegion; } - +///对线性区进行调整 STATUS_T OsVmRegionAdjust(LosVmSpace *space, VADDR_T newRegionStart, size_t size) { LosVmMapRegion *region = NULL; VADDR_T nextRegionBase = newRegionStart + size; LosVmMapRegion *newRegion = NULL; - region = LOS_RegionFind(space, newRegionStart); + region = LOS_RegionFind(space, newRegionStart);//先找到线性区 if ((region != NULL) && (newRegionStart > region->range.base)) { newRegion = OsVmRegionSplit(region, newRegionStart); if (newRegion == NULL) { @@ -737,7 +809,7 @@ STATUS_T OsVmRegionAdjust(LosVmSpace *space, VADDR_T newRegionStart, size_t size return LOS_OK; } - +///删除线性区 STATUS_T OsRegionsRemove(LosVmSpace *space, VADDR_T regionBase, size_t size) { STATUS_T status; @@ -748,12 +820,12 @@ STATUS_T OsRegionsRemove(LosVmSpace *space, VADDR_T regionBase, size_t size) (VOID)LOS_MuxAcquire(&space->regionMux); - status = OsVmRegionAdjust(space, regionBase, size); + status = OsVmRegionAdjust(space, regionBase, size);//线性区调整 if (status != LOS_OK) { goto ERR_REGION_SPLIT; } - RB_SCAN_SAFE(&space->regionRbTree, pstRbNodeTemp, pstRbNodeNext) + RB_SCAN_SAFE(&space->regionRbTree, pstRbNodeTemp, pstRbNodeNext)//扫描虚拟空间内的线性区 regionTemp = (LosVmMapRegion *)pstRbNodeTemp; if (regionTemp->range.base > regionEnd) { break; @@ -772,7 +844,7 @@ ERR_REGION_SPLIT: (VOID)LOS_MuxRelease(&space->regionMux); return status; } - +///根据指定参数范围[addr,addr+len] 释放用户空间中堆区所占用的物理内存 INT32 OsUserHeapFree(LosVmSpace *vmSpace, VADDR_T addr, size_t len) { LosVmMapRegion *vmRegion = NULL; @@ -781,25 +853,25 @@ INT32 OsUserHeapFree(LosVmSpace *vmSpace, VADDR_T addr, size_t len) VADDR_T vaddr; STATUS_T ret; - if (vmSpace == LOS_GetKVmSpace() || vmSpace->heap == NULL) { + if (vmSpace == LOS_GetKVmSpace() || vmSpace->heap == NULL) {//虚拟空间堆区必须在非内核空间 return -1; } - vmRegion = LOS_RegionFind(vmSpace, addr); + vmRegion = LOS_RegionFind(vmSpace, addr);//通过参数虚拟地址红黑树找到线性区,线性区范围内包含了参数虚拟地址 if (vmRegion == NULL) { return -1; } - if (vmRegion == vmSpace->heap) { + if (vmRegion == vmSpace->heap) {//地址所在的线性区为堆区 vaddr = addr; - while (len > 0) { - if (LOS_ArchMmuQuery(&vmSpace->archMmu, vaddr, &paddr, 0) == LOS_OK) { - ret = LOS_ArchMmuUnmap(&vmSpace->archMmu, vaddr, 1); + while (len > 0) {//参数0 代表不获取 flags 信息 + if (LOS_ArchMmuQuery(&vmSpace->archMmu, vaddr, &paddr, 0) == LOS_OK) {//通过虚拟地址查到物理地址 + ret = LOS_ArchMmuUnmap(&vmSpace->archMmu, vaddr, 1);//解除映射关系以页为单位,这里解除1页 if (ret <= 0) { VM_ERR("unmap failed, ret = %d", ret); } - vmPage = LOS_VmPageGet(paddr); - LOS_PhysPageFree(vmPage); + vmPage = LOS_VmPageGet(paddr);//获取物理页面信息 + LOS_PhysPageFree(vmPage);//释放页 } vaddr += PAGE_SIZE; len -= PAGE_SIZE; @@ -809,7 +881,7 @@ INT32 OsUserHeapFree(LosVmSpace *vmSpace, VADDR_T addr, size_t len) return -1; } - +///线性区是否支持扩展 STATUS_T OsIsRegionCanExpand(LosVmSpace *space, LosVmMapRegion *region, size_t size) { LosVmMapRegion *nextRegion = NULL; @@ -826,13 +898,13 @@ STATUS_T OsIsRegionCanExpand(LosVmSpace *space, LosVmMapRegion *region, size_t s return LOS_NOK; } - +///解除一定范围的虚拟地址的映射关系 STATUS_T OsUnMMap(LosVmSpace *space, VADDR_T addr, size_t size) { size = LOS_Align(size, PAGE_SIZE); addr = LOS_Align(addr, PAGE_SIZE); (VOID)LOS_MuxAcquire(&space->regionMux); - STATUS_T status = OsRegionsRemove(space, addr, size); + STATUS_T status = OsRegionsRemove(space, addr, size);//删除线性区 if (status != LOS_OK) { status = -EINVAL; VM_ERR("region_split failed"); @@ -843,28 +915,28 @@ ERR_REGION_SPLIT: (VOID)LOS_MuxRelease(&space->regionMux); return status; } - +/// 释放所有线性区 STATIC VOID OsVmSpaceAllRegionFree(LosVmSpace *space) { LosRbNode *pstRbNode = NULL; LosRbNode *pstRbNodeNext = NULL; /* free all of the regions */ - RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext) - LosVmMapRegion *region = (LosVmMapRegion *)pstRbNode; + RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext) //遍历红黑树 + LosVmMapRegion *region = (LosVmMapRegion *)pstRbNode;//拿到线性区 if (region->range.size == 0) { VM_ERR("space free, region: %#x flags: %#x, base:%#x, size: %#x", region, region->regionFlags, region->range.base, region->range.size); } - STATUS_T ret = LOS_RegionFree(space, region); + STATUS_T ret = LOS_RegionFree(space, region);//释放线性区 if (ret != LOS_OK) { VM_ERR("free region error, space %p, region %p", space, region); } - RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext) + RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)//要好好研究下这几个宏,有点意思 return; } - +/// 释放虚拟空间 STATUS_T OsVmSpaceRegionFree(LosVmSpace *space) { if (space == NULL) { @@ -882,22 +954,21 @@ STATUS_T OsVmSpaceRegionFree(LosVmSpace *space) return LOS_OK; } - +///释放虚拟空间,注意内核空间不能被释放掉,永驻内存 STATUS_T LOS_VmSpaceFree(LosVmSpace *space) { if (space == NULL) { return LOS_ERRNO_VM_INVALID_ARGS; } - if (space == &g_kVmSpace) { + if (space == &g_kVmSpace) {//不能释放内核虚拟空间,内核空间常驻内存 VM_ERR("try to free kernel aspace, not allowed"); return LOS_OK; } /* pop it out of the global aspace list */ (VOID)LOS_MuxAcquire(&space->regionMux); - - LOS_ListDelete(&space->node); + LOS_ListDelete(&space->node);//从g_vmSpaceList链表里删除,g_vmSpaceList记录了所有空间节点。 OsVmSpaceAllRegionFree(space); @@ -920,7 +991,7 @@ STATUS_T LOS_VmSpaceFree(LosVmSpace *space) LOS_MemFree(m_aucSysMem0, space); return LOS_OK; } - +///虚拟地址和size是否在空间 BOOL LOS_IsRangeInSpace(const LosVmSpace *space, VADDR_T vaddr, size_t size) { /* is the starting address within the address space */ @@ -940,7 +1011,7 @@ BOOL LOS_IsRangeInSpace(const LosVmSpace *space, VADDR_T vaddr, size_t size) } return TRUE; } - +/// 在进程空间中预留一块内存空间 STATUS_T LOS_VmSpaceReserve(LosVmSpace *space, size_t size, VADDR_T vaddr) { UINT32 regionFlags = 0; @@ -961,7 +1032,7 @@ STATUS_T LOS_VmSpaceReserve(LosVmSpace *space, size_t size, VADDR_T vaddr) return region ? LOS_OK : LOS_ERRNO_VM_NO_MEMORY; } - +///实现从虚拟地址到物理地址的映射,将指定长度的物理地址区间与虚拟地址区间做映射,需提前申请物理地址区间 STATUS_T LOS_VaddrToPaddrMmap(LosVmSpace *space, VADDR_T vaddr, PADDR_T paddr, size_t len, UINT32 flags) { STATUS_T ret; @@ -976,19 +1047,19 @@ STATUS_T LOS_VaddrToPaddrMmap(LosVmSpace *space, VADDR_T vaddr, PADDR_T paddr, s } if (space == NULL) { - space = OsCurrProcessGet()->vmSpace; + space = OsCurrProcessGet()->vmSpace;//获取当前进程的空间 } - region = LOS_RegionFind(space, vaddr); - if (region != NULL) { + region = LOS_RegionFind(space, vaddr);//通过虚拟地址查找线性区 + if (region != NULL) {//已经被映射过了,失败返回 VM_ERR("vaddr : 0x%x already used!", vaddr); return LOS_ERRNO_VM_BUSY; } - region = LOS_RegionAlloc(space, vaddr, len, flags, 0); + region = LOS_RegionAlloc(space, vaddr, len, flags, 0);//通过虚拟地址 创建一个region if (region == NULL) { VM_ERR("failed"); - return LOS_ERRNO_VM_NO_MEMORY; + return LOS_ERRNO_VM_NO_MEMORY;//内存不够 } while (len > 0) { @@ -998,9 +1069,9 @@ STATUS_T LOS_VaddrToPaddrMmap(LosVmSpace *space, VADDR_T vaddr, PADDR_T paddr, s VM_ERR("Page is NULL"); return LOS_ERRNO_VM_NOT_VALID; } - LOS_AtomicInc(&vmPage->refCounts); + LOS_AtomicInc(&vmPage->refCounts);//ref自增 - ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, region->regionFlags); + ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, region->regionFlags);//mmu map if (ret <= 0) { VM_ERR("LOS_ArchMmuMap failed: %d", ret); LOS_RegionFree(space, region); @@ -1014,9 +1085,10 @@ STATUS_T LOS_VaddrToPaddrMmap(LosVmSpace *space, VADDR_T vaddr, PADDR_T paddr, s return LOS_OK; } +//对外接口|申请内核堆空间内存 VOID *LOS_VMalloc(size_t size) { - LosVmSpace *space = &g_vMallocSpace; + LosVmSpace *space = &g_vMallocSpace;//从内核动态空间申请 LosVmMapRegion *region = NULL; size_t sizeCount; size_t count; @@ -1025,48 +1097,48 @@ VOID *LOS_VMalloc(size_t size) PADDR_T pa; STATUS_T ret; - size = LOS_Align(size, PAGE_SIZE); + size = LOS_Align(size, PAGE_SIZE);// if ((size == 0) || (size > space->size)) { return NULL; } - sizeCount = size >> PAGE_SHIFT; + sizeCount = size >> PAGE_SHIFT;//按页申请所以需右移12位 LOS_DL_LIST_HEAD(pageList); - (VOID)LOS_MuxAcquire(&space->regionMux); + (VOID)LOS_MuxAcquire(&space->regionMux);//获得互斥锁 - count = LOS_PhysPagesAlloc(sizeCount, &pageList); + count = LOS_PhysPagesAlloc(sizeCount, &pageList);//一页一页申请,并从pageList尾部插入 if (count < sizeCount) { VM_ERR("failed to allocate enough pages (ask %zu, got %zu)", sizeCount, count); goto ERROR; } - /* allocate a region and put it in the aspace list */ - region = LOS_RegionAlloc(space, 0, size, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE, 0); + /* allocate a region and put it in the aspace list *///分配一个可读写的线性区,并挂在space + region = LOS_RegionAlloc(space, 0, size, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE, 0);//注意第二个参数是 vaddr = 0 !!! if (region == NULL) { VM_ERR("alloc region failed, size = %x", size); goto ERROR; } - va = region->range.base; - while ((vmPage = LOS_ListRemoveHeadType(&pageList, LosVmPage, node))) { - pa = vmPage->physAddr; - LOS_AtomicInc(&vmPage->refCounts); - ret = LOS_ArchMmuMap(&space->archMmu, va, pa, 1, region->regionFlags); + va = region->range.base;//va 该区范围基地址为虚拟地址的开始位置,理解va怎么来的是理解线性地址的关键! + while ((vmPage = LOS_ListRemoveHeadType(&pageList, LosVmPage, node))) {//从pageList循环拿page + pa = vmPage->physAddr;//获取page物理地址,因上面是通过LOS_PhysPagesAlloc分配 + LOS_AtomicInc(&vmPage->refCounts);//refCounts 自增 + ret = LOS_ArchMmuMap(&space->archMmu, va, pa, 1, region->regionFlags);//一页一页的map if (ret != 1) { VM_ERR("LOS_ArchMmuMap failed!, err;%d", ret); } - va += PAGE_SIZE; - } + va += PAGE_SIZE;//一页映射完成,进入下一页 + }//va 注意 region的虚拟地址页是连续的,但物理页可以不连续! 很重要!!! - (VOID)LOS_MuxRelease(&space->regionMux); - return (VOID *)(UINTPTR)region->range.base; + (VOID)LOS_MuxRelease(&space->regionMux);//释放互斥锁 + return (VOID *)(UINTPTR)region->range.base;//返回虚拟基地址供应用使用 ERROR: - (VOID)LOS_PhysPagesFree(&pageList); - (VOID)LOS_MuxRelease(&space->regionMux); + (VOID)LOS_PhysPagesFree(&pageList);//释放物理内存页 + (VOID)LOS_MuxRelease(&space->regionMux);//释放互斥锁 return NULL; } - +///对外接口|释放内核堆空间内存 VOID LOS_VFree(const VOID *addr) { LosVmSpace *space = &g_vMallocSpace; @@ -1080,13 +1152,13 @@ VOID LOS_VFree(const VOID *addr) (VOID)LOS_MuxAcquire(&space->regionMux); - region = LOS_RegionFind(space, (VADDR_T)(UINTPTR)addr); + region = LOS_RegionFind(space, (VADDR_T)(UINTPTR)addr);//先找到线性区 if (region == NULL) { VM_ERR("find region failed"); goto DONE; } - ret = LOS_RegionFree(space, region); + ret = LOS_RegionFree(space, region);//释放线性区 if (ret) { VM_ERR("free region failed, ret = %d", ret); } @@ -1097,10 +1169,9 @@ DONE: LosMux *OsGVmSpaceMuxGet(VOID) { - return &g_vmSpaceListMux; + return &g_vmSpaceListMux; } - -STATIC INLINE BOOL OsMemLargeAlloc(UINT32 size) +STATIC INLINE BOOL OsMemLargeAlloc(UINT32 size)//是不是分配浪费大于1K的内存 { if (g_kHeapInited == FALSE) { return FALSE; @@ -1122,28 +1193,28 @@ PADDR_T LOS_PaddrQuery(VOID *vaddr) return (PADDR_T)VMM_TO_DMA_ADDR((VADDR_T)vaddr); } #endif - +///内核空间内存分配,申请小于16KiB的内存则通过堆内存池获取,否则申请多个连续物理页 VOID *LOS_KernelMalloc(UINT32 size) { VOID *ptr = NULL; - + //从本函数可知,内核空间的分配有两种方式 #ifdef LOSCFG_KERNEL_VM - if (OsMemLargeAlloc(size)) { - ptr = LOS_PhysPagesAllocContiguous(ROUNDUP(size, PAGE_SIZE) >> PAGE_SHIFT); + if (OsMemLargeAlloc(size)) {//是不是分配浪费小于1K的内存 + ptr = LOS_PhysPagesAllocContiguous(ROUNDUP(size, PAGE_SIZE) >> PAGE_SHIFT);//分配连续的物理内存页 } else #endif { - ptr = LOS_MemAlloc(OS_SYS_MEM_ADDR, size); + ptr = LOS_MemAlloc(OS_SYS_MEM_ADDR, size);//从内存池分配 } return ptr; } - +/// 申请具有对齐属性的内存,申请规则:申请小于16KiB的内存则通过堆内存池获取,否则申请多个连续物理页 VOID *LOS_KernelMallocAlign(UINT32 size, UINT32 boundary) { VOID *ptr = NULL; -#ifdef LOSCFG_KERNEL_VM +#ifdef LOSCFG_KERNEL_VM if (OsMemLargeAlloc(size) && IS_ALIGNED(PAGE_SIZE, boundary)) { ptr = LOS_PhysPagesAllocContiguous(ROUNDUP(size, PAGE_SIZE) >> PAGE_SHIFT); } else @@ -1154,7 +1225,7 @@ VOID *LOS_KernelMallocAlign(UINT32 size, UINT32 boundary) return ptr; } - +/// 重新分配内核内存空间 VOID *LOS_KernelRealloc(VOID *ptr, UINT32 size) { VOID *tmpPtr = NULL; @@ -1162,6 +1233,7 @@ VOID *LOS_KernelRealloc(VOID *ptr, UINT32 size) #ifdef LOSCFG_KERNEL_VM LosVmPage *page = NULL; errno_t ret; + if (ptr == NULL) { tmpPtr = LOS_KernelMalloc(size); } else { @@ -1198,7 +1270,7 @@ VOID LOS_KernelFree(VOID *ptr) { #ifdef LOSCFG_KERNEL_VM UINT32 ret; - if (OsMemIsHeapNode(ptr) == FALSE) { + if (OsMemIsHeapNode(ptr) == FALSE) {//判断地址是否在堆区 ret = OsMemLargeNodeFree(ptr); if (ret != LOS_OK) { VM_ERR("KernelFree %p failed", ptr); @@ -1207,6 +1279,6 @@ VOID LOS_KernelFree(VOID *ptr) } else #endif { - (VOID)LOS_MemFree(OS_SYS_MEM_ADDR, ptr); + (VOID)LOS_MemFree(OS_SYS_MEM_ADDR, ptr);//从内存池中释放 } -} +} \ No newline at end of file diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_page.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_page.c index 650fd0b0..a711ea7c 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_page.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_page.c @@ -40,26 +40,26 @@ #ifdef LOSCFG_KERNEL_VM -LosVmPage *g_vmPageArray = NULL; -size_t g_vmPageArraySize; - +LosVmPage *g_vmPageArray = NULL;//物理页框数组 +size_t g_vmPageArraySize;//物理页框大小 +//页框初始化 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; + LOS_ListInit(&page->node);//页节点初始化 + page->flags = FILE_PAGE_FREE;//页标签,初始为空闲页 + LOS_AtomicSet(&page->refCounts, 0);//引用次数0 + page->physAddr = pa;//物理地址 + page->segID = segID;//物理地址使用段管理,段ID + page->order = VM_LIST_ORDER_MAX;//初始化值,不属于任何块组 page->nPages = 0; #ifdef LOSCFG_PAGE_TABLE_FINE_LOCK LOS_SpinInit(&page->lock); #endif } - +///伙伴算法初始化 STATIC INLINE VOID OsVmPageOrderListInit(LosVmPage *page, size_t nPages) -{ - OsVmPhysPagesFreeContiguous(page, nPages); +{//@note_why 此时所有页面 page->order = VM_LIST_ORDER_MAX,能挂入伙伴算法的链表吗? + OsVmPhysPagesFreeContiguous(page, nPages);//释放连续的物理页框 } #define VMPAGEINIT(page, pa, segID) do { \ @@ -68,6 +68,10 @@ STATIC INLINE VOID OsVmPageOrderListInit(LosVmPage *page, size_t nPages) (pa) += PAGE_SIZE; \ } while (0) +/*! + 完成对物理内存整体初始化,本函数一定运行在实模式下 + 1.申请大块内存g_vmPageArray存放LosVmPage,按4K一页划分物理内存存放在数组中. +*/ VOID OsVmPageStartup(VOID) { struct VmPhysSeg *seg = NULL; @@ -76,7 +80,7 @@ VOID OsVmPageStartup(VOID) UINT32 nPage; INT32 segID; - OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE)); + OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));//校正 g_physArea size /* * Pages getting from OsVmPhysPageNumGet() interface here contain the memory @@ -85,20 +89,20 @@ VOID OsVmPageStartup(VOID) */ UINT32 pageNum = OsVmPhysPageNumGet(); nPage = pageNum * PAGE_SIZE / (sizeof(LosVmPage) + PAGE_SIZE); - g_vmPageArraySize = nPage * sizeof(LosVmPage); - g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize); + g_vmPageArraySize = nPage * sizeof(LosVmPage);//页表总大小 + g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//实模式下申请内存,此时还没有初始化MMU OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE)); - OsVmPhysSegAdd(); - OsVmPhysInit(); + OsVmPhysSegAdd();// 完成对段的初始化 + OsVmPhysInit();// 加入空闲链表和设置置换算法,LRU(最近最久未使用)算法 #ifdef LOSCFG_KERNEL_PLIMITS OsMemLimitSetLimit(pageNum * PAGE_SIZE); #endif - for (segID = 0; segID < g_vmPhysSegNum; segID++) { + for (segID = 0; segID < g_vmPhysSegNum; segID++) {//遍历物理段,将段切成一页一页 seg = &g_vmPhysSeg[segID]; - nPage = seg->size >> PAGE_SHIFT; + 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 */ @@ -116,17 +120,17 @@ VOID OsVmPageStartup(VOID) for (; left > 0; left--) { VMPAGEINIT(page, pa, segID); } - OsVmPageOrderListInit(seg->pageBase, nPage); + OsVmPageOrderListInit(seg->pageBase, nPage);//伙伴算法初始化,将所有页加入空闲链表供分配 } } - +///通过物理地址获取页框 LosVmPage *LOS_VmPageGet(PADDR_T paddr) { INT32 segID; LosVmPage *page = NULL; - for (segID = 0; segID < g_vmPhysSegNum; segID++) { - page = OsVmPhysToPage(paddr, segID); + for (segID = 0; segID < g_vmPhysSegNum; segID++) {//物理内存采用段页管理 + page = OsVmPhysToPage(paddr, segID);//通过物理地址和段ID找出物理页框 if (page != NULL) { break; } diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_phys.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_phys.c index 2c581456..3dc182e6 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_phys.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_phys.c @@ -1,3 +1,29 @@ +/*! +基本概念 + 物理内存是计算机上最重要的资源之一,指的是实际的内存设备提供的、可以通过CPU总线直接进行寻址的内存空间, + 其主要作用是为操作系统及程序提供临时存储空间。LiteOS-A内核管理物理内存是通过分页实现的,除了内核堆占用的一部分内存外, + 其余可用内存均以4KiB为单位划分成页帧,内存分配和内存回收便是以页帧为单位进行操作。内核采用伙伴算法管理空闲页面, + 可以降低一定的内存碎片率,提高内存分配和释放的效率,但是一个很小的块往往也会阻塞一个大块的合并,导致不能分配较大的内存块。 +运行机制 + LiteOS-A内核的物理内存使用分布视图,主要由内核镜像、内核堆及物理页组成。内核堆部分见堆内存管理一节。 + ----------------------------------------------------- + + kernel.bin | heap | page frames + (内核镜像) | (内核堆) | (物理页框) + ----------------------------------------------------- + 伙伴算法把所有空闲页帧分成9个内存块组,每组中内存块包含2的幂次方个页帧,例如:第0组的内存块包含2的0次方个页帧, + 即1个页帧;第8组的内存块包含2的8次方个页帧,即256个页帧。相同大小的内存块挂在同一个链表上进行管理。 + +申请内存 + 系统申请20KiB内存,按一页帧4K算,即5个页帧时,9个内存块组中索引为2的链表挂着一块大小为8个页帧的内存块满足要求,分配出20KiB内存后还剩余12KiB内存, + 即3个页帧,将3个页帧分成2的幂次方之和,即0跟1,尝试查找伙伴进行合并。2个页帧的内存块没有伙伴则直接插到索引为1的链表上, + 继续查找1个页帧的内存块是否有伙伴,索引为0的链表上此时有1个,如果两个内存块地址连续则进行合并,并将内存块挂到索引为0的链表上,否则不做处理。 +释放内存 + 系统释放12KiB内存,即3个页帧,将3个页帧分成2的幂次方之和,即2跟1,尝试查找伙伴进行合并,索引为1的链表上有1个内存块, + 若地址连续则合并,并将合并后的内存块挂到索引为2的链表上,索引为0的链表上此时也有1个,如果地址连续则进行合并, + 并将合并后的内存块挂到索引为1的链表上,此时继续判断是否有伙伴,重复上述操作。 + */ + /* * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved. * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved. @@ -41,36 +67,36 @@ #define ONE_PAGE 1 -/* Physical memory area array */ -STATIC struct VmPhysArea g_physArea[] = { +/* Physical memory area array | 物理内存区数组 */ +STATIC struct VmPhysArea g_physArea[] = {///< 这里只有一个区域,即只生成一个段 { - .start = SYS_MEM_BASE, - .size = SYS_MEM_SIZE_DEFAULT, + .start = SYS_MEM_BASE, //整个物理内存基地址,#define SYS_MEM_BASE DDR_MEM_ADDR , 0x80000000 + .size = SYS_MEM_SIZE_DEFAULT,//整个物理内存总大小 0x07f00000 }, }; -struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX]; -INT32 g_vmPhysSegNum = 0; - +struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX]; ///< 最大32段 +INT32 g_vmPhysSegNum = 0; ///< 段数 +/// 获取段数组,全局变量,变量放在 .bbs 区 LosVmPhysSeg *OsGVmPhysSegGet(void) { return g_vmPhysSeg; } - +/// 初始化Lru置换链表 STATIC VOID OsVmPhysLruInit(struct VmPhysSeg *seg) { INT32 i; UINT32 intSave; - LOS_SpinInit(&seg->lruLock); + LOS_SpinInit(&seg->lruLock);//初始化自旋锁,自旋锁用于CPU多核同步 LOS_SpinLockSave(&seg->lruLock, &intSave); - for (i = 0; i < VM_NR_LRU_LISTS; i++) { - seg->lruSize[i] = 0; - LOS_ListInit(&seg->lruList[i]); + for (i = 0; i < VM_NR_LRU_LISTS; i++) { //五个双循环链表 + seg->lruSize[i] = 0; //记录链表节点数 + LOS_ListInit(&seg->lruList[i]); //初始化LRU链表 } LOS_SpinUnlockRestore(&seg->lruLock, intSave); } - +/// 创建物理段,由区划分转成段管理 STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size) { struct VmPhysSeg *seg = NULL; @@ -79,8 +105,8 @@ STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size) return -1; } - seg = &g_vmPhysSeg[g_vmPhysSegNum++]; - for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) { + seg = &g_vmPhysSeg[g_vmPhysSegNum++];//拿到一段数据 + for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) {//定位到合适的段 *seg = *(seg - 1); } seg->start = start; @@ -88,21 +114,21 @@ STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size) return 0; } - +/// 添加物理段 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); + + for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {//遍历g_physArea数组 + ret = OsVmPhysSegCreate(g_physArea[i].start, g_physArea[i].size);//由区划分转成段管理 if (ret != 0) { VM_ERR("create phys seg failed"); } } } - +/// 段区域大小调整 VOID OsVmPhysAreaSizeAdjust(size_t size) { /* @@ -113,35 +139,36 @@ VOID OsVmPhysAreaSizeAdjust(size_t size) g_physArea[0].size -= size; } +/// 获得物理内存的总页数 UINT32 OsVmPhysPageNumGet(VOID) { UINT32 nPages = 0; INT32 i; for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) { - nPages += g_physArea[i].size >> PAGE_SHIFT; + nPages += g_physArea[i].size >> PAGE_SHIFT;//右移12位,相当于除以4K, 计算出总页数 } - return nPages; + return nPages;//返回所有物理内存总页数 } - +/// 初始化空闲链表,分配物理页框使用伙伴算法 STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg) { int i; UINT32 intSave; struct VmFreeList *list = NULL; - LOS_SpinInit(&seg->freeListLock); + 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; + for (i = 0; i < VM_LIST_ORDER_MAX; i++) {//遍历伙伴算法空闲块组链表 + list = &seg->freeList[i]; //一个个来 + LOS_ListInit(&list->node); //LosVmPage.node将挂到list->node上 + list->listCnt = 0; //链表上的数量默认0 } LOS_SpinUnlockRestore(&seg->freeListLock, intSave); } - +/// 物理段初始化 VOID OsVmPhysInit(VOID) { struct VmPhysSeg *seg = NULL; @@ -150,13 +177,13 @@ VOID OsVmPhysInit(VOID) 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); + seg->pageBase = &g_vmPageArray[nPages];//记录本段首页物理页框地址 + nPages += seg->size >> PAGE_SHIFT;//偏移12位,按4K一页,算出本段总页数 + OsVmPhysFreeListInit(seg); //初始化空闲链表,分配页框使用伙伴算法 + OsVmPhysLruInit(seg); //初始化LRU置换链表 } } - +/// 将页框挂入空闲链表,分配物理页框从空闲链表里拿 STATIC VOID OsVmPhysFreeListAddUnsafe(LosVmPage *page, UINT8 order) { struct VmPhysSeg *seg = NULL; @@ -173,36 +200,44 @@ STATIC VOID OsVmPhysFreeListAddUnsafe(LosVmPage *page, UINT8 order) LOS_ListTailInsert(&list->node, &page->node); list->listCnt++; } - +///将物理页框从空闲链表上摘除,见于物理页框被分配的情况 STATIC VOID OsVmPhysFreeListDelUnsafe(LosVmPage *page) { struct VmPhysSeg *seg = NULL; struct VmFreeList *list = NULL; - if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) { + if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) {//等于VM_LIST_ORDER_MAX也不行,说明伙伴算法最大支持 2^8的分配 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; + seg = &g_vmPhysSeg[page->segID]; //找到物理页框对应的段 + list = &seg->freeList[page->order]; //根据伙伴算法组序号找到空闲链表 + list->listCnt--; //链表节点总数减一 + LOS_ListDelete(&page->node); //将自己从链表上摘除 + page->order = VM_LIST_ORDER_MAX; //告诉系统物理页框已不在空闲链表上, 用于OsVmPhysPagesSpiltUnsafe的断言 } + +/** + * @brief 本函数很像卖猪肉的,拿一大块肉剁,先把多余的放回到小块肉堆里去. + * @param page + * @param oldOrder 原本要买 2^2肉 + * @param newOrder 却找到个 2^8肉块 + * @return STATIC + */ STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder) { UINT32 order; LosVmPage *buddyPage = NULL; - for (order = newOrder; order > oldOrder;) { - order--; - buddyPage = &page[VM_ORDER_TO_PAGES(order)]; - LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX); - OsVmPhysFreeListAddUnsafe(buddyPage, order); + for (order = newOrder; order > oldOrder;) {//把肉剁碎的过程,把多余的肉块切成2^7,2^6...标准块, + 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 } } - +///通过物理地址获取所属参数段的物理页框 LosVmPage *OsVmPhysToPage(paddr_t pa, UINT8 segID) { struct VmPhysSeg *seg = NULL; @@ -216,8 +251,8 @@ LosVmPage *OsVmPhysToPage(paddr_t pa, UINT8 segID) return NULL; } - offset = pa - seg->start; - return (seg->pageBase + (offset >> PAGE_SHIFT)); + offset = pa - seg->start;//得到物理地址的偏移量 + return (seg->pageBase + (offset >> PAGE_SHIFT));//得到对应的物理页框 } LosVmPage *OsVmPaddrToPage(paddr_t paddr) @@ -233,31 +268,37 @@ LosVmPage *OsVmPaddrToPage(paddr_t paddr) } return NULL; } - -VOID *OsVmPageToVaddr(LosVmPage *page) +/*! + * @brief 通过page获取内核空间的虚拟地址 参考OsArchMmuInit + \n #define SYS_MEM_BASE DDR_MEM_ADDR /* physical memory base 物理地址的起始地址 * / + \n 本函数非常重要,通过一个物理地址找到内核虚拟地址 + \n 内核静态映射:提升虚实转化效率,段映射减少页表项 + * @param page + * @return VOID* + */ +VOID *OsVmPageToVaddr(LosVmPage *page)// { VADDR_T vaddr; - vaddr = KERNEL_ASPACE_BASE + page->physAddr - SYS_MEM_BASE; - - return (VOID *)(UINTPTR)vaddr; + vaddr = KERNEL_ASPACE_BASE + page->physAddr - SYS_MEM_BASE;//表示申请的物理地址在物理空间的偏移量等于映射的虚拟地址在内核空间的偏移量 + return (VOID *)(UINTPTR)vaddr;//不需要存储映射关系,这简直就是神来之笔,拍案叫绝。@note_good 详见 鸿蒙内核源码分析(页表管理篇) } - +///通过虚拟地址找映射的物理页框 LosVmPage *OsVmVaddrToPage(VOID *ptr) { struct VmPhysSeg *seg = NULL; - PADDR_T pa = LOS_PaddrQuery(ptr); + PADDR_T pa = LOS_PaddrQuery(ptr);//通过空间的虚拟地址查询物理地址 UINT32 segID; - for (segID = 0; segID < g_vmPhysSegNum; segID++) { + for (segID = 0; segID < g_vmPhysSegNum; segID++) {//遍历所有段 seg = &g_vmPhysSeg[segID]; - if ((pa >= seg->start) && (pa < (seg->start + seg->size))) { - return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT); + if ((pa >= seg->start) && (pa < (seg->start + seg->size))) {//找到物理地址所在的段 + return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT);//段基地址+页偏移索引 得到虚拟地址经映射所在物理页框 } } return NULL; } - +/// 回收一定范围内的页框 STATIC INLINE VOID OsVmRecycleExtraPages(LosVmPage *page, size_t startPage, size_t endPage) { if (startPage >= endPage) { @@ -266,7 +307,7 @@ STATIC INLINE VOID OsVmRecycleExtraPages(LosVmPage *page, size_t startPage, size OsVmPhysPagesFreeContiguous(page, endPage - startPage); } - +/// 大块的物理内存分配 STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages) { struct VmFreeList *list = NULL; @@ -276,11 +317,11 @@ STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages) PADDR_T paEnd; size_t size = nPages << PAGE_SHIFT; - list = &seg->freeList[VM_LIST_ORDER_MAX - 1]; - LOS_DL_LIST_FOR_EACH_ENTRY(page, &list->node, LosVmPage, node) { + list = &seg->freeList[VM_LIST_ORDER_MAX - 1];//先找伙伴算法中内存块最大的开撸 + LOS_DL_LIST_FOR_EACH_ENTRY(page, &list->node, LosVmPage, node) {//遍历链表 paStart = page->physAddr; paEnd = paStart + size; - if (paEnd > (seg->start + seg->size)) { + if (paEnd > (seg->start + seg->size)) {//匹配物理地址范围 continue; } @@ -302,7 +343,7 @@ STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages) return NULL; } - +/// 申请物理页并挂在对应的链表上 STATIC LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages) { struct VmFreeList *list = NULL; @@ -312,13 +353,13 @@ STATIC LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages) UINT32 newOrder; order = OsVmPagesToOrder(nPages); - if (order < VM_LIST_ORDER_MAX) { - for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) { + 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; + if (LOS_ListEmpty(&list->node)) {//这条链路上没有可分配的物理页框 + continue;//继续往大的找 } - page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node); + page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);//找到了直接返回第一个节点 goto DONE; } } else { @@ -339,7 +380,7 @@ DONE: return page; } - +/// 释放物理页框,所谓释放物理页就是把页挂到空闲链表中 VOID OsVmPhysPagesFree(LosVmPage *page, UINT8 order) { paddr_t pa; @@ -349,51 +390,59 @@ VOID OsVmPhysPagesFree(LosVmPage *page, UINT8 order) return; } - if (order < VM_LIST_ORDER_MAX - 1) { - pa = VM_PAGE_TO_PHYS(page); - do { - pa ^= VM_ORDER_TO_PHYS(order); - buddyPage = OsVmPhysToPage(pa, page->segID); - if ((buddyPage == NULL) || (buddyPage->order != order)) { + if (order < VM_LIST_ORDER_MAX - 1) {//order[0,7] + pa = VM_PAGE_TO_PHYS(page);//获取物理地址 + do {//按位异或 + pa ^= VM_ORDER_TO_PHYS(order);//@note_good 注意这里是高位和低位的 ^= ,也就是说跳到order块组物理地址处,此处处理甚妙! + buddyPage = OsVmPhysToPage(pa, page->segID);//通过物理地址拿到页框 + if ((buddyPage == NULL) || (buddyPage->order != order)) {//页框所在组块必须要对应 break; } - OsVmPhysFreeListDelUnsafe(buddyPage); + OsVmPhysFreeListDelUnsafe(buddyPage);//注意buddypage是连续的物理页框 例如order=2时,2^2=4页就是一个块组 |_|_|_|_| order++; pa &= ~(VM_ORDER_TO_PHYS(order) - 1); page = OsVmPhysToPage(pa, page->segID); } while (order < VM_LIST_ORDER_MAX - 1); } - OsVmPhysFreeListAddUnsafe(page, order); + OsVmPhysFreeListAddUnsafe(page, order);//伙伴算法 空闲节点增加 } - +///连续的释放物理页框, 如果8页连在一块是一起释放的 VOID OsVmPhysPagesFreeContiguous(LosVmPage *page, size_t nPages) { paddr_t pa; UINT32 order; size_t n; - while (TRUE) { - pa = VM_PAGE_TO_PHYS(page); - order = VM_PHYS_TO_ORDER(pa); - n = VM_ORDER_TO_PAGES(order); - if (n > nPages) { + while (TRUE) {//死循环 + pa = VM_PAGE_TO_PHYS(page);//获取页面物理地址 + order = VM_PHYS_TO_ORDER(pa);//通过物理地址找到伙伴算法的级别 + n = VM_ORDER_TO_PAGES(order);//通过级别找到物理页块 (1< nPages) {//nPages只剩下小于2^order时,退出循环 break; } - OsVmPhysPagesFree(page, order); - nPages -= n; - page += n; + OsVmPhysPagesFree(page, order);//释放伙伴算法对应块组 + nPages -= n;//总页数减少 + page += n;//释放的页数增多 } - + //举例剩下 7个页框时,依次用 2^2 2^1 2^0 方式释放 while (nPages > 0) { - order = LOS_HighBitGet(nPages); - n = VM_ORDER_TO_PAGES(order); - OsVmPhysPagesFree(page, order); + order = LOS_HighBitGet(nPages);//从高到低块组释放 + n = VM_ORDER_TO_PAGES(order);//2^order次方 + OsVmPhysPagesFree(page, order);//释放块组 nPages -= n; - page += n; + page += n;//相当于page[n] } } +/*! + * @brief OsVmPhysPagesGet 获取一定数量的页框 LosVmPage实体是放在全局大数组中的, + * LosVmPage->nPages 标记了分配页数 + * @param nPages + * @return + * + * @see + */ STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages) { UINT32 intSave; @@ -404,11 +453,11 @@ STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages) 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; + page = OsVmPhysPagesAlloc(seg, nPages);//分配指定页数的物理页,nPages需小于伙伴算法一次能分配的最大页数 + if (page != NULL) {//分配成功 + /* */ + LOS_AtomicSet(&page->refCounts, 0);//设置引用次数为0 + page->nPages = nPages;//页数 LOS_SpinUnlockRestore(&seg->freeListLock, intSave); return page; } @@ -416,7 +465,7 @@ STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages) } return NULL; } - +///分配连续的物理页 VOID *LOS_PhysPagesAllocContiguous(size_t nPages) { LosVmPage *page = NULL; @@ -424,15 +473,15 @@ VOID *LOS_PhysPagesAllocContiguous(size_t nPages) if (nPages == 0) { return NULL; } - - page = OsVmPhysPagesGet(nPages); + //鸿蒙 nPages 不能大于 2^8 次方,即256个页,1M内存,仅限于内核态,用户态不限制分配大小. + page = OsVmPhysPagesGet(nPages);//通过伙伴算法获取物理上连续的页 if (page == NULL) { return NULL; } - return OsVmPageToVaddr(page); + return OsVmPageToVaddr(page);//通过物理页找虚拟地址 } - +/// 释放指定页数地址连续的物理内存 VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages) { UINT32 intSave; @@ -443,17 +492,17 @@ VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages) return; } - page = OsVmVaddrToPage(ptr); + page = OsVmVaddrToPage(ptr);//通过虚拟地址找到页框 if (page == NULL) { VM_ERR("vm page of ptr(%#x) is null", ptr); return; } - page->nPages = 0; + page->nPages = 0;//被分配的页数置为0,表示不被分配 seg = &g_vmPhysSeg[page->segID]; LOS_SpinLockSave(&seg->freeListLock, &intSave); - OsVmPhysPagesFreeContiguous(page, nPages); + OsVmPhysPagesFreeContiguous(page, nPages);//具体释放实现 LOS_SpinUnlockRestore(&seg->freeListLock, intSave); #ifdef LOSCFG_KERNEL_PLIMITS @@ -468,7 +517,7 @@ PADDR_T OsKVaddrToPaddr(VADDR_T kvaddr) } return (kvaddr - KERNEL_ASPACE_BASE + SYS_MEM_BASE); } - +/// 通过物理地址获取内核虚拟地址 VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr) { struct VmPhysSeg *seg = NULL; @@ -484,10 +533,10 @@ VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr) return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE); } } - - return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE); + //内核 + return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);// } - +///释放一个物理页框 VOID LOS_PhysPageFree(LosVmPage *page) { UINT32 intSave; @@ -497,12 +546,12 @@ VOID LOS_PhysPageFree(LosVmPage *page) return; } - if (LOS_AtomicDecRet(&page->refCounts) <= 0) { + if (LOS_AtomicDecRet(&page->refCounts) <= 0) {//减少引用数后不能小于0 seg = &g_vmPhysSeg[page->segID]; LOS_SpinLockSave(&seg->freeListLock, &intSave); - OsVmPhysPagesFreeContiguous(page, ONE_PAGE); - LOS_AtomicSet(&page->refCounts, 0); + OsVmPhysPagesFreeContiguous(page, ONE_PAGE);//释放一页 + LOS_AtomicSet(&page->refCounts, 0);//只要物理内存被释放了,引用数就必须得重置为 0 LOS_SpinUnlockRestore(&seg->freeListLock, intSave); } @@ -510,12 +559,22 @@ VOID LOS_PhysPageFree(LosVmPage *page) OsMemLimitMemFree(PAGE_SIZE); #endif } - +/// 申请一个物理页 LosVmPage *LOS_PhysPageAlloc(VOID) { - return OsVmPhysPagesGet(ONE_PAGE); + return OsVmPhysPagesGet(ONE_PAGE);//分配一页物理页 } +/*! + * @brief LOS_PhysPagesAlloc 分配nPages页个物理页框,并将页框挂入list + \n 返回已分配的页面大小,不负责一定能分配到nPages的页框 + * + * @param list + * @param nPages + * @return + * + * @see + */ size_t LOS_PhysPagesAlloc(size_t nPages, LOS_DL_LIST *list) { LosVmPage *page = NULL; @@ -526,17 +585,17 @@ size_t LOS_PhysPagesAlloc(size_t nPages, LOS_DL_LIST *list) } while (nPages--) { - page = OsVmPhysPagesGet(ONE_PAGE); + page = OsVmPhysPagesGet(ONE_PAGE);//一页一页分配,由伙伴算法分配 if (page == NULL) { break; } - LOS_ListTailInsert(list, &page->node); + LOS_ListTailInsert(list, &page->node);//从参数链表list尾部挂入新页面结点 count++; } return count; } - +///拷贝共享页面 VOID OsPhysSharePageCopy(PADDR_T oldPaddr, PADDR_T *newPaddr, LosVmPage *newPage) { UINT32 intSave; @@ -550,43 +609,43 @@ VOID OsPhysSharePageCopy(PADDR_T oldPaddr, PADDR_T *newPaddr, LosVmPage *newPage return; } - oldPage = LOS_VmPageGet(oldPaddr); + oldPage = LOS_VmPageGet(oldPaddr);//由物理地址得到页框 if (oldPage == NULL) { VM_ERR("invalid oldPaddr %p", oldPaddr); return; } - seg = &g_vmPhysSeg[oldPage->segID]; + seg = &g_vmPhysSeg[oldPage->segID];//拿到物理段 LOS_SpinLockSave(&seg->freeListLock, &intSave); - if (LOS_AtomicRead(&oldPage->refCounts) == 1) { - *newPaddr = oldPaddr; - } else { - newMem = LOS_PaddrToKVaddr(*newPaddr); - oldMem = LOS_PaddrToKVaddr(oldPaddr); + if (LOS_AtomicRead(&oldPage->refCounts) == 1) {//页面引用次数仅一次,说明只有一个进程在操作 + *newPaddr = oldPaddr;//新老指向同一块物理地址 + } else {//是个共享内存 + newMem = LOS_PaddrToKVaddr(*newPaddr); //新页虚拟地址 + oldMem = LOS_PaddrToKVaddr(oldPaddr); //老页虚拟地址 if ((newMem == NULL) || (oldMem == NULL)) { LOS_SpinUnlockRestore(&seg->freeListLock, intSave); return; - } - if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) { + }//请记住,在保护模式下,物理地址只能用于计算,操作(包括拷贝)需要虚拟地址! + if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) {//老页内容复制给新页,需操作虚拟地址,拷贝一页数据 VM_ERR("memcpy_s failed"); } - LOS_AtomicInc(&newPage->refCounts); - LOS_AtomicDec(&oldPage->refCounts); + LOS_AtomicInc(&newPage->refCounts);//新页引用次数以原子方式自动减量 + LOS_AtomicDec(&oldPage->refCounts);//老页引用次数以原子方式自动减量 } LOS_SpinUnlockRestore(&seg->freeListLock, intSave); return; } - +///获取物理页框所在段 struct VmPhysSeg *OsVmPhysSegGet(LosVmPage *page) { if ((page == NULL) || (page->segID >= VM_PHYS_SEG_MAX)) { return NULL; } - return (OsGVmPhysSegGet() + page->segID); + return (OsGVmPhysSegGet() + page->segID);//等用于OsGVmPhysSegGet()[page->segID] } - +///获取参数nPages对应的块组,例如 7 -> 2^3 返回 3 UINT32 OsVmPagesToOrder(size_t nPages) { UINT32 order; @@ -595,7 +654,7 @@ UINT32 OsVmPagesToOrder(size_t nPages) return order; } - +///释放双链表中的所有节点内存,本质是回归到伙伴orderlist中 size_t LOS_PhysPagesFree(LOS_DL_LIST *list) { UINT32 intSave; @@ -608,16 +667,16 @@ size_t LOS_PhysPagesFree(LOS_DL_LIST *list) return 0; } - 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); - OsVmPhysPagesFreeContiguous(page, ONE_PAGE); - LOS_AtomicSet(&page->refCounts, 0); - LOS_SpinUnlockRestore(&seg->freeListLock, intSave); + 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);//恢复锁 } - count++; + count++;//继续取下一个node } return count; @@ -632,4 +691,3 @@ VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr) return (VADDR_T *)DMA_TO_VMM_ADDR(paddr); } #endif - diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_scan.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_scan.c index ab467818..01624eac 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_scan.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_scan.c @@ -37,6 +37,10 @@ #ifdef LOSCFG_KERNEL_VM /* unmap a lru page by map record info caller need lru lock */ +/************************************************************************************************** + 解除文件页和进程(mmu)的映射关系 + 参数info记录了进程的MMU +**************************************************************************************************/ VOID OsUnmapPageLocked(LosFilePage *page, LosMapInfo *info) { if (page == NULL || info == NULL) { @@ -47,88 +51,88 @@ VOID OsUnmapPageLocked(LosFilePage *page, LosMapInfo *info) LOS_ListDelete(&info->node); LOS_AtomicDec(&page->vmPage->refCounts); LOS_ArchMmuUnmap(info->archMmu, info->vaddr, 1); - LOS_MemFree(m_aucSysMem0, info); + LOS_MemFree(m_aucSysMem0, info);//释放虚拟 } - +///解除文件页在所有进程的映射 VOID OsUnmapAllLocked(LosFilePage *page) { LosMapInfo *info = NULL; LosMapInfo *next = NULL; LOS_DL_LIST *immap = &page->i_mmap; - - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(info, next, immap, LosMapInfo, node) { + + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(info, next, immap, LosMapInfo, node) {//遍历 immap->info 链表 OsUnmapPageLocked(page, info); } } /* add a new lru node to lru list, lruType can be file or anon */ -VOID OsLruCacheAdd(LosFilePage *fpage, enum OsLruList lruType) +VOID OsLruCacheAdd(LosFilePage *fpage, enum OsLruList lruType)//在lru列表中添加一个新的lru节点,lruType可以是文件或匿名 { UINT32 intSave; - LosVmPhysSeg *physSeg = fpage->physSeg; - LosVmPage *page = fpage->vmPage; + LosVmPhysSeg *physSeg = fpage->physSeg; //得到页面对应段 + LosVmPage *page = fpage->vmPage; //得到物理页面 - LOS_SpinLockSave(&physSeg->lruLock, &intSave); - OsSetPageActive(page); - OsCleanPageReferenced(page); - physSeg->lruSize[lruType]++; - LOS_ListTailInsert(&physSeg->lruList[lruType], &fpage->lru); + LOS_SpinLockSave(&physSeg->lruLock, &intSave);//自旋锁:最多只能被一个内核持有,CPU内核 互斥锁 + OsSetPageActive(page); //设置页面为活动页 + OsCleanPageReferenced(page);//清除页面被引用位 + physSeg->lruSize[lruType]++; //lruType页总size++ + LOS_ListTailInsert(&physSeg->lruList[lruType], &fpage->lru);//加入lruType页双循环链表中 - LOS_SpinUnlockRestore(&physSeg->lruLock, intSave); + LOS_SpinUnlockRestore(&physSeg->lruLock, intSave);//解锁 } -/* delete a lru node, caller need hold lru_lock */ -VOID OsLruCacheDel(LosFilePage *fpage) +/* dellete a lru node, caller need hold lru_lock */ +VOID OsLruCacheDel(LosFilePage *fpage)//删除lru节点,调用者需要拿到lru锁 { - LosVmPhysSeg *physSeg = fpage->physSeg; - int type = OsIsPageActive(fpage->vmPage) ? VM_LRU_ACTIVE_FILE : VM_LRU_INACTIVE_FILE; + LosVmPhysSeg *physSeg = fpage->physSeg; //得到页面对应段 + int type = OsIsPageActive(fpage->vmPage) ? VM_LRU_ACTIVE_FILE : VM_LRU_INACTIVE_FILE;//得到页面LRU类型 - physSeg->lruSize[type]--; - LOS_ListDelete(&fpage->lru); + physSeg->lruSize[type]--; //type页总size-- + LOS_ListDelete(&fpage->lru);//将自己从lru链表中摘出来 } - +///非活动文件页低于活动文件页吗 BOOL OsInactiveListIsLow(LosVmPhysSeg *physSeg) { return (physSeg->lruSize[VM_LRU_ACTIVE_FILE] > - physSeg->lruSize[VM_LRU_INACTIVE_FILE]) ? TRUE : FALSE; + physSeg->lruSize[VM_LRU_INACTIVE_FILE]) ? TRUE : FALSE;//直接对比size,效率杠杠的 } /* move a page from inactive list to active list head */ -STATIC INLINE VOID OsMoveToActiveList(LosFilePage *fpage) +STATIC INLINE VOID OsMoveToActiveList(LosFilePage *fpage)//将页面从非活动列表移动到活动列表 { - LosVmPhysSeg *physSeg = fpage->physSeg; + LosVmPhysSeg *physSeg = fpage->physSeg; //得到页面对应段 - physSeg->lruSize[VM_LRU_ACTIVE_FILE]++; - physSeg->lruSize[VM_LRU_INACTIVE_FILE]--; - LOS_ListDelete(&fpage->lru); - LOS_ListTailInsert(&physSeg->lruList[VM_LRU_ACTIVE_FILE], &fpage->lru); + physSeg->lruSize[VM_LRU_ACTIVE_FILE]++; //活动页总size++ + physSeg->lruSize[VM_LRU_INACTIVE_FILE]--; //不活动页总size-- + LOS_ListDelete(&fpage->lru); //将自己从lru链表中摘出来 + LOS_ListTailInsert(&physSeg->lruList[VM_LRU_ACTIVE_FILE], &fpage->lru);//加入活动页双循环链表中 } /* move a page from active list to inactive list head */ -STATIC INLINE VOID OsMoveToInactiveList(LosFilePage *fpage) +STATIC INLINE VOID OsMoveToInactiveList(LosFilePage *fpage)//将页面从活动列表移动到非活动列表 { - LosVmPhysSeg *physSeg = fpage->physSeg; + LosVmPhysSeg *physSeg = fpage->physSeg; //得到页面对应段 - physSeg->lruSize[VM_LRU_ACTIVE_FILE]--; - physSeg->lruSize[VM_LRU_INACTIVE_FILE]++; - LOS_ListDelete(&fpage->lru); - LOS_ListTailInsert(&physSeg->lruList[VM_LRU_INACTIVE_FILE], &fpage->lru); + physSeg->lruSize[VM_LRU_ACTIVE_FILE]--; //活动页总size-- + physSeg->lruSize[VM_LRU_INACTIVE_FILE]++; //不活动页总size++ + LOS_ListDelete(&fpage->lru); //将自己从lru链表中摘出来 + LOS_ListTailInsert(&physSeg->lruList[VM_LRU_INACTIVE_FILE], &fpage->lru);//加入不活动页双循环链表中 } -/* move a page to the most active pos in lru list(active head) */ +/* move a page to the most active pos in lru list(active head) *///将页面移至lru列表中最活跃的位置 STATIC INLINE VOID OsMoveToActiveHead(LosFilePage *fpage) { - LosVmPhysSeg *physSeg = fpage->physSeg; - LOS_ListDelete(&fpage->lru); - LOS_ListTailInsert(&physSeg->lruList[VM_LRU_ACTIVE_FILE], &fpage->lru); + LosVmPhysSeg *physSeg = fpage->physSeg; //得到页面对应段 + LOS_ListDelete(&fpage->lru); //将自己从lru链表中摘出来 + LOS_ListTailInsert(&physSeg->lruList[VM_LRU_ACTIVE_FILE], &fpage->lru);//加入活动页双循环链表中 } /* move a page to the most active pos in lru list(inactive head) */ -STATIC INLINE VOID OsMoveToInactiveHead(LosFilePage *fpage) +STATIC INLINE VOID OsMoveToInactiveHead(LosFilePage *fpage)//鸿蒙会从inactive链表的尾部开始进行回收,跟linux一样 { - LosVmPhysSeg *physSeg = fpage->physSeg; - LOS_ListDelete(&fpage->lru); - LOS_ListTailInsert(&physSeg->lruList[VM_LRU_INACTIVE_FILE], &fpage->lru); + LosVmPhysSeg *physSeg = fpage->physSeg; //得到页面对应段 + LOS_ListDelete(&fpage->lru); //将自己从lru链表中摘出来 + LOS_ListTailInsert(&physSeg->lruList[VM_LRU_INACTIVE_FILE], &fpage->lru);//加入不活动页双循环链表中 } /* page referced add: (call by page cache get) @@ -138,7 +142,7 @@ ref:0, act:0 --> ref:1, act:0 ref:1, act:0 --> ref:0, act:1 ref:0, act:1 --> ref:1, act:1 */ -VOID OsPageRefIncLocked(LosFilePage *fpage) +VOID OsPageRefIncLocked(LosFilePage *fpage)// ref ,act 标签转换功能 { BOOL isOrgActive; UINT32 intSave; @@ -148,16 +152,16 @@ VOID OsPageRefIncLocked(LosFilePage *fpage) return; } - LOS_SpinLockSave(&fpage->physSeg->lruLock, &intSave); + LOS_SpinLockSave(&fpage->physSeg->lruLock, &intSave);//要处理lruList,先拿锁 - page = fpage->vmPage; - isOrgActive = OsIsPageActive(page); + page = fpage->vmPage;//拿到物理页框 + isOrgActive = OsIsPageActive(page);//页面是否在活动 - if (OsIsPageReferenced(page) && !OsIsPageActive(page)) { - OsCleanPageReferenced(page); - OsSetPageActive(page); + if (OsIsPageReferenced(page) && !OsIsPageActive(page)) {//身兼 不活动和引用标签 + OsCleanPageReferenced(page);//撕掉引用标签 ref:1, act:0 --> ref:0, act:1 + OsSetPageActive(page); //贴上活动标签 } else if (!OsIsPageReferenced(page)) { - OsSetPageReferenced(page); + OsSetPageReferenced(page);//ref:0, act:0 --> ref:1, act:0 } if (!isOrgActive && OsIsPageActive(page)) { @@ -175,14 +179,14 @@ VOID OsPageRefIncLocked(LosFilePage *fpage) LOS_SpinUnlockRestore(&fpage->physSeg->lruLock, intSave); } -/* page referced dec: (call by shrinker) +/* page referced dec: (call by thrinker) ----------inactive----------|----------active------------ [ref:0,act:0], [ref:1,act:0]|[ref:0,act:1], [ref:1,act:1] ref:1, act:1 --> ref:0, act:1 ref:0, act:1 --> ref:1, act:0 ref:1, act:0 --> ref:0, act:0 */ -VOID OsPageRefDecNoLock(LosFilePage *fpage) +VOID OsPageRefDecNoLock(LosFilePage *fpage) // ref ,act 标签转换功能 { BOOL isOrgActive; LosVmPage *page = NULL; @@ -194,7 +198,7 @@ VOID OsPageRefDecNoLock(LosFilePage *fpage) page = fpage->vmPage; isOrgActive = OsIsPageActive(page); - if (!OsIsPageReferenced(page) && OsIsPageActive(page)) { + if (!OsIsPageReferenced(page) && OsIsPageActive(page)) {//[ref:0,act:1]的情况 OsCleanPageActive(page); OsSetPageReferenced(page); } else if (OsIsPageReferenced(page)) { @@ -205,39 +209,39 @@ VOID OsPageRefDecNoLock(LosFilePage *fpage) OsMoveToInactiveList(fpage); } } - +///缩小活动页链表 VOID OsShrinkActiveList(LosVmPhysSeg *physSeg, int nScan) { LosFilePage *fpage = NULL; LosFilePage *fnext = NULL; LOS_DL_LIST *activeFile = &physSeg->lruList[VM_LRU_ACTIVE_FILE]; - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, activeFile, LosFilePage, lru) { - if (LOS_SpinTrylock(&fpage->mapping->list_lock) != LOS_OK) { - continue; + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, activeFile, LosFilePage, lru) {//一页一页处理 + if (LOS_SpinTrylock(&fpage->mapping->list_lock) != LOS_OK) {//尝试获取文件页所在的page_mapping锁 + continue;//接着处理下一文件页 } - /* happened when caller hold cache lock and try reclaim this page */ - if (OsIsPageLocked(fpage->vmPage)) { - LOS_SpinUnlock(&fpage->mapping->list_lock); - continue; + /* happend when caller hold cache lock and try reclaim this page *///调用方持有缓存锁并尝试回收此页时发生 + if (OsIsPageLocked(fpage->vmPage)) {//页面是否被锁 + LOS_SpinUnlock(&fpage->mapping->list_lock);//失败时,一定要释放page_mapping锁. + continue;//接着处理下一文件页 } - if (OsIsPageMapped(fpage) && (fpage->flags & VM_MAP_REGION_FLAG_PERM_EXECUTE)) { - LOS_SpinUnlock(&fpage->mapping->list_lock); - continue; + if (OsIsPageMapped(fpage) && (fpage->flags & VM_MAP_REGION_FLAG_PERM_EXECUTE)) {//文件页是否被映射而且是个可执行文件 ? + LOS_SpinUnlock(&fpage->mapping->list_lock);//是时,一定要释放page_mapping锁. + continue;//接着处理下一文件页 } + //找了可以收缩的文件页 + OsPageRefDecNoLock(fpage); //将页面移到未活动文件链表 - OsPageRefDecNoLock(fpage); - - LOS_SpinUnlock(&fpage->mapping->list_lock); + LOS_SpinUnlock(&fpage->mapping->list_lock); //释放page_mapping锁. if (--nScan <= 0) { break; } } } - +///缩小未活动页链表 int OsShrinkInactiveList(LosVmPhysSeg *physSeg, int nScan, LOS_DL_LIST *list) { UINT32 nrReclaimed = 0; @@ -248,36 +252,36 @@ int OsShrinkInactiveList(LosVmPhysSeg *physSeg, int nScan, LOS_DL_LIST *list) LosFilePage *ftemp = NULL; LOS_DL_LIST *inactive_file = &physSeg->lruList[VM_LRU_INACTIVE_FILE]; - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, inactive_file, LosFilePage, lru) { + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, inactive_file, LosFilePage, lru) {//遍历链表一页一页处理 flock = &fpage->mapping->list_lock; - if (LOS_SpinTrylock(flock) != LOS_OK) { - continue; + if (LOS_SpinTrylock(flock) != LOS_OK) {//尝试获取文件页所在的page_mapping锁 + continue;//接着处理下一文件页 } - page = fpage->vmPage; - if (OsIsPageLocked(page)) { + page = fpage->vmPage;//获取物理页框 + if (OsIsPageLocked(page)) {//页面是否被锁 LOS_SpinUnlock(flock); - continue; + continue;//接着处理下一文件页 } if (OsIsPageMapped(fpage) && (OsIsPageDirty(page) || (fpage->flags & VM_MAP_REGION_FLAG_PERM_EXECUTE))) { - LOS_SpinUnlock(flock); - continue; + LOS_SpinUnlock(flock);//文件页是否被映射而且是个脏页获取是个可执行文件 ? + continue;//接着处理下一文件页 } - if (OsIsPageDirty(page)) { - ftemp = OsDumpDirtyPage(fpage); - if (ftemp != NULL) { - LOS_ListTailInsert(list, &ftemp->node); + if (OsIsPageDirty(page)) {//是脏页 + ftemp = OsDumpDirtyPage(fpage);//备份脏页 + if (ftemp != NULL) {//备份成功了 + LOS_ListTailInsert(list, &ftemp->node);//将脏页挂到参数链表上带走 } } - OsDeletePageCacheLru(fpage); + OsDeletePageCacheLru(fpage);//将文件页从LRU和pagecache上摘除 LOS_SpinUnlock(flock); - nrReclaimed++; + nrReclaimed++;//成功回收了一页 - if (--nScan <= 0) { + if (--nScan <= 0) {//继续回收 break; } } @@ -286,48 +290,48 @@ int OsShrinkInactiveList(LosVmPhysSeg *physSeg, int nScan, LOS_DL_LIST *list) } #ifdef LOSCFG_FS_VFS -int OsTryShrinkMemory(size_t nPage) +int OsTryShrinkMemory(size_t nPage)//尝试收缩文件页 { UINT32 intSave; size_t totalPages; size_t nReclaimed = 0; LosVmPhysSeg *physSeg = NULL; UINT32 index; - LOS_DL_LIST_HEAD(dirtyList); + LOS_DL_LIST_HEAD(dirtyList);//初始化脏页链表,上面将挂所有脏页用于同步到磁盘后回收 LosFilePage *fpage = NULL; LosFilePage *fnext = NULL; if (nPage == 0) { - nPage = VM_FILEMAP_MIN_SCAN; + nPage = VM_FILEMAP_MIN_SCAN;// } if (nPage > VM_FILEMAP_MAX_SCAN) { nPage = VM_FILEMAP_MAX_SCAN; } - for (index = 0; index < g_vmPhysSegNum; index++) { - physSeg = &g_vmPhysSeg[index]; + for (index = 0; index < g_vmPhysSegNum; index++) {//遍历整个物理段组 + physSeg = &g_vmPhysSeg[index];//一段段来 LOS_SpinLockSave(&physSeg->lruLock, &intSave); - totalPages = physSeg->lruSize[VM_LRU_ACTIVE_FILE] + physSeg->lruSize[VM_LRU_INACTIVE_FILE]; - if (totalPages < VM_FILEMAP_MIN_SCAN) { + totalPages = physSeg->lruSize[VM_LRU_ACTIVE_FILE] + physSeg->lruSize[VM_LRU_INACTIVE_FILE];//统计所有文件页 + if (totalPages < VM_FILEMAP_MIN_SCAN) {//文件页占用内存不多的情况下,怎么处理? LOS_SpinUnlockRestore(&physSeg->lruLock, intSave); - continue; + continue;//放过这一段,找下一段 } if (OsInactiveListIsLow(physSeg)) { - OsShrinkActiveList(physSeg, (nPage < VM_FILEMAP_MIN_SCAN) ? VM_FILEMAP_MIN_SCAN : nPage); + OsShrinkActiveList(physSeg, (nPage < VM_FILEMAP_MIN_SCAN) ? VM_FILEMAP_MIN_SCAN : nPage);//缩小活动页 } - nReclaimed += OsShrinkInactiveList(physSeg, nPage, &dirtyList); + nReclaimed += OsShrinkInactiveList(physSeg, nPage, &dirtyList);//缩小未活动页,带出脏页链表 LOS_SpinUnlockRestore(&physSeg->lruLock, intSave); - if (nReclaimed >= nPage) { - break; + if (nReclaimed >= nPage) {//够了,够了,达到目的了. + break;//退出收缩 } } - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, &dirtyList, LosFilePage, node) { - OsDoFlushDirtyPage(fpage); + LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(fpage, fnext, &dirtyList, LosFilePage, node) {//遍历处理脏页数据 + OsDoFlushDirtyPage(fpage);//冲洗脏页数据,将脏页数据回写磁盘 } return nReclaimed; @@ -340,4 +344,4 @@ int OsTryShrinkMemory(size_t nPage) #endif #endif -#endif +#endif \ No newline at end of file diff --git a/src/kernel_liteos_a/kernel/base/vm/los_vm_syscall.c b/src/kernel_liteos_a/kernel/base/vm/los_vm_syscall.c index 6aa23993..72bdd10b 100644 --- a/src/kernel_liteos_a/kernel/base/vm/los_vm_syscall.c +++ b/src/kernel_liteos_a/kernel/base/vm/los_vm_syscall.c @@ -65,7 +65,7 @@ STATUS_T OsCheckMMapParams(VADDR_T *vaddr, unsigned long flags, size_t len, unsi return -EINVAL; } - if ((flags & MAP_SUPPORT_MASK) == 0) { + if ((flags & MAP_SUPPORT_MASK) == 0) {//映射权限限制 return -EINVAL; } if (((flags & MAP_SHARED_PRIVATE) == 0) || ((flags & MAP_SHARED_PRIVATE) == MAP_SHARED_PRIVATE)) { @@ -95,20 +95,54 @@ STATUS_T OsNamedMmapingPermCheck(struct file *filep, unsigned long flags, unsign return LOS_OK; } - +///匿名映射 STATUS_T OsAnonMMap(LosVmMapRegion *region) { LOS_SetRegionTypeAnon(region); return LOS_OK; } +/** + mmap基础概念: + 一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系. + 实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上, + 即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间, + 从而可以实现不同进程间的文件共享。 + + https://www.cnblogs.com/huxiao-tee/p/4660352.html + http://abcdxyzk.github.io/blog/2015/09/11/kernel-mm-mmap/ + + 参数 描述 + addr 指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。 + length 代表将文件中多大的部分映射到内存。 + prot 用于设置内存段的访问权限,有如下权限: + PROT_EXEC 映射区域可被执行 + PROT_READ 映射区域可被读取 + PROT_WRITE 映射区域可被写入 + PROT_NONE 映射区域不能存取 + + flags 控制程序对内存段的改变所造成的影响,有如下属性: + MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。 + MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。 + MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。 + MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。 + MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 + MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。 + + fd: 要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。 + 有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。 + + offset 文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是PAGE_SIZE的整数倍。 + 成功返回:虚拟内存地址,这地址是页对齐。 + 失败返回:(void *)-1。 + */ VADDR_T LOS_MMap(VADDR_T vaddr, size_t len, unsigned prot, unsigned long flags, int fd, unsigned long pgoff) { STATUS_T status; VADDR_T resultVaddr; UINT32 regionFlags; - LosVmMapRegion *newRegion = NULL; - struct file *filep = NULL; + LosVmMapRegion *newRegion = NULL;//应用的内存分配对应到内核就是分配一个线性区 + struct file *filep = NULL;// inode : file = 1:N ,一对多关系,一个inode可以被多个进程打开,返回不同的file但都指向同一个inode LosVmSpace *vmSpace = OsCurrProcessGet()->vmSpace; len = ROUNDUP(len, PAGE_SIZE); @@ -116,9 +150,9 @@ VADDR_T LOS_MMap(VADDR_T vaddr, size_t len, unsigned prot, unsigned long flags, if (checkRst != LOS_OK) { return checkRst; } - - if (LOS_IsNamedMapping(flags)) { - status = fs_getfilep(fd, &filep); + + if (LOS_IsNamedMapping(flags)) {//是否文件映射 + status = fs_getfilep(fd, &filep);//获取文件描述符和状态 if (status < 0) { return -EBADF; } @@ -131,30 +165,30 @@ VADDR_T LOS_MMap(VADDR_T vaddr, size_t len, unsigned prot, unsigned long flags, (VOID)LOS_MuxAcquire(&vmSpace->regionMux); /* user mode calls mmap to release heap physical memory without releasing heap virtual space */ - status = OsUserHeapFree(vmSpace, vaddr, len); - if (status == LOS_OK) { + status = OsUserHeapFree(vmSpace, vaddr, len);//用户模式释放堆物理内存而不释放堆虚拟空间 + if (status == LOS_OK) {//OsUserHeapFree 干两件事 1.解除映射关系 2.释放物理页 resultVaddr = vaddr; goto MMAP_DONE; } - - regionFlags = OsCvtProtFlagsToRegionFlags(prot, flags); - newRegion = LOS_RegionAlloc(vmSpace, vaddr, len, regionFlags, pgoff); + //地址不在堆区 + regionFlags = OsCvtProtFlagsToRegionFlags(prot, flags);//将参数flag转换Region的flag + newRegion = LOS_RegionAlloc(vmSpace, vaddr, len, regionFlags, pgoff);//分配一个线性区 if (newRegion == NULL) { - resultVaddr = (VADDR_T)-ENOMEM; + resultVaddr = (VADDR_T)-ENOMEM;//ENOMEM:内存溢出 goto MMAP_DONE; } newRegion->regionFlags |= VM_MAP_REGION_FLAG_MMAP; - resultVaddr = newRegion->range.base; + resultVaddr = newRegion->range.base;//线性区基地址为分配的地址 if (LOS_IsNamedMapping(flags)) { - status = OsNamedMMap(filep, newRegion); + status = OsNamedMMap(filep, newRegion);//文件映射 } else { - status = OsAnonMMap(newRegion); + status = OsAnonMMap(newRegion);//匿名映射 } if (status != LOS_OK) { - LOS_RbDelNode(&vmSpace->regionRbTree, &newRegion->rbNode); - LOS_RegionFree(vmSpace, newRegion); + LOS_RbDelNode(&vmSpace->regionRbTree, &newRegion->rbNode);//从红黑树和双循环链表中删除 + LOS_RegionFree(vmSpace, newRegion);//释放 resultVaddr = (VADDR_T)-ENOMEM; goto MMAP_DONE; } @@ -163,7 +197,7 @@ MMAP_DONE: (VOID)LOS_MuxRelease(&vmSpace->regionMux); return resultVaddr; } - +///解除映射关系 STATUS_T LOS_UnMMap(VADDR_T addr, size_t size) { if ((addr <= 0) || (size == 0)) { @@ -172,7 +206,6 @@ STATUS_T LOS_UnMMap(VADDR_T addr, size_t size) return OsUnMMap(OsCurrProcessGet()->vmSpace, addr, size); } - STATIC INLINE BOOL OsProtMprotectPermCheck(unsigned long prot, LosVmMapRegion *region) { UINT32 protFlags = 0; @@ -186,20 +219,28 @@ STATIC INLINE BOOL OsProtMprotectPermCheck(unsigned long prot, LosVmMapRegion *r return ((protFlags & permFlags) == protFlags); } - +/// 收缩堆区 VOID *OsShrinkHeap(VOID *addr, LosVmSpace *space) { VADDR_T newBrk, oldBrk; - newBrk = LOS_Align((VADDR_T)(UINTPTR)addr, PAGE_SIZE); - oldBrk = LOS_Align(space->heapNow, PAGE_SIZE); - if (LOS_UnMMap(newBrk, (oldBrk - newBrk)) < 0) { - return (void *)(UINTPTR)space->heapNow; + newBrk = LOS_Align((VADDR_T)(UINTPTR)addr, PAGE_SIZE);//新堆顶 + oldBrk = LOS_Align(space->heapNow, PAGE_SIZE);//旧堆顶 + if (LOS_UnMMap(newBrk, (oldBrk - newBrk)) < 0) {//解除相差区的映射 + return (void *)(UINTPTR)space->heapNow;//解除失败就持续现有的 } - space->heapNow = (VADDR_T)(UINTPTR)addr; + space->heapNow = (VADDR_T)(UINTPTR)addr;//返回新堆顶 return addr; } +/** + 用户进程向内核申请空间,进一步说用于扩展用户堆栈空间,或者回收用户堆栈空间 + 扩展当前进程的堆空间 + 一个进程所有的线性区都在进程指定的线性地址范围内, + 线性区之间是不会有地址的重叠的,开始都是连续的,随着进程的运行出现了释放再分配的情况 + 由此出现了断断续续的线性区,内核回收线性区时会检测是否和周边的线性区可合并成一个更大 + 的线性区用于分配。 + */ VOID *LOS_DoBrk(VOID *addr) { LosVmSpace *space = OsCurrProcessGet()->vmSpace; @@ -209,60 +250,60 @@ VOID *LOS_DoBrk(VOID *addr) VOID *alignAddr = NULL; VOID *shrinkAddr = NULL; - if (addr == NULL) { - return (void *)(UINTPTR)space->heapNow; + if (addr == NULL) {//参数地址未传情况 + return (void *)(UINTPTR)space->heapNow;//以现有指向地址为基础进行扩展 } - if ((UINTPTR)addr < (UINTPTR)space->heapBase) { + if ((UINTPTR)addr < (UINTPTR)space->heapBase) {//heapBase是堆区的开始地址,所以参数地址不能低于它 return (VOID *)-ENOMEM; } - size = (UINTPTR)addr - (UINTPTR)space->heapBase; - size = ROUNDUP(size, PAGE_SIZE); - alignAddr = (CHAR *)(UINTPTR)(space->heapBase) + size; + size = (UINTPTR)addr - (UINTPTR)space->heapBase;//算出大小 + size = ROUNDUP(size, PAGE_SIZE); //圆整size + alignAddr = (CHAR *)(UINTPTR)(space->heapBase) + size;//得到新的线性区的结束地址 PRINT_INFO("brk addr %p , size 0x%x, alignAddr %p, align %d\n", addr, size, alignAddr, PAGE_SIZE); (VOID)LOS_MuxAcquire(&space->regionMux); - if (addr < (VOID *)(UINTPTR)space->heapNow) { - shrinkAddr = OsShrinkHeap(addr, space); + if (addr < (VOID *)(UINTPTR)space->heapNow) {//如果地址小于堆区现地址 + shrinkAddr = OsShrinkHeap(addr, space);//收缩堆区 (VOID)LOS_MuxRelease(&space->regionMux); return shrinkAddr; } - if ((UINTPTR)alignAddr >= space->mapBase) { - VM_ERR("Process heap memory space is insufficient"); + if ((UINTPTR)alignAddr >= space->mapBase) {//参数地址 大于映射区地址 + VM_ERR("Process heap memory space is insufficient");//进程堆空间不足 ret = (VOID *)-ENOMEM; goto REGION_ALLOC_FAILED; } - if (space->heapBase == space->heapNow) { - region = LOS_RegionAlloc(space, space->heapBase, size, - VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE | + if (space->heapBase == space->heapNow) {//往往是第一次调用本函数才会出现,因为初始化时 heapBase = heapNow + region = LOS_RegionAlloc(space, space->heapBase, size,//分配一个可读/可写/可使用的线性区,只需分配一次 + VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |//线性区的大小由range.size决定 VM_MAP_REGION_FLAG_FIXED | VM_MAP_REGION_FLAG_PERM_USER, 0); if (region == NULL) { ret = (VOID *)-ENOMEM; VM_ERR("LOS_RegionAlloc failed"); goto REGION_ALLOC_FAILED; } - region->regionFlags |= VM_MAP_REGION_FLAG_HEAP; - space->heap = region; + region->regionFlags |= VM_MAP_REGION_FLAG_HEAP;//贴上线性区类型为堆区的标签,注意一个线性区可以有多种标签 + space->heap = region;//指定线性区为堆区 } - space->heapNow = (VADDR_T)(UINTPTR)alignAddr; - space->heap->range.size = size; - ret = (VOID *)(UINTPTR)space->heapNow; + space->heapNow = (VADDR_T)(UINTPTR)alignAddr;//更新堆区顶部位置 + space->heap->range.size = size; //更新堆区大小,经此操作线性区变大或缩小了 + ret = (VOID *)(UINTPTR)space->heapNow;//返回堆顶 REGION_ALLOC_FAILED: (VOID)LOS_MuxRelease(&space->regionMux); return ret; } - +/// 继承老线性区的标签 STATIC UINT32 OsInheritOldRegionName(UINT32 oldRegionFlags) { UINT32 vmFlags = 0; - if (oldRegionFlags & VM_MAP_REGION_FLAG_HEAP) { - vmFlags |= VM_MAP_REGION_FLAG_HEAP; + if (oldRegionFlags & VM_MAP_REGION_FLAG_HEAP) { //如果是从大堆区中申请的 + vmFlags |= VM_MAP_REGION_FLAG_HEAP; //线性区则贴上堆区标签 } else if (oldRegionFlags & VM_MAP_REGION_FLAG_STACK) { vmFlags |= VM_MAP_REGION_FLAG_STACK; } else if (oldRegionFlags & VM_MAP_REGION_FLAG_TEXT) { @@ -277,7 +318,7 @@ STATIC UINT32 OsInheritOldRegionName(UINT32 oldRegionFlags) return vmFlags; } - +///修改内存段的访问权限 INT32 LOS_DoMprotect(VADDR_T vaddr, size_t len, unsigned long prot) { LosVmSpace *space = OsCurrProcessGet()->vmSpace; @@ -287,7 +328,7 @@ INT32 LOS_DoMprotect(VADDR_T vaddr, size_t len, unsigned long prot) int ret; (VOID)LOS_MuxAcquire(&space->regionMux); - region = LOS_RegionFind(space, vaddr); + region = LOS_RegionFind(space, vaddr);//通过虚拟地址找到线性区 if (!IS_ALIGNED(vaddr, PAGE_SIZE) || (region == NULL) || (vaddr > vaddr + len)) { ret = -EINVAL; goto OUT_MPROTECT; @@ -297,19 +338,18 @@ INT32 LOS_DoMprotect(VADDR_T vaddr, size_t len, unsigned long prot) ret = -EINVAL; goto OUT_MPROTECT; } - + //如果是堆区或VDSO区,说明区内容是不能修改的 if ((region->regionFlags & VM_MAP_REGION_FLAG_VDSO) || (region->regionFlags & VM_MAP_REGION_FLAG_HEAP)) { ret = -EPERM; goto OUT_MPROTECT; } - + //如果是共享文件,说明内容也不能修改 if (LOS_IsRegionTypeFile(region) && (region->regionFlags & VM_MAP_REGION_FLAG_SHARED)) { if (!OsProtMprotectPermCheck(prot, region)) { ret = -EACCES; goto OUT_MPROTECT; } } - len = LOS_Align(len, PAGE_SIZE); /* can't operation cross region */ if ((region->range.base + region->range.size) < (vaddr + len)) { @@ -318,11 +358,11 @@ INT32 LOS_DoMprotect(VADDR_T vaddr, size_t len, unsigned long prot) } /* if only move some part of region, we need to split first */ - if (region->range.size > len) { - OsVmRegionAdjust(space, vaddr, len); + if (region->range.size > len) {//如果只修改部分区域,我们需要先拆分区 + OsVmRegionAdjust(space, vaddr, len);//调整下线性区范围 } - vmFlags = OsCvtProtFlagsToRegionFlags(prot, 0); + vmFlags = OsCvtProtFlagsToRegionFlags(prot, 0);//转换FLAGS vmFlags |= (region->regionFlags & VM_MAP_REGION_FLAG_SHARED) ? VM_MAP_REGION_FLAG_SHARED : 0; vmFlags |= OsInheritOldRegionName(region->regionFlags); region = LOS_RegionFind(space, vaddr); @@ -332,7 +372,7 @@ INT32 LOS_DoMprotect(VADDR_T vaddr, size_t len, unsigned long prot) } region->regionFlags = vmFlags; count = len >> PAGE_SHIFT; - ret = LOS_ArchMmuChangeProt(&space->archMmu, vaddr, count, region->regionFlags); + ret = LOS_ArchMmuChangeProt(&space->archMmu, vaddr, count, region->regionFlags);//修改访问权限实体函数 if (ret) { ret = -ENOMEM; goto OUT_MPROTECT; @@ -387,7 +427,7 @@ STATUS_T OsMremapCheck(VADDR_T addr, size_t oldLen, VADDR_T newAddr, size_t newL } } - /* avoid new region overlapping with the old one */ + /* avoid new region overlaping with the old one */ if (flags & MREMAP_FIXED) { if (((region->range.base + region->range.size) > newAddr) && (region->range.base < (newAddr + newLen))) { @@ -401,7 +441,7 @@ STATUS_T OsMremapCheck(VADDR_T addr, size_t oldLen, VADDR_T newAddr, size_t newL return LOS_OK; } - +///重新映射虚拟内存地址。 VADDR_T LOS_DoMremap(VADDR_T oldAddress, size_t oldSize, size_t newSize, int flags, VADDR_T newAddr) { LosVmMapRegion *regionOld = NULL; @@ -496,7 +536,7 @@ OUT_MREMAP: (VOID)LOS_MuxRelease(&space->regionMux); return ret; } - +///输出内存线性区 VOID LOS_DumpMemRegion(VADDR_T vaddr) { LosVmSpace *space = NULL; @@ -506,12 +546,11 @@ VOID LOS_DumpMemRegion(VADDR_T vaddr) return; } - if (LOS_IsRangeInSpace(space, ROUNDDOWN(vaddr, MB), MB) == FALSE) { + if (LOS_IsRangeInSpace(space, ROUNDDOWN(vaddr, MB), MB) == FALSE) {//是否在空间范围内 return; } - OsDumpPte(vaddr); - OsDumpAspace(space); + OsDumpPte(vaddr);//dump L1 L2 + OsDumpAspace(space);//dump 空间 } -#endif - +#endif \ No newline at end of file diff --git a/src/kernel_liteos_a/kernel/base/vm/oom.c b/src/kernel_liteos_a/kernel/base/vm/oom.c index 72996781..60d80128 100644 --- a/src/kernel_liteos_a/kernel/base/vm/oom.c +++ b/src/kernel_liteos_a/kernel/base/vm/oom.c @@ -47,8 +47,8 @@ #ifdef LOSCFG_KERNEL_VM -LITE_OS_SEC_BSS OomCB *g_oomCB = NULL; -static SPIN_LOCK_INIT(g_oomSpinLock); +LITE_OS_SEC_BSS OomCB *g_oomCB = NULL; //全局内存溢出控制块 +static SPIN_LOCK_INIT(g_oomSpinLock);//内存溢出自旋锁 LITE_OS_SEC_TEXT_MINOR STATIC UINT32 OomScoreProcess(LosProcessCB *candidateProcess) { @@ -57,20 +57,20 @@ LITE_OS_SEC_TEXT_MINOR STATIC UINT32 OomScoreProcess(LosProcessCB *candidateProc #ifndef LOSCFG_KERNEL_SMP (VOID)LOS_MuxAcquire(&candidateProcess->vmSpace->regionMux); #endif - /* we only consider actual physical memory here. */ + /* we only consider actual physical memory here. */ //只考虑实际的物理内存 OsUProcessPmUsage(candidateProcess->vmSpace, NULL, &actualPm); #ifndef LOSCFG_KERNEL_SMP (VOID)LOS_MuxRelease(&candidateProcess->vmSpace->regionMux); #endif return actualPm; } - +///用于设置 g_oomCB->processVictimCB 回调函数 LITE_OS_SEC_TEXT_MINOR STATIC UINT32 OomKillProcess(UINTPTR param) { /* we will not kill process, and do nothing here */ return LOS_OK; } - +///强制收缩内存 LITE_OS_SEC_TEXT_MINOR STATIC UINT32 OomForceShrinkMemory(VOID) { UINT32 i; @@ -80,13 +80,14 @@ LITE_OS_SEC_TEXT_MINOR STATIC UINT32 OomForceShrinkMemory(VOID) * TryShrinkMemory maybe reclaim 0 pages in the first time from active list * to inactive list, and in the second time reclaim memory from inactive list. */ + //TryShrinkMemory可能会在第一时间从活动列表中回收0页到非活动列表,并在第二次从非活动列表中回收内存。 for (i = 0; i < MAX_SHRINK_PAGECACHE_TRY; i++) { reclaimMemPages += OsTryShrinkMemory(0); } return reclaimMemPages; } - +///内存不足时回收页高速缓存 LITE_OS_SEC_TEXT_MINOR STATIC BOOL OomReclaimPageCache(VOID) { UINT32 totalPm = 0; @@ -96,43 +97,44 @@ LITE_OS_SEC_TEXT_MINOR STATIC BOOL OomReclaimPageCache(VOID) UINT32 i; for (i = 0; i < MAX_SHRINK_PAGECACHE_TRY; i++) { - OsVmPhysUsedInfoGet(&usedPm, &totalPm); - isReclaimMemory = ((totalPm - usedPm) << PAGE_SHIFT) < g_oomCB->reclaimMemThreshold; - if (isReclaimMemory) { + OsVmPhysUsedInfoGet(&usedPm, &totalPm);//获取总的和已经使用的物理内存数量 + isReclaimMemory = ((totalPm - usedPm) << PAGE_SHIFT) < g_oomCB->reclaimMemThreshold;//检查是否过了回收门槛 + if (isReclaimMemory) {//要回收了 /* * we do force memory reclaim from page cache here. * if we get memory, we will reclaim pagecache memory again. * if there is no memory to reclaim, we will return. */ - reclaimMemPages = OomForceShrinkMemory(); - if (reclaimMemPages > 0) { + //在这里强制从页缓存中回收内存, + reclaimMemPages = OomForceShrinkMemory();//强制回收内存 + if (reclaimMemPages > 0) {//如果得到内存,将再次回收pagecache内存 continue; } } - break; + break;//实在没有内存可回收 } - return isReclaimMemory; + return isReclaimMemory;//返回回收的数量 } /* - * check is low memory or not, if low memory, try to kill process. - * return is kill process or not. + * check is low memory or not, if low memory, try to kill process. + * return is kill process or not. */ -LITE_OS_SEC_TEXT_MINOR BOOL OomCheckProcess(VOID) +LITE_OS_SEC_TEXT_MINOR BOOL OomCheckProcess(VOID)//检查内存是否不足,如果内存不足,请尝试终止进程,返回是否kill进程 { UINT32 totalPm; UINT32 usedPm; BOOL isLowMemory = FALSE; /* - * spinlock the current core schedule, make sure oom process atomic - * spinlock other place entering OomCheckProcess, make sure oom process mutex + * spinlock the current core schedule, make sure oom process atomic //旋转锁定当前核心计划,确保oom进程原子化 + * spinlock other place entering OomCheckProcess, make sure oom process mutex //旋转锁定其他进入OomCheckProcess的地方,确保oom进程互斥 */ LOS_SpinLock(&g_oomSpinLock); /* first we will check if we need to reclaim pagecache memory */ - if (OomReclaimPageCache() == FALSE) { + if (OomReclaimPageCache() == FALSE) {// LOS_SpinUnlock(&g_oomSpinLock); goto NO_VICTIM_PROCESS; } @@ -140,9 +142,7 @@ LITE_OS_SEC_TEXT_MINOR BOOL OomCheckProcess(VOID) /* get free bytes */ OsVmPhysUsedInfoGet(&usedPm, &totalPm); isLowMemory = ((totalPm - usedPm) << PAGE_SHIFT) < g_oomCB->lowMemThreshold; - LOS_SpinUnlock(&g_oomSpinLock); - if (isLowMemory) { PRINTK("[oom] OS is in low memory state\n" "total physical memory: %#x(byte), used: %#x(byte)," @@ -155,14 +155,14 @@ NO_VICTIM_PROCESS: return isLowMemory; } -#ifdef LOSCFG_ENABLE_OOM_LOOP_TASK -STATIC VOID OomWriteEvent(VOID) +#ifdef LOSCFG_ENABLE_OOM_LOOP_TASK //内存溢出监测任务开关 +STATIC VOID OomWriteEvent(VOID) // OomTaskInit中创建的定时器回调 { - OsWriteResourceEvent(OS_RESOURCE_EVENT_OOM); + OsWriteResourceEvent(OS_RESOURCE_EVENT_OOM);//广播内存溢出事件 } #endif - -LITE_OS_SEC_TEXT_MINOR VOID OomInfodump(VOID) +//打印内存不足时的信息 +LITE_OS_SEC_TEXT_MINOR VOID OomInfodump(VOID) //打印内存溢出信息 { PRINTK("[oom] oom loop task status: %s\n" " oom low memory threshold: %#x(byte)\n" @@ -172,7 +172,7 @@ LITE_OS_SEC_TEXT_MINOR VOID OomInfodump(VOID) g_oomCB->lowMemThreshold, g_oomCB->reclaimMemThreshold, g_oomCB->checkInterval); } - +///设置低内存门槛 LITE_OS_SEC_TEXT_MINOR VOID OomSetLowMemThreashold(UINT32 lowMemThreshold) { if ((lowMemThreshold > OOM_DEFAULT_LOW_MEM_THRESHOLD_MAX)) { @@ -186,7 +186,7 @@ LITE_OS_SEC_TEXT_MINOR VOID OomSetLowMemThreashold(UINT32 lowMemThreshold) g_oomCB->lowMemThreshold); } } - +///设置回收内存的门槛 LITE_OS_SEC_TEXT_MINOR VOID OomSetReclaimMemThreashold(UINT32 reclaimMemThreshold) { UINT32 totalPm = 0; @@ -204,7 +204,7 @@ LITE_OS_SEC_TEXT_MINOR VOID OomSetReclaimMemThreashold(UINT32 reclaimMemThreshol g_oomCB->reclaimMemThreshold); } } - +///设置监控间隔 LITE_OS_SEC_TEXT_MINOR VOID OomSetCheckInterval(UINT32 checkInterval) { if ((checkInterval >= OOM_CHECK_MIN) && (checkInterval <= OOM_CHECK_MAX)) { @@ -216,7 +216,7 @@ LITE_OS_SEC_TEXT_MINOR VOID OomSetCheckInterval(UINT32 checkInterval) g_oomCB->checkInterval, OOM_CHECK_MIN, OOM_CHECK_MAX); } } - +///内存不足监控任务初始化, OOM 通过开一个软件定时器来检查内存的使用情况 LITE_OS_SEC_TEXT_MINOR UINT32 OomTaskInit(VOID) { g_oomCB = (OomCB *)LOS_MemAlloc(m_aucSysMem0, sizeof(OomCB)); @@ -225,28 +225,27 @@ LITE_OS_SEC_TEXT_MINOR UINT32 OomTaskInit(VOID) return LOS_NOK; } - g_oomCB->lowMemThreshold = OOM_DEFAULT_LOW_MEM_THRESHOLD; - g_oomCB->reclaimMemThreshold = OOM_DEFAULT_RECLAIM_MEM_THRESHOLD; - g_oomCB->checkInterval = OOM_DEFAULT_CHECK_INTERVAL; - g_oomCB->processVictimCB = (OomFn)OomKillProcess; - g_oomCB->scoreCB = (OomFn)OomScoreProcess; - g_oomCB->enabled = FALSE; + g_oomCB->lowMemThreshold = OOM_DEFAULT_LOW_MEM_THRESHOLD; //运行任务的门槛 + g_oomCB->reclaimMemThreshold = OOM_DEFAULT_RECLAIM_MEM_THRESHOLD; //回收内存的门槛 + g_oomCB->checkInterval = OOM_DEFAULT_CHECK_INTERVAL; //检测时间间隔 1S + g_oomCB->processVictimCB = (OomFn)OomKillProcess; //出问题时对进程的处理函数 + g_oomCB->scoreCB = (OomFn)OomScoreProcess; //统计进程占用的物理内存 + g_oomCB->enabled = FALSE; //是否启用监控 -#ifdef LOSCFG_ENABLE_OOM_LOOP_TASK +#ifdef LOSCFG_ENABLE_OOM_LOOP_TASK //内存溢出检测开关 g_oomCB->enabled = TRUE; UINT32 ret = LOS_SwtmrCreate(g_oomCB->checkInterval, LOS_SWTMR_MODE_PERIOD, (SWTMR_PROC_FUNC)OomWriteEvent, - &g_oomCB->swtmrID, (UINTPTR)g_oomCB); + &g_oomCB->swtmrID, (UINTPTR)g_oomCB);//创建检测定时器 if (ret != LOS_OK) { return ret; } - return LOS_SwtmrStart(g_oomCB->swtmrID); + return LOS_SwtmrStart(g_oomCB->swtmrID);//启动定时器 #else return LOS_OK; #endif } -LOS_MODULE_INIT(OomTaskInit, LOS_INIT_LEVEL_KMOD_TASK); - -#endif +LOS_MODULE_INIT(OomTaskInit, LOS_INIT_LEVEL_KMOD_TASK);//初始化内存监控模块 +#endif \ No newline at end of file diff --git a/src/kernel_liteos_a/kernel/base/vm/shm.c b/src/kernel_liteos_a/kernel/base/vm/shm.c index 637c6167..a61cb0f4 100644 --- a/src/kernel_liteos_a/kernel/base/vm/shm.c +++ b/src/kernel_liteos_a/kernel/base/vm/shm.c @@ -1,3 +1,17 @@ +/*! + 什么是共享内存 + 顾名思义,共享内存就是允许两个不相关的进程访问同一个物理内存。共享内存是在两个正在运行的进程之间 + 共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同 + 一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言 + 函数malloc()分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段 + 共享内存的任何其他进程。 + + 特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制 + 可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问 + + 共享线性区可以由任意的进程创建,每个使用共享线性区都必须经过映射. + */ + /* * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved. * Copyright (c) 2020-2023 Huawei Device Co., Ltd. All rights reserved. @@ -55,9 +69,9 @@ #ifdef LOSCFG_KERNEL_SHM -#define SHM_SEG_FREE 0x2000 -#define SHM_SEG_USED 0x4000 -#define SHM_SEG_REMOVE 0x8000 +#define SHM_SEG_FREE 0x2000 //空闲未使用 +#define SHM_SEG_USED 0x4000 //已使用 +#define SHM_SEG_REMOVE 0x8000 //删除 #ifndef SHM_M #define SHM_M 010000 @@ -66,21 +80,17 @@ #ifndef SHM_X #define SHM_X 0100 #endif - #ifndef ACCESSPERMS -#define ACCESSPERMS (S_IRWXU | S_IRWXG | S_IRWXO) -#endif +#define ACCESSPERMS (S_IRWXU | S_IRWXG | S_IRWXO)//文件权限值意思就是 用户,用户组,其他可读可写. +#endif //代表含义U:user G:group O:other #define SHM_S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) #define SHM_S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) #define SHM_S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) - #define SHM_GROUPE_TO_USER 3 #define SHM_OTHER_TO_USER 6 - #ifndef LOSCFG_IPC_CONTAINER STATIC LosMux g_sysvShmMux; - /* private data */ STATIC struct shminfo g_shmInfo; STATIC struct shmIDSource *g_shmSegs = NULL; @@ -96,6 +106,60 @@ STATIC UINT32 g_shmUsedPageCount; #define SYSV_SHM_LOCK() (VOID)LOS_MuxLock(&IPC_SHM_SYS_VSHM_MUTEX, LOS_WAIT_FOREVER) #define SYSV_SHM_UNLOCK() (VOID)LOS_MuxUnlock(&IPC_SHM_SYS_VSHM_MUTEX) +#if 0 // @note_#if0 + +//内核为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者 +struct ipc_perm { + key_t __ipc_perm_key; //调用shmget()时给出的关键字 + uid_t uid; //共享内存所有者的有效用户ID + gid_t gid; //共享内存所有者所属组的有效组ID + uid_t cuid; //共享内存创建 者的有效用户ID + gid_t cgid; //共享内存创建者所属组的有效组ID + mode_t mode; //权限 + SHM_DEST / SHM_LOCKED /SHM_HUGETLB 标志位 + int __ipc_perm_seq; //序列号 + long __pad1; //保留扩展用 + long __pad2; +}; +//每个共享内存段在内核中维护着一个内部结构shmid_ds +struct shmid_ds { + struct ipc_perm shm_perm;///< 操作许可,里面包含共享内存的用户ID、组ID等信息 + size_t shm_segsz; ///< 共享内存段的大小,单位为字节 + time_t shm_atime; ///< 最后一个进程访问共享内存的时间 + time_t shm_dtime; ///< 最后一个进程离开共享内存的时间 + time_t shm_ctime; ///< 创建时间 + pid_t shm_cpid; ///< 创建共享内存的进程ID + pid_t shm_lpid; ///< 最后操作共享内存的进程ID + unsigned long shm_nattch; ///< 当前使用该共享内存段的进程数量 + unsigned long __pad1; //保留扩展用 + unsigned long __pad2; +}; +// 共享内存模块设置信息 +struct shminfo { + unsigned long shmmax, shmmin, shmmni, shmseg, shmall, __unused[4]; +}; +struct shmIDSource {//共享内存描述符 + struct shmid_ds ds; //是内核为每一个共享内存段维护的数据结构 + UINT32 status; //状态 SHM_SEG_FREE ... + LOS_DL_LIST node; //节点,挂VmPage +#ifdef LOSCFG_SHELL + CHAR ownerName[OS_PCB_NAME_LEN]; +#endif +}; + +/* private data */ +STATIC struct shminfo g_shmInfo = { //描述共享内存范围的全局变量 + .shmmax = SHM_MAX,//共享内存单个上限 4096页 即 16M + .shmmin = SHM_MIN,//共享内存单个下限 1页 即:4K + .shmmni = SHM_MNI,//共享内存总数 默认192 + .shmseg = SHM_SEG,//每个用户进程可以使用的最多的共享内存段的数目 128 + .shmall = SHM_ALL,//系统范围内共享内存的总页数, 4096页 +}; + +STATIC struct shmIDSource *g_shmSegs = NULL; +STATIC UINT32 g_shmUsedPageCount; +#endif + +//共享内存初始化 struct shmIDSource *OsShmCBInit(LosMux *sysvShmMux, struct shminfo *shmInfo, UINT32 *shmUsedPageCount) { UINT32 ret; @@ -104,7 +168,6 @@ struct shmIDSource *OsShmCBInit(LosMux *sysvShmMux, struct shminfo *shmInfo, UIN if ((sysvShmMux == NULL) || (shmInfo == NULL) || (shmUsedPageCount == NULL)) { return NULL; } - ret = LOS_MuxInit(sysvShmMux, NULL); if (ret != LOS_OK) { goto ERROR; @@ -115,7 +178,6 @@ struct shmIDSource *OsShmCBInit(LosMux *sysvShmMux, struct shminfo *shmInfo, UIN shmInfo->shmmni = SHM_MNI; shmInfo->shmseg = SHM_SEG; shmInfo->shmall = SHM_ALL; - struct shmIDSource *shmSegs = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, sizeof(struct shmIDSource) * shmInfo->shmmni); if (shmSegs == NULL) { (VOID)LOS_MuxDestroy(sysvShmMux); @@ -125,9 +187,9 @@ struct shmIDSource *OsShmCBInit(LosMux *sysvShmMux, struct shminfo *shmInfo, UIN 0, (sizeof(struct shmIDSource) * shmInfo->shmmni)); for (i = 0; i < shmInfo->shmmni; i++) { - shmSegs[i].status = SHM_SEG_FREE; - shmSegs[i].ds.shm_perm.seq = i + 1; - LOS_ListInit(&shmSegs[i].node); + shmSegs[i].status = SHM_SEG_FREE;//节点初始状态为空闲 + shmSegs[i].ds.shm_perm.seq = i + 1;//struct ipc_perm shm_perm;系统为每一个IPC对象保存一个ipc_perm结构体,结构说明了IPC对象的权限和所有者 + LOS_ListInit(&shmSegs[i].node);//初始化节点 } *shmUsedPageCount = 0; @@ -137,7 +199,6 @@ ERROR: VM_ERR("ShmInit fail\n"); return NULL; } - UINT32 ShmInit(VOID) { #ifndef LOSCFG_IPC_CONTAINER @@ -149,8 +210,9 @@ UINT32 ShmInit(VOID) return LOS_OK; } -LOS_MODULE_INIT(ShmInit, LOS_INIT_LEVEL_VM_COMPLETE); +LOS_MODULE_INIT(ShmInit, LOS_INIT_LEVEL_VM_COMPLETE);//共享内存模块初始化 +//共享内存反初始化 UINT32 ShmDeinit(VOID) { UINT32 ret; @@ -165,7 +227,7 @@ UINT32 ShmDeinit(VOID) return 0; } - +///给共享段中所有物理页框贴上共享标签 STATIC inline VOID ShmSetSharedFlag(struct shmIDSource *seg) { LosVmPage *page = NULL; @@ -174,7 +236,7 @@ STATIC inline VOID ShmSetSharedFlag(struct shmIDSource *seg) OsSetPageShared(page); } } - +///给共享段中所有物理页框撕掉共享标签 STATIC inline VOID ShmClearSharedFlag(struct shmIDSource *seg) { LosVmPage *page = NULL; @@ -183,7 +245,7 @@ STATIC inline VOID ShmClearSharedFlag(struct shmIDSource *seg) OsCleanPageShared(page); } } - +///seg下所有共享页引用减少 STATIC VOID ShmPagesRefDec(struct shmIDSource *seg) { LosVmPage *page = NULL; @@ -193,6 +255,15 @@ STATIC VOID ShmPagesRefDec(struct shmIDSource *seg) } } +/** + * @brief 为共享段分配物理内存 + 例如:参数size = 4097, LOS_Align(size, PAGE_SIZE) = 8192 + 分配页数 size >> PAGE_SHIFT = 2页 + * @param key + * @param size + * @param shmflg + * @return STATIC + */ STATIC INT32 ShmAllocSegCheck(key_t key, size_t *size, INT32 *segNum) { INT32 i; @@ -201,7 +272,7 @@ STATIC INT32 ShmAllocSegCheck(key_t key, size_t *size, INT32 *segNum) return -EINVAL; } - *size = LOS_Align(*size, PAGE_SIZE); + *size = LOS_Align(*size, PAGE_SIZE);//必须对齐 if ((IPC_SHM_USED_PAGE_COUNT + (*size >> PAGE_SHIFT)) > IPC_SHM_INFO.shmall) { return -ENOMEM; } @@ -211,11 +282,10 @@ STATIC INT32 ShmAllocSegCheck(key_t key, size_t *size, INT32 *segNum) return -ENOMEM; } #endif - - for (i = 0; i < IPC_SHM_INFO.shmmni; i++) { - if (IPC_SHM_SEGS[i].status & SHM_SEG_FREE) { - IPC_SHM_SEGS[i].status &= ~SHM_SEG_FREE; - *segNum = i; + for (i = 0; i < IPC_SHM_INFO.shmmni; i++) {//试图找到一个空闲段与参数key绑定 + if (IPC_SHM_SEGS[i].status & SHM_SEG_FREE) {//找到空闲段 + IPC_SHM_SEGS[i].status &= ~SHM_SEG_FREE;//变成非空闲状态 + *segNum = i;//标号 break; } } @@ -236,49 +306,47 @@ STATIC INT32 ShmAllocSeg(key_t key, size_t size, INT32 shmflg) if (ret < 0) { return ret; } - seg = &IPC_SHM_SEGS[segNum]; - count = LOS_PhysPagesAlloc(size >> PAGE_SHIFT, &seg->node); - if (count != (size >> PAGE_SHIFT)) { - (VOID)LOS_PhysPagesFree(&seg->node); - seg->status = SHM_SEG_FREE; + count = LOS_PhysPagesAlloc(size >> PAGE_SHIFT, &seg->node);//分配共享页面,函数内部把node都挂好了. + if (count != (size >> PAGE_SHIFT)) {//当未分配到足够的内存时,处理方式是:不稀罕给那么点,舍弃! + (VOID)LOS_PhysPagesFree(&seg->node);//释放节点上的物理页框 + seg->status = SHM_SEG_FREE;//共享段变回空闲状态 #ifdef LOSCFG_KERNEL_IPC_PLIMIT OsIPCLimitShmFree(size); #endif return -ENOMEM; } - - ShmSetSharedFlag(seg); + ShmSetSharedFlag(seg);//将node的每个页面设置为共享页 IPC_SHM_USED_PAGE_COUNT += size >> PAGE_SHIFT; - seg->status |= SHM_SEG_USED; + seg->status |= SHM_SEG_USED; //共享段贴上已在使用的标签 seg->ds.shm_perm.mode = (UINT32)shmflg & ACCESSPERMS; - seg->ds.shm_perm.key = key; - seg->ds.shm_segsz = size; - seg->ds.shm_perm.cuid = LOS_GetUserID(); - seg->ds.shm_perm.uid = LOS_GetUserID(); - seg->ds.shm_perm.cgid = LOS_GetGroupID(); - seg->ds.shm_perm.gid = LOS_GetGroupID(); - seg->ds.shm_lpid = 0; - seg->ds.shm_nattch = 0; - seg->ds.shm_cpid = LOS_GetCurrProcessID(); - seg->ds.shm_atime = 0; - seg->ds.shm_dtime = 0; - seg->ds.shm_ctime = time(NULL); + seg->ds.shm_perm.key = key;//保存参数key,如此 key 和 共享ID绑定在一块 + seg->ds.shm_segsz = size; //共享段的大小 + seg->ds.shm_perm.cuid = LOS_GetUserID(); //设置用户ID + seg->ds.shm_perm.uid = LOS_GetUserID(); //设置用户ID + seg->ds.shm_perm.cgid = LOS_GetGroupID(); //设置组ID + seg->ds.shm_perm.gid = LOS_GetGroupID(); //设置组ID + seg->ds.shm_lpid = 0; //最后一个操作的进程 + seg->ds.shm_nattch = 0; //绑定进程的数量 + seg->ds.shm_cpid = LOS_GetCurrProcessID(); //获取进程ID + seg->ds.shm_atime = 0; //访问时间 + seg->ds.shm_dtime = 0; //detach 分离时间 共享内存使用完之后,需要将它从进程地址空间中分离出来;将共享内存分离并不是删除它,只是使该共享内存对当前的进程不再可用 + seg->ds.shm_ctime = time(NULL);//创建时间 #ifdef LOSCFG_SHELL (VOID)memcpy_s(seg->ownerName, OS_PCB_NAME_LEN, OsCurrProcessGet()->processName, OS_PCB_NAME_LEN); #endif return segNum; } - +///释放seg->node 所占物理页框,seg本身重置 STATIC INLINE VOID ShmFreeSeg(struct shmIDSource *seg, UINT32 *shmUsedPageCount) { UINT32 count; - ShmClearSharedFlag(seg); - count = LOS_PhysPagesFree(&seg->node); - if (count != (seg->ds.shm_segsz >> PAGE_SHIFT)) { + ShmClearSharedFlag(seg);//先撕掉 seg->node 中vmpage的共享标签 + count = LOS_PhysPagesFree(&seg->node);//再挨个删除物理页框 + if (count != (seg->ds.shm_segsz >> PAGE_SHIFT)) {//异常,必须要一样 VM_ERR("free physical pages failed, count = %d, size = %d", count, seg->ds.shm_segsz >> PAGE_SHIFT); return; } @@ -288,31 +356,31 @@ STATIC INLINE VOID ShmFreeSeg(struct shmIDSource *seg, UINT32 *shmUsedPageCount) if (shmUsedPageCount != NULL) { (*shmUsedPageCount) -= seg->ds.shm_segsz >> PAGE_SHIFT; } - seg->status = SHM_SEG_FREE; - LOS_ListInit(&seg->node); + seg->status = SHM_SEG_FREE;//seg恢复自由之身 + LOS_ListInit(&seg->node);//重置node } - +///通过key查找 shmId STATIC INT32 ShmFindSegByKey(key_t key) { INT32 i; struct shmIDSource *seg = NULL; - for (i = 0; i < IPC_SHM_INFO.shmmni; i++) { + for (i = 0; i < IPC_SHM_INFO.shmmni; i++) {//遍历共享段池,找到与key绑定的共享ID seg = &IPC_SHM_SEGS[i]; if ((seg->status & SHM_SEG_USED) && - (seg->ds.shm_perm.key == key)) { + (seg->ds.shm_perm.key == key)) {//满足两个条件,找到后返回 return i; } } return -1; } - +///共享内存段有效性检查 STATIC INT32 ShmSegValidCheck(INT32 segNum, size_t size, INT32 shmFlg) { - struct shmIDSource *seg = &IPC_SHM_SEGS[segNum]; + struct shmIDSource *seg = &IPC_SHM_SEGS[segNum];//拿到shmID - if (size > seg->ds.shm_segsz) { + if (size > seg->ds.shm_segsz) {//段长 return -EINVAL; } @@ -323,7 +391,7 @@ STATIC INT32 ShmSegValidCheck(INT32 segNum, size_t size, INT32 shmFlg) return segNum; } - +///通过ID找到共享内存资源 STATIC struct shmIDSource *ShmFindSeg(int shmid) { struct shmIDSource *seg = NULL; @@ -341,7 +409,7 @@ STATIC struct shmIDSource *ShmFindSeg(int shmid) return seg; } - +///共享内存映射 STATIC VOID ShmVmmMapping(LosVmSpace *space, LOS_DL_LIST *pageList, VADDR_T vaddr, UINT32 regionFlags) { LosVmPage *vmPage = NULL; @@ -349,64 +417,64 @@ STATIC VOID ShmVmmMapping(LosVmSpace *space, LOS_DL_LIST *pageList, VADDR_T vadd PADDR_T pa; STATUS_T ret; - LOS_DL_LIST_FOR_EACH_ENTRY(vmPage, pageList, LosVmPage, node) { - pa = VM_PAGE_TO_PHYS(vmPage); - LOS_AtomicInc(&vmPage->refCounts); - ret = LOS_ArchMmuMap(&space->archMmu, va, pa, 1, regionFlags); + LOS_DL_LIST_FOR_EACH_ENTRY(vmPage, pageList, LosVmPage, node) {//遍历一页一页映射 + pa = VM_PAGE_TO_PHYS(vmPage);//拿到物理地址 + LOS_AtomicInc(&vmPage->refCounts);//自增 + ret = LOS_ArchMmuMap(&space->archMmu, va, pa, 1, regionFlags);//虚实映射 if (ret != 1) { VM_ERR("LOS_ArchMmuMap failed, ret = %d", ret); } va += PAGE_SIZE; } } - +///fork 一个共享线性区 VOID OsShmFork(LosVmSpace *space, LosVmMapRegion *oldRegion, LosVmMapRegion *newRegion) { struct shmIDSource *seg = NULL; SYSV_SHM_LOCK(); - seg = ShmFindSeg(oldRegion->shmid); + seg = ShmFindSeg(oldRegion->shmid);//通过老区ID获取对应的共享资源ID结构体 if (seg == NULL) { SYSV_SHM_UNLOCK(); VM_ERR("shm fork failed!"); return; } - newRegion->shmid = oldRegion->shmid; - newRegion->forkFlags = oldRegion->forkFlags; - ShmVmmMapping(space, &seg->node, newRegion->range.base, newRegion->regionFlags); - seg->ds.shm_nattch++; + newRegion->shmid = oldRegion->shmid;//一样的共享区ID + newRegion->forkFlags = oldRegion->forkFlags;//forkFlags也一样了 + ShmVmmMapping(space, &seg->node, newRegion->range.base, newRegion->regionFlags);//新线性区与共享内存进行映射 + seg->ds.shm_nattch++;//附在共享线性区上的进程数++ SYSV_SHM_UNLOCK(); } - +///释放共享线性区 VOID OsShmRegionFree(LosVmSpace *space, LosVmMapRegion *region) { struct shmIDSource *seg = NULL; SYSV_SHM_LOCK(); - seg = ShmFindSeg(region->shmid); + seg = ShmFindSeg(region->shmid);//通过线性区ID获取对应的共享资源ID结构体 if (seg == NULL) { SYSV_SHM_UNLOCK(); return; } - LOS_ArchMmuUnmap(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT); - ShmPagesRefDec(seg); - seg->ds.shm_nattch--; + LOS_ArchMmuUnmap(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);//解除线性区的映射 + ShmPagesRefDec(seg);//ref -- + seg->ds.shm_nattch--;//附在共享线性区上的进程数-- if (seg->ds.shm_nattch <= 0 && (seg->status & SHM_SEG_REMOVE)) { - ShmFreeSeg(seg, &IPC_SHM_USED_PAGE_COUNT); + ShmFreeSeg(seg, &IPC_SHM_USED_PAGE_COUNT);//就释放掉物理内存!注意是:物理内存 } else { seg->ds.shm_dtime = time(NULL); seg->ds.shm_lpid = LOS_GetCurrProcessID(); /* may not be the space's PID. */ } SYSV_SHM_UNLOCK(); } - +///是否为共享线性区,是否有标签? BOOL OsIsShmRegion(LosVmMapRegion *region) { return (region->regionFlags & VM_MAP_REGION_FLAG_SHM) ? TRUE : FALSE; } - +///获取共享内存池中已被使用的段数量 STATIC INT32 ShmSegUsedCount(VOID) { INT32 i; @@ -415,16 +483,16 @@ STATIC INT32 ShmSegUsedCount(VOID) for (i = 0; i < IPC_SHM_INFO.shmmni; i++) { seg = &IPC_SHM_SEGS[i]; - if (seg->status & SHM_SEG_USED) { + if (seg->status & SHM_SEG_USED) {//找到一个 count++; } } return count; } - +///对共享内存段权限检查 STATIC INT32 ShmPermCheck(struct shmIDSource *seg, mode_t mode) { - INT32 uid = LOS_GetUserID(); + INT32 uid = LOS_GetUserID();//当前进程的用户ID UINT32 tmpMode = 0; mode_t privMode = seg->ds.shm_perm.mode; mode_t accMode; @@ -466,6 +534,22 @@ STATIC INT32 ShmPermCheck(struct shmIDSource *seg, mode_t mode) } } +/*! + * @brief ShmGet + * 得到一个共享内存标识符或创建一个共享内存对象 + * @param key 建立新共享内存对象 标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案。 + 为此,每个IPC对象都与一个键(key)相关联,这个键作为该对象的外部名,无论何时创建IPC结构(通过msgget、semget、shmget创建), + 都应给IPC指定一个键, key_t由ftok创建,ftok当然在本工程里找不到,所以要写这么多. + * @param shmflg IPC_CREAT IPC_EXCL + IPC_CREAT: 在创建新的IPC时,如果key参数是IPC_PRIVATE或者和当前某种类型的IPC结构无关,则需要指明flag参数的IPC_CREAT标志位, + 则用来创建一个新的IPC结构。(如果IPC结构已存在,并且指定了IPC_CREAT,则IPC_CREAT什么都不做,函数也不出错) + IPC_EXCL: 此参数一般与IPC_CREAT配合使用来创建一个新的IPC结构。如果创建的IPC结构已存在函数就出错返回, + 返回EEXIST(这与open函数指定O_CREAT和O_EXCL标志原理相同) + * @param size 新建的共享内存大小,以字节为单位 + * @return + * + * @see + */ INT32 ShmGet(key_t key, size_t size, INT32 shmflg) { INT32 ret; @@ -476,13 +560,13 @@ INT32 ShmGet(key_t key, size_t size, INT32 shmflg) if (key == IPC_PRIVATE) { ret = ShmAllocSeg(key, size, shmflg); } else { - ret = ShmFindSegByKey(key); + ret = ShmFindSegByKey(key);//通过key查找资源ID if (ret < 0) { - if (((UINT32)shmflg & IPC_CREAT) == 0) { + if (((UINT32)shmflg & IPC_CREAT) == 0) {// ret = -ENOENT; goto ERROR; } else { - ret = ShmAllocSeg(key, size, shmflg); + ret = ShmAllocSeg(key, size, shmflg);//分配一个共享内存 } } else { shmid = ret; @@ -491,7 +575,7 @@ INT32 ShmGet(key_t key, size_t size, INT32 shmflg) ret = -EEXIST; goto ERROR; } - ret = ShmPermCheck(ShmFindSeg(shmid), (UINT32)shmflg & ACCESSPERMS); + ret = ShmPermCheck(ShmFindSeg(shmid), (UINT32)shmflg & ACCESSPERMS);//对共享内存权限检查 if (ret != 0) { ret = -ret; goto ERROR; @@ -526,13 +610,13 @@ INT32 ShmatParamCheck(const VOID *shmaddr, INT32 shmflg) return 0; } - +///分配一个共享线性区并映射好 LosVmMapRegion *ShmatVmmAlloc(struct shmIDSource *seg, const VOID *shmaddr, INT32 shmflg, UINT32 prot) { LosVmSpace *space = OsCurrProcessGet()->vmSpace; LosVmMapRegion *region = NULL; - UINT32 flags = MAP_ANONYMOUS | MAP_SHARED; + UINT32 flags = MAP_ANONYMOUS | MAP_SHARED;//本线性区为共享+匿名标签 UINT32 mapFlags = flags | MAP_FIXED; VADDR_T vaddr; UINT32 regionFlags; @@ -543,29 +627,29 @@ LosVmMapRegion *ShmatVmmAlloc(struct shmIDSource *seg, const VOID *shmaddr, } regionFlags = OsCvtProtFlagsToRegionFlags(prot, flags); (VOID)LOS_MuxAcquire(&space->regionMux); - if (shmaddr == NULL) { - region = LOS_RegionAlloc(space, 0, seg->ds.shm_segsz, regionFlags, 0); - } else { + if (shmaddr == NULL) {//未指定了共享内存连接到当前进程中的地址位置 + region = LOS_RegionAlloc(space, 0, seg->ds.shm_segsz, regionFlags, 0);//分配线性区 + } else {//指定时,就需要先找地址所在的线性区 if ((UINT32)shmflg & SHM_RND) { vaddr = ROUNDDOWN((VADDR_T)(UINTPTR)shmaddr, SHMLBA); } else { vaddr = (VADDR_T)(UINTPTR)shmaddr; - } + }//找到线性区并重新映射,当指定地址时需贴上重新映射的标签 if (!((UINT32)shmflg & SHM_REMAP) && (LOS_RegionFind(space, vaddr) || LOS_RegionFind(space, vaddr + seg->ds.shm_segsz - 1) || LOS_RegionRangeFind(space, vaddr, seg->ds.shm_segsz - 1))) { ret = EINVAL; goto ERROR; } - vaddr = (VADDR_T)LOS_MMap(vaddr, seg->ds.shm_segsz, prot, mapFlags, -1, 0); - region = LOS_RegionFind(space, vaddr); + vaddr = (VADDR_T)LOS_MMap(vaddr, seg->ds.shm_segsz, prot, mapFlags, -1, 0);//做好映射 + region = LOS_RegionFind(space, vaddr);//重新查找线性区,用于返回. } if (region == NULL) { ret = ENOMEM; goto ERROR; } - ShmVmmMapping(space, &seg->node, region->range.base, regionFlags); + ShmVmmMapping(space, &seg->node, region->range.base, regionFlags);//共享内存映射 (VOID)LOS_MuxRelease(&space->regionMux); return region; ERROR: @@ -574,6 +658,17 @@ ERROR: return NULL; } +/*! + * @brief ShmAt + * 用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。 + * @param shm_flg 是一组标志位,通常为0。 + * @param shmaddr 指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。 + * @param shmid 是shmget()函数返回的共享内存标识符 + * @return + * 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值加1 + shmid 就是个索引,就跟进程和线程的ID一样 g_shmSegs[shmid] shmid > 192个 + * @see + */ VOID *ShmAt(INT32 shmid, const VOID *shmaddr, INT32 shmflg) { INT32 ret; @@ -582,13 +677,13 @@ VOID *ShmAt(INT32 shmid, const VOID *shmaddr, INT32 shmflg) struct shmIDSource *seg = NULL; LosVmMapRegion *r = NULL; - ret = ShmatParamCheck(shmaddr, shmflg); + ret = ShmatParamCheck(shmaddr, shmflg);//参数检查 if (ret != 0) { set_errno(ret); return (VOID *)-1; } - if ((UINT32)shmflg & SHM_EXEC) { + if ((UINT32)shmflg & SHM_EXEC) {//flag 转换 prot |= PROT_EXEC; acc_mode |= SHM_S_IXUGO; } else if (((UINT32)shmflg & SHM_RDONLY) == 0) { @@ -597,7 +692,7 @@ VOID *ShmAt(INT32 shmid, const VOID *shmaddr, INT32 shmflg) } SYSV_SHM_LOCK(); - seg = ShmFindSeg(shmid); + seg = ShmFindSeg(shmid);//找到段 if (seg == NULL) { SYSV_SHM_UNLOCK(); return (VOID *)-1; @@ -608,18 +703,18 @@ VOID *ShmAt(INT32 shmid, const VOID *shmaddr, INT32 shmflg) goto ERROR; } - seg->ds.shm_nattch++; - r = ShmatVmmAlloc(seg, shmaddr, shmflg, prot); + seg->ds.shm_nattch++;//ds上记录有一个进程绑定上来 + r = ShmatVmmAlloc(seg, shmaddr, shmflg, prot);//在当前进程空间分配一个线性区并映射到共享内存 if (r == NULL) { seg->ds.shm_nattch--; SYSV_SHM_UNLOCK(); return (VOID *)-1; } - r->shmid = shmid; - r->regionFlags |= VM_MAP_REGION_FLAG_SHM; - seg->ds.shm_atime = time(NULL); - seg->ds.shm_lpid = LOS_GetCurrProcessID(); + r->shmid = shmid;//把ID给线性区的shmid + r->regionFlags |= VM_MAP_REGION_FLAG_SHM;//这是一个共享线性区 + seg->ds.shm_atime = time(NULL);//访问时间 + seg->ds.shm_lpid = LOS_GetCurrProcessID();//进程ID SYSV_SHM_UNLOCK(); return (VOID *)(UINTPTR)r->range.base; @@ -630,6 +725,19 @@ ERROR: return (VOID *)-1; } +/*! + * @brief ShmCtl + * 此函数可以对shmid指定的共享存储进行多种操作(删除、取信息、加锁、解锁等) + * @param buf 是一个结构指针,它指向共享内存模式和访问权限的结构。 + * @param cmd command是要采取的操作,它可以取下面的三个值 : + IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。 + IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值 + IPC_RMID:删除共享内存段 + * @param shmid 是shmget()函数返回的共享内存标识符 + * @return + * + * @see + */ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) { struct shmIDSource *seg = NULL; @@ -642,7 +750,7 @@ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) SYSV_SHM_LOCK(); if ((cmd != IPC_INFO) && (cmd != SHM_INFO)) { - seg = ShmFindSeg(shmid); + seg = ShmFindSeg(shmid);//通过索引ID找到seg if (seg == NULL) { SYSV_SHM_UNLOCK(); return -1; @@ -656,13 +764,13 @@ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) switch (cmd) { case IPC_STAT: - case SHM_STAT: + case SHM_STAT://取段结构 ret = ShmPermCheck(seg, SHM_S_IRUGO); if (ret != 0) { goto ERROR; } - ret = LOS_ArchCopyToUser(buf, &seg->ds, sizeof(struct shmid_ds)); + ret = LOS_ArchCopyToUser(buf, &seg->ds, sizeof(struct shmid_ds));//把内核空间的共享页数据拷贝到用户空间 if (ret != 0) { ret = EFAULT; goto ERROR; @@ -671,13 +779,13 @@ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) ret = (unsigned int)((unsigned int)seg->ds.shm_perm.seq << 16) | (unsigned int)((unsigned int)shmid & 0xffff); /* 16: use the seq as the upper 16 bits */ } break; - case IPC_SET: + case IPC_SET://重置共享段 ret = ShmPermCheck(seg, SHM_M); if (ret != 0) { ret = EPERM; goto ERROR; } - + //从用户空间拷贝数据到内核空间 ret = LOS_ArchCopyFromUser(&shm_perm, &buf->shm_perm, sizeof(struct ipc_perm)); if (ret != 0) { ret = EFAULT; @@ -686,14 +794,14 @@ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) seg->ds.shm_perm.uid = shm_perm.uid; seg->ds.shm_perm.gid = shm_perm.gid; seg->ds.shm_perm.mode = (seg->ds.shm_perm.mode & ~ACCESSPERMS) | - (shm_perm.mode & ACCESSPERMS); + (shm_perm.mode & ACCESSPERMS);//可访问 seg->ds.shm_ctime = time(NULL); #ifdef LOSCFG_SHELL (VOID)memcpy_s(seg->ownerName, OS_PCB_NAME_LEN, OS_PCB_FROM_PID(shm_perm.uid)->processName, OS_PCB_NAME_LEN); #endif break; - case IPC_RMID: + case IPC_RMID://删除共享段 ret = ShmPermCheck(seg, SHM_M); if (ret != 0) { ret = EPERM; @@ -701,11 +809,11 @@ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) } seg->status |= SHM_SEG_REMOVE; - if (seg->ds.shm_nattch <= 0) { + if (seg->ds.shm_nattch <= 0) {//没有任何进程在使用了 ShmFreeSeg(seg, &IPC_SHM_USED_PAGE_COUNT); } break; - case IPC_INFO: + case IPC_INFO://把内核空间的共享页数据拷贝到用户空间 ret = LOS_ArchCopyToUser(buf, &IPC_SHM_INFO, sizeof(struct shminfo)); if (ret != 0) { ret = EFAULT; @@ -719,8 +827,8 @@ INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf) shmInfo.shm_tot = 0; shmInfo.swap_attempts = 0; shmInfo.swap_successes = 0; - shmInfo.used_ids = ShmSegUsedCount(); - ret = LOS_ArchCopyToUser(buf, &shmInfo, sizeof(struct shm_info)); + shmInfo.used_ids = ShmSegUsedCount();//在使用的seg数 + ret = LOS_ArchCopyToUser(buf, &shmInfo, sizeof(struct shm_info));//把内核空间的共享页数据拷贝到用户空间 if (ret != 0) { ret = EFAULT; goto ERROR; @@ -743,55 +851,63 @@ ERROR: return -1; } +/** + * @brief 当对共享存储的操作已经结束时,则调用shmdt与该存储段分离 + 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值减1 + * @attention 注意:这并不从系统中删除共享存储的标识符以及其相关的数据结构。共享存储的仍然存在, + 直至某个进程带IPC_RMID命令的调用shmctl特地删除共享存储为止 + * @param shmaddr + * @return INT32 + */ INT32 ShmDt(const VOID *shmaddr) { - LosVmSpace *space = OsCurrProcessGet()->vmSpace; + LosVmSpace *space = OsCurrProcessGet()->vmSpace;//获取进程空间 struct shmIDSource *seg = NULL; LosVmMapRegion *region = NULL; INT32 shmid; INT32 ret; - if (IS_PAGE_ALIGNED(shmaddr) == 0) { + if (IS_PAGE_ALIGNED(shmaddr) == 0) {//地址是否对齐 ret = EINVAL; goto ERROR; } (VOID)LOS_MuxAcquire(&space->regionMux); - region = LOS_RegionFind(space, (VADDR_T)(UINTPTR)shmaddr); + region = LOS_RegionFind(space, (VADDR_T)(UINTPTR)shmaddr);//找到线性区 if (region == NULL) { ret = EINVAL; goto ERROR_WITH_LOCK; } - shmid = region->shmid; + shmid = region->shmid;//线性区共享ID - if (region->range.base != (VADDR_T)(UINTPTR)shmaddr) { - ret = EINVAL; + if (region->range.base != (VADDR_T)(UINTPTR)shmaddr) {//这是用户空间和内核空间的一次解绑 + ret = EINVAL; //shmaddr 必须要等于region->range.base goto ERROR_WITH_LOCK; } /* remove it from aspace */ - LOS_RbDelNode(&space->regionRbTree, ®ion->rbNode); - LOS_ArchMmuUnmap(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT); + LOS_RbDelNode(&space->regionRbTree, ®ion->rbNode);//从红黑树和链表中摘除节点 + LOS_ArchMmuUnmap(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);//解除线性区的映射 (VOID)LOS_MuxRelease(&space->regionMux); /* free it */ - free(region); + free(region);//释放线性区所占内存池中的内存 SYSV_SHM_LOCK(); - seg = ShmFindSeg(shmid); + seg = ShmFindSeg(shmid);//找到seg,线性区和共享段的关系是 1:N 的关系,其他空间的线性区也会绑在共享段上 if (seg == NULL) { ret = EINVAL; SYSV_SHM_UNLOCK(); goto ERROR; } - ShmPagesRefDec(seg); - seg->ds.shm_nattch--; - if ((seg->ds.shm_nattch <= 0) && - (seg->status & SHM_SEG_REMOVE)) { - ShmFreeSeg(seg, &IPC_SHM_USED_PAGE_COUNT); + ShmPagesRefDec(seg);//页面引用数 -- + seg->ds.shm_nattch--;//使用共享内存的进程数少了一个 + if ((seg->ds.shm_nattch <= 0) && //无任何进程使用共享内存 + (seg->status & SHM_SEG_REMOVE)) {//状态为删除时需要释放物理页内存了,否则其他进程还要继续使用共享内存 + ShmFreeSeg(seg, &IPC_SHM_USED_PAGE_COUNT);//释放seg 页框链表中的页框内存,再重置seg状态 } else { - seg->ds.shm_dtime = time(NULL); - seg->ds.shm_lpid = LOS_GetCurrProcessID(); + seg->ds.shm_dtime = time(NULL);//记录分离的时间 + seg->ds.shm_lpid = LOS_GetCurrProcessID();//记录操作进程ID } SYSV_SHM_UNLOCK(); @@ -847,7 +963,6 @@ STATIC VOID OsShmInfoCmd(VOID) } SYSV_SHM_UNLOCK(); } - STATIC VOID OsShmDeleteCmd(INT32 shmid) { struct shmIDSource *seg = NULL; @@ -876,7 +991,7 @@ STATIC VOID OsShmCmdUsage(VOID) "\t-r [shmid], Recycle the specified shared memory about shmid\n" "\t-h | --help, print shm command usage\n"); } - +///共享内存 UINT32 OsShellCmdShm(INT32 argc, const CHAR *argv[]) { INT32 shmid; @@ -911,4 +1026,3 @@ DONE: SHELLCMD_ENTRY(shm_shellcmd, CMD_TYPE_SHOW, "shm", 2, (CmdCallBackFunc)OsShellCmdShm); #endif #endif -