第10 章
虚拟内存
虚拟内存储存管理是现代操作系统的核心技术之一,它是整个系统高效合理地管理储存资源的基本保障,尤其是在嵌入式系统中,系统自身资源管理占的比例相对较大的情况下,就显得更为重要。在Windows CE中,除了个别对实时性能要求很高的应用程序需要关闭分页以外,大部分的应用程序都要求系统在虚拟储存上花费很大的比例。本章从系统的角度在特定的环境下跟踪虚拟内存的配置部分,因为虚拟内存配置是系统和使用者程序连接的主要渠道之一,配置的算法也在很大程度上决定了系统的性能。在分析过程中,还要对实体页的获取过程进行详细地分析,因为实体页面是系统极其珍贵的资源,对其的管理要格外小心。本章将会对在这个过程中可能出现的情况逐一分析,力图完整的向读者展示从系统的视角看到的储存管理技术。
10.1 配置过程概述
在本书的第4章初步介绍了虚拟内存的配置,下面再详细地介绍一下配置的各个具体过程(总体分析及流程图参见第4章)。虚拟内存的状态分为提交( c o m m i t t e d )和保留( r e s e r v e d ),这两部分功能都是在DoVitualAlloc中实现的。先看一下该函数的入口参数
LPVOID lpvAddress0, /*要保存或提交的区域地址* /
DWORD cbSize, /*区域大小* /
DWORD fdwAllocationType, /* 配置方式* /
DWORD fdwProtect, /*存取保护方式* /
lpvAddress0为呼叫者指定配置的虚拟内存启始地址,为零则表示由系统自动配置,cbSize为需要配置的虚拟内存大小,fdwAllocationType和fdwProtect分别为配置类型(保留或提交)和保护信息。
10.1.1 参数验证
以下参数为无效参数:
• cbSize为零。
• cbSize>32MB且没有指定虚拟内存启始地址。
• 无效的存取控制信息。
10.1.2 扫描虚拟内存区域找到合适大小的空闲区块
由于各应用程序扫描空闲区块存在互斥, 配置的虚拟内存前,要先进入虚拟内存临界区。如果需要配置的虚拟内存大于2MB,则从系统的空闲虚拟内存堆中配置,否则根据使用者指定的配置方式从第一个空闲区块或最后一个空闲区块扫描空闲的虚拟内存(MEM_TOPDOWN为从后向前扫描),若使用者指定了配置的虚拟内存启始地址,则只要简单的验证指定的虚拟内存是否都空闲即可。
10.1.3 在得到的虚拟内存区块中写入控制信息
写入的信息包括存取控制信息、虚拟内存的配置方式信息、共享使用者数(初始为1)以及该段虚存的启始地址。注意如果最后一个区块的页没有用完,要为其配置一个区块描述子,记录其为无效区块,对自动提交的页也要配置区块描述子,这样以后在提交的时候就不必再进入虚拟内存临界区了。
10.1.4 获取足够的实体区块并建立映像
在指定配置方式为提交的情况下,则跳过上两步,直接执行此步。因为如果需要映像到实体页面,所有的配置都是分两次呼叫DoVirtualAlloc的,第一次保留,第二次映射。所以,这一步也可以看作前两步的延续。在此过程中,先要在系统堆中建立这段区域的页表,最关键的步骤是向系统提交对实体页的申请,得到使用指定大小的实体页的授权,这就有点像盖房子要先申请地皮,申请下来以后再怎么盖由自己决定,当然可以空在那里不用。而前面的对虚拟内存的保留则是单单得到了对地皮使用的认可,并没有真正拿到这块地方(当然,认可也是有限的)。
10.1.5 小实验:虚拟内存配置的直观印象
用Platform Builder建立一个应用程序,下载到终端机上,观察它的储存配置。在控制台的命令行下键入mi full可得到有关储存管理的全部信息,关于虚拟内存配置的部分信息摘录如下:
Memory usage for Process 81a 9711c : 'device.exe' pid 3
Slot base 08000000 Section ptr 83fca000
08000000(1): -----r----------
08010000(0): -CCCCCc
08020000(0): ------------SSSS
08030000(0): WWWWWWWWWWWWWWWW
08040000(0): WWWWWWWWWWWWWWWW
08050000(0): WWWWWWWWWWWWWWW
08060000(0): --------------SS
08070000(0): ---------------S
08080000(0): ---------------S
08090000(0): -------------SSS
080a 0000(0): ---------------S
080b0000(0): ---------------S
080c 0000(0): --------------SS
080d0000(0): ---------------S
080e0000(0): ---------------S
080f 0000(0): ---------------S
08100000(0): -------------SSS
08110000(0): -------------SSS
08120000(0): ---------------S
08140000(0): ---------------S
08150000(0): ---------------S
08160000(0): --------------SS
08170000(0): WW--------------
08190000(0): ---------------
081a 0000(0): ---------------S
081b0000(0): W---------------
081d0000(0): ---------------
081e0000(0): ---------------S
081f 0000(0): WWWWWWWWWWWWWWWW
08200000(0): W---------------
08210000(0): ---------------
08220000(0): WWWWWWWWWWWWWWWW
08230000(0): WWWWWWWWWWWWWWWW
08240000(0): WWWWWWWWWWWWWWW
08250000(0): WWWW------------
08340000(0): ----------
08350000(0): ---------------S
08360000(0): ---------------S
08370000(0): ---------------S
08390000(0): ---------------S
083a 0000(0): ---------------S
083b0000(0): ---------------S
083c 0000(0): ---------------S
083d0000(0): ---------------S
083e0000(0): -------SSSSSSSSS
083f 0000(0): ---------------S
08400000(0): ---------------S
08420000(0): ---------------S
08430000(0): WWWWWWWWW-------
08450000(0): ---------------
08460000(0): ---------------S
09e70000(0): ------W-W-------
09e80000(0): ----------------
09e90000(0): -------------W--
09ea0000(0): ----W-W---------
09f 50000(0): -----WW---------
09f 60000(0): ----------W-----
09f 70000(0): ----W-----W-W---
09f 80000(0): W---W-W--W-W-W-W
09f 90000(0): WWWWWWW-W-W-----
09fa0000(0): ----W-W---W-W-W-
09fb0000(0): W-W-W-W-W-W-W-W-
09fc0000(0): ----W-W-W-W-W-W-
09fd0000(0): W-W-W-W-WW-W-W-W
09fe0000(0): ---------WWWWW-W
09ff0000(0): ---W-------W-W-W
Page summary: code=6(1) data r/o=0 r/w=193 stack=52 reserved=1216
这是系统分配给行程device.exe的虚拟内存储存槽(Slot),可以看出每个Slot的第一行(每行1 6个页)用来存放该Slot的信息,堆栈都是在每行末尾向前增长,经过几次分配回收,Slot中出现了一些碎片。在该例子中,最长的一段虚拟内存是09e70000~09ff0000共384页。
10.2 物理内存的获取
10.2.1 分配过程
下面详细地分析一下系统在调度实际物理内存时的行为,之所以选择这部分做详细地分析,是因为它是整个系统对公共资源进行分配的底层行为,从系统作为资源管理者的角度,可以从宏观上把握控制各微观行为之间的关系,可以很好地呈现系统的整体性,以及各部分之间的交互作用。而且这部分虽然重要,却只有短短的100多行原始码,因为系统把对物理内存的分配简化成对一个全局变量(PageFreeCount)的切割,很好地实现了系统设计的模块化想法。原始码中几个关于控制的阈值的选取也是经过深思熟虑的,可以帮助理解系统设计者在各个方面的权衡,也能帮助理解嵌入式系统对性能的要求与通用系统的差异。
下面对Holdpages函数这段原始码做详细分析,这部分原始码定义在[CEROOT]private/winceos/coreos/nk/kernel/physmem.c中,原始码的流程图参见第4四章图4 - 11。
0684 BOOL
0685 HoldPages(
0686 int cpNeed,
0687 BOOL bForce
0688 )
0689 {
0690 BOOL fSetGweOomEvent = FALSE;
0691 BOOL bRet = FALSE;
0692 WORD prio;
0693 EnterPhysCS();
0694 // Check if this request will drop the page free count below the
0695 // page out trigger and signal the clean up thread to start doing
0696 // pageouts if so.
0697 if ((cpNeed+PageOutTrigger > PageFreeCount) &&
0698 (!PageOutNeeded || (PagedInCount > PAGE_OUT_TRIGGER))) {
0699 PageOutNeeded = 1;
0700 PagedInCount = 0;
0701 if (prio = GET_CPRIO(pCurThread))
0702 prio--;
0703 if (prio < GET_CPRIO(pCleanupThread))
0704 KCall((PKFN)SetThreadBasePrio, pCleanupThread->hTh, (DWORD)prio);
0705 SetEvent(hAlarmThreadWakeup);
0706 }
这段原始码首先进入物理内存临界区,检测内存分配是否低于一定值,来决定是否要启动换页行程,将部分实体页换出。具体表现是将PCleanupThread的优先级设置为比目前行程略低,这样就可以在目前行程执行完毕之后马上启动。请注意,PageOutNeeded,PagedInCount,PageOutTrigger都是与schedule.c和loader.c通信的变量,其值和系统当时的执行状态密切相关。
0707 do {
0708 if (cpNeed+GwesLowThreshold <= PageFreeCount) {
0709 DWORD pfc, pfc2;
0710 do {
0711 pfc = PageFreeCount;
0712 } while (InterlockedTestExchange(&PageFreeCount,pfc,pfc-cpNeed) != (LONG) pfc);
0718 pfc -= cpNeed;
0719 do {
0720 pfc2 = KInfoTable[KINX_MINPAGEFREE];
0721 } while ((pfc2 > pfc)
0722 && (InterlockedTestExchange ((PLONG)&KInfoTable [KINX_MINPAGEFREE], pfc2,
pfc) != (LONG)pfc2));
0723 LogPhysicalPages(PageFreeCount);
0724 bRet = TRUE;
0725 goto hpDone;
0726 }
0727 } while (ScavengeStacks(cpNeed+GwesLowThreshold));
第二层阈值,如果超出则清空一些无用行程占用的堆栈,注意在这个过程中会修改PageFreeCount的值,因为如果有其它行程释放物理内存则会增加这个值,所以修改过程必须是不可分割的执行,否则可能会出现不一致,造成以后的增加无效。修改系统的核心数据结构KInfoTable中的闲置页项的时候同样是这个道理,请读者仔细体会这段原始码为什么要进行两次不可分割的执行,以及执行顺序。对协调系统工作的临界变量的使用有个初步的认识。
0729 // Even after scavenging stacks, we were unable to satisfy the request
0730 // without going below the GWE low threshold.
0731
0732 // Do not allow a request of size GwesLowBlockSize to succeed if
0733 // doing so would leave less than the low threshold. Same with
0734 // GwesCriticalBlockSize and GwesCriticalThreshold.
0735 if (bForce || !((cpNeed > GwesLowBlockSize
0736 && cpNeed+GwesLowThreshold > PageFreeCount)
0737 || (cpNeed > GwesCriticalBlockSize
0738 && cpNeed + GwesCriticalThreshold > PageFreeCount))) {
0739
第三层阈值,如果清空行程堆栈之后仍然不能满足条件,则要启动这层阈值,由于此时实体资源已经接近枯竭,所以除了要预留部分物理内存维持系统运行以外,还要限制每次分配的内存大小,由GwesLowBlockSize限定。分配之后向GWE系统发出内存资源不足的警告。
0740 // Memory is low. Notify GWE, so that GWE can ask
0741 // the user to close some apps.
0742 if (GwesOOMEvent &&
0743 ((PageFreeCount >= GwesLowThreshold) ||
0744 ((PageFreeCount < cpNeed + GwesCriticalThreshold))))
0745 fSetGweOomEvent = TRUE;
0746
0747 if ((cpNeed + (bForce?0:STACK_RESERVE)) <= PageFreeCount) {
0748 DWORD pfc, pfc2;
0749 do {
0750 pfc = PageFreeCount;
0751 } while (InterlockedTestExchange(&PageFreeCount,pfc,pfc-cpNeed) != (LONG)pfc);
0757 pfc -= cpNeed;
0758 do {
0759 pfc2 = KInfoTable[KINX_MINPAGEFREE];
0760 } while ((pfc2 > pfc)
0761 && (InterlockedTestExchange ((PLONG)&KInfoTable[KINX_MINPAGEFREE], pfc2,
pfc) != (LONG)pfc2));
0762 LogPhysicalPages(PageFreeCount);
0763 bRet = TRUE;
0764 }
0765 }
0766 hpDone:
0767 LeaveCriticalSection(&PhysCS);
0768 if (fSetGweOomEvent)
0769 SetEvent(GwesOOMEvent);
0770 return bRet;
0771 }
本节详细分析了HoldPages函数的原始码,在对物理内存的配置上,总体上分为三层阈值,这种层次结构可以较好地满足各种不同层次的需要,使得系统在时间和空间的总体效率比较好,较多地使用了系统的全域环境变量,能灵活地适应系统在不同状态下的需求。
10.2.2 小实验:HoldPages函数跟踪
在Platform Builder中配置一个应用程序,建立其debug版本,在HoldPages函数入口设置断点,可以跟踪其执行,由于在debug模式下无法仿真多行程协调工作的环境,只能简略的看看这个函数的工作过程。
图10-1 察看变数
这是在程序启动入口处设置断点时的变量值,由于系统刚刚启动,各阈值还都是0,由于Platform Builder的命名关联的原因,有些变量的值不能直接观察到,可以在汇编码中找到其地址,在除错窗口中直接读取内存的值。察看特定地址的内容如图1 0 - 2所示。
图10-2 察看特定地址的内容
从上图可以看出,PageFreeCount的值是0x 00000c 26,即3100个空闲页,大约有 12M 的空闲物理内存,跟实际试验机的空闲内存数相仿,接下来的除错工作就变得相对简单,可以设置变量cpNeed的值来仿真各种实用条件。也可以通过在进入临界区后改变PageFreeCount的值(可以在Memory 1窗口中直接进行修改)来做一致性检测。这部分工作留给读者自己去完成。
10.3 虚拟内存配置原始码片段
这部分的流程和提交部分的原始码分析在第4章已经给了,读者可再对照上下文读一下这段原始码,另外作者在认为需要的地方适当地增加了一些中文注释,为了节约篇幅,有些常数、变量的定义和一些简单的英文注释、除错信息、系统日志信息也略去。原始码定义在private/winceos/coreos/nk/kernel/virmem.c中。
0645 LPVOID
0646 DoVirtualAlloc(
0647 LPVOID lpvAddress0, /* address of region to reserve or commit */
0648 DWORD cbSize, /* size of the region */
0649 DWORD fdwAllocationType, /* type of allocation */
0650 DWORD fdwProtect, /* type of access protection */
0651 DWORD fdwInternal,
0652 DWORD dwPFNBase
0653 )
0654 {
0681 if (IsKernelVa (dwAddr) // invalid address?
0682 || !cbSize // size == 0?
0683 || ((cbSize >= (1<<VA_SECTION)) // size too big? (okay to reserver 32M with specific parameters)
0684 && (dwSlot0Addr || (fdwAllocationType != MEM_RESERVE) || (fdwProtect !=
PAGE_NOACCESS)))
0685 || !(ulPgMask = MakePagePerms(fdwProtect))) { // invalid flag?
0686 KSetLastError(pCurThread, ERROR_INVALID_PARAMETER);
0687 return NULL;
0688 }
这段原始码进行了参数验证,其原始码结构安排比较巧妙,将最经常发生的或比较时间最短的非法情况放在前面比较,可以让总合的平均比较时间最小。
0694 EnterCriticalSection(&VAcs);
0695
0696 if (IsSecureVa(dwAddr)) {
0697 aky = ProcArray[0].aky;
0698 pscn = &NKSection;
0699 dwBase = SECURE_VMBASE;
0700 } else {
0701 ix = dwAddr >> VA_SECTION;
0702 DEBUGCHK (ix <= SECTION_MASK); // can't be 0, but can be NULL_SECTION
0703 if ((pscn = SectionTable[ix]) == NULL_SECTION)
0704 goto invalidParm;
0705 dwBase = dwAddr & (SECTION_MASK<<VA_SECTION);
0706 // Make sure that when remotely allocating memory in another process that
0707 // we use the access key for the remote process instead of the current one.
0708 if (dwAddr < FIRST_MAPPER_ADDRESS) {
0709 if (ix && ix <= MAX_PROCESSES && ProcArray[ix-1].dwVMBase)
0710 aky = ProcArray[ix-1].aky;
0711 }
0712 }
0713
0714 if ((fdwAllocationType & MEM_RESERVE) || (!dwAddr && (fdwAllocationType &
MEM_COMMIT))) {
0715 // Set MEM_RESERVE so the error cleanup works properly.
0716 fdwAllocationType |= MEM_RESERVE;
0717
0718 ixBlock = dwSlot0Addr >> VA_BLOCK;
0719 if (dwSlot0Addr) {
0720 /* The client has asked to reserve a specific region of memory.
0721 * Verify that the requested region is within range and within an
0722 * existing memory section that the client is allowed to access.
0723 */
0724 /* adjust lpvAddress to 64K boundary. */
0725 dwAddr &= 0xFFFF 0000l ;
0726 /* Verify that the entire range is available to be reserved. */
0727 cPages = (dwEnd - dwAddr + PAGE_SIZE-1) / PAGE_SIZE;
0728 for (cNeed = cPages, ix = ixBlock ; cNeed > 0
0729 ; ++ix, cNeed -= PAGES_PER_BLOCK) {
0730 if ((*pscn)[ix] != NULL_BLOCK)
0731 goto invalidParm;
0732 }
这段原始码保留指定的虚拟内存地址,只需要简单地做合法性检查,并标记虚拟内存区块为Reserved,并不需要向系统申请虚拟内存地址。
0733 } else {
0734 /* No specific address given. Go find a region of free blocks */
0735 cPages = (dwEnd - dwAddr + PAGE_SIZE-1) / PAGE_SIZE;
0736 cNeed = cPages;
0737 if (((cPages * PAGE_SIZE >= 2*1024*1024) || (fdwInternal & MEM_SHAREDONLY))
0738 && (fdwAllocationType == MEM_RESERVE)
0739 && (fdwProtect == PAGE_NOACCESS)) {
0740 DEBUGMSG(ZONE_VIRTMEM, (TEXT("VirtualAlloc(%8.8lx, %lx, %lx, %lx):
doing HugeVirtualAlloc/r/n"),
0741 lpvAddress0, cbSize, fdwAllocationType, fdwProtect));
0742 LeaveCriticalSection(&VAcs);
0743 lpvResult = HugeVirtualReserve(cbSize, (fdwInternal & MEM_SHAREDONLY)
? MEM_SHAREDONLY : 0);
0744 if (IsCeLogEnabled()) {
0745 CELOG_VirtualAlloc((DWORD) lpvResult,(DWORD) dwAddr, cbSize, fdwAllocationType, fdwProtect);
0746 }
0747 return lpvResult;
0748 }
0749 if (fdwAllocationType & MEM_TOP_DOWN) {
0750 /* Scan backwards from the end of the section */
0751 for (ix = BLOCK_MASK+1 ; --ix > 0 ; ) {
0752 if ((*pscn)[ix] != NULL_BLOCK)
0753 cNeed = cPages;
0754 else if ((cNeed -= PAGES_PER_BLOCK) <= 0) {
0755 ixBlock = ix;
0756 goto foundRegion;
0757 }
0758 }
0759 } else {
0760 /* Scan forwards from the beginning of the section */
0761 ixBlock = 1;
0762 for (ix = 1 ; ix < BLOCK_MASK+1 ; ++ix) {
0763 if ((*pscn)[ix] != NULL_BLOCK) {
0764 ixBlock = ix+1;
0765 cNeed = cPages;
0766 } else if ((cNeed -= PAGES_PER_BLOCK) <= 0)
0767 goto foundRegion;
0768 }
0769 }
0770 err = ERROR_NOT_ENOUGH_MEMORY;
0771 goto setError;
0772 foundRegion:
0773 dwAddr = dwBase | ((ulong)ixBlock << VA_BLOCK);
0774 }
0775 /* Reserve the range of blocks */
0776 if (!(pmb = AllocMem(HEAP_MEMBLOCK))) {
0777 err = ERROR_NOT_ENOUGH_MEMORY;
0778 goto setError;
0779 }
0780 memset(pmb,0,sizeof(MEMBLOCK));
0781 LockFromKey(&pmb->alk, &aky);
0782
0783 DEBUGMSG(ZONE_VIRTMEM, (TEXT("VirtualAlloc: reserved lock=%lx/r/n"),
pmb->alk));
0784 pmb->cUses = 1;
0785 switch (fdwAllocationType & (MEM_MAPPED|MEM_IMAGE)) {
0786 case MEM_MAPPED:
0787 pmb->flags = MB_PAGER_MAPPING;
0788 break;
0789 case MEM_IMAGE:
0790 pmb->flags = MB_PAGER_LOADER;
0791 break;
0792 case 0:
0793 pmb->flags = MB_PAGER_NONE;
0794 break;
0795 default:
0796 goto invalidParm;
0797 }
0798 if (fdwAllocationType & MEM_AUTO_COMMIT)
0799 pmb->flags |= MB_FLAG_AUTOCOMMIT;
0800 pmb->ixBase = ixBlock;
0801 (*pscn)[ixBlock] = pmb;
0802 DEBUGMSG(ZONE_VIRTMEM,
0803 (TEXT("VirtualAlloc: created head block pmb=%8.8lx (%x)/r/n"),
0804 pmb, ixBlock));
0805 ixFirB = ixBlock; /* note start of region for error recovery */
0806 for (cNeed = cPages, ix = ixBlock+1 ; (cNeed -= PAGES_PER_BLOCK) > ++ix) {
0807 if (cNeed < PAGES_PER_BLOCK || (fdwAllocationType & MEM_AUTO_COMMIT)) {
0808 /* The last block is not full so must allocate a MEMBLOCK
0809 * and mark the extra pages as invalid or this is an auto-commit
0810 * region which must have all memblocks filled in so that
0811 * pages can be committed without entering the virtual memory
0812 * critical section. */
0813 if (!(pmb = AllocMem(HEAP_MEMBLOCK))) {
0814 err = ERROR_NOT_ENOUGH_MEMORY;
0815 goto setError;
0816 }
0817 memset(pmb,0,sizeof(MEMBLOCK));
0818 pmb->alk = aky;
0819 pmb->flags = (*pscn)[ixFirB]->flags;
0820 pmb->cUses = 1;
0821 pmb->ixBase = ixFirB;
0822 (*pscn)[ix] = pmb;
0823 DEBUGMSG(ZONE_VIRTMEM,
0824 (TEXT("VirtualAlloc: created a tail block pmb=%8.8lx (%x)/r/n"),
0825 pmb, ixBlock));
0826 } else
0827 (*pscn)[ix] = RESERVED_BLOCK;
0828 }
0829 DEBUGMSG(ZONE_VIRTMEM, (TEXT("Reserved %d pages @%8.8lx/r/n"), cPages,
dwAddr));
0830 if (cNeed) {
0831 /* Set unused entries to BAD_PAGE */
0832 DEBUGMSG(ZONE_VIRTMEM, (TEXT("Reserved %d extra pages./r/n"), - cNeed));
0833 cNeed += PAGES_PER_BLOCK;
0834 for (ix = cNeed ; ix < PAGES_PER_BLOCK ; ++ix)
0835 pmb->aPages[ix] = BAD_PAGE;
0836 }
0837 /* If not committing pages, then return the address of the
0838 * reserved region */
0839 if (!(fdwAllocationType & MEM_COMMIT)) {
0840 LeaveCriticalSection(&VAcs);
0841 ERRORMSG(!dwAddr,(L"Failed VirtualAlloc/reserve of %8.8lx bytes/r/n",cbSize));
0842 if (IsCeLogEnabled()) {
0843 CELOG_VirtualAlloc(dwAddr, (DWORD) lpvAddress0, cbSize, fdwAllocationType, fdwProtect);
0844 }
0845 return (LPVOID) dwAddr;
0846
0847 }
这段原始码处理了没有指定虚拟内存地址的情况下,由系统寻找空间虚拟内存区块的情况。在这个过程中,系统还负责修改已保留的虚拟内存区块的各种信息。
0848 } else {
0849 //
0850 // Not reserving memory, so must be committing. Verify that the
0851 // requested region is within range and within an existing reserved
0852 // memory region that the client is allowed to access.
0853 //
0854 if (!(fdwAllocationType & MEM_COMMIT)
0855 || !dwAddr
0856 // || (!(fdwInternal&MEM_NOSTKCHK) && IsStackVA (dwSlot0Addr | (dwBase? dwBase : pCurProc->dwVMBase), cbSize))
0857 || (fdwInternal & MEM_CONTIGUOUS))
0858 goto invalidParm;
0859
0860 ixBlock = (dwAddr >> VA_BLOCK) & BLOCK_MASK;
0861 /* Adjust lpvAddress to PAGE boundary and calculate the number
0862 * of pages to commit. */
0863 dwAddr &= ~(PAGE_SIZE-1);
0864 cPages = (dwEnd - dwAddr + PAGE_SIZE-1) / PAGE_SIZE;
0865 /* locate the starting block of the region */
0866 if ((ixFirB = FindFirstBlock(pscn, ixBlock)) == UNKNOWN_BASE)
0867 goto invalidParm;
0868 }
0869 /* Verify that cPages of memory starting with the first page indicated by
0870 * lpvAddress can be committed within the region.
0871 *
0872 * (pscn) = ptr to section array
0873 * (ixBlock) = index of block containing the first page to commit
0874 * (cPages) = # of pages to commit
0875 */
0876 ixPage = (dwAddr >> VA_PAGE) & PAGE_MASK;
0877 cpAlloc = ScanRegion(pscn, ixFirB, ixBlock, ixPage, cPages, 0);
0878 if (cpAlloc == -1)
0879 goto cleanUp;
0880 //
0881 // Commit cPages of memory starting with the first page indicated by
0882 // lpvAddress. Allocate all required pages before any changes to the
0883 // virtual region are performed.
0884 //
0885 // (pscn) = ptr to section array
0886 // (ixBlock) = index of block containing the first page to commit
0887 // (cPages) = # of pages to commit
0888 // (cpAlloc) = # of physical pages required
0889 //
0890 DEBUGMSG(ZONE_VIRTMEM, (TEXT("Allocating %d pages./r/n"), cpAlloc));
0891
0892 if (fdwInternal & MEM_CONTIGUOUS) {
0893 //
0894 // Map to physically contiguous pages. Mark as LOCKED as we go.
0895 // Walk the page tables to map in the physical pages.
0896 //
0897 ULONG ulPFN = dwPFNBase;
0898
0899 for (; cPages ; ixBlock++) {
0900 pmb = (*pscn)[ixBlock];
0901 pmb->cLocks++;
0902 ix = 0;
0903 for (; cPages && ix < PAGES_PER_BLOCK; --cPages) {
0904 DEBUGCHK(pmb->aPages[ix] == 0);
0905 pmb->aPages[ix] = ulPFN | ulPgMask;
0906 ulPFN = NextPFN(ulPFN);
0907 ix++;
0908 }
0909 }
指定物理内存地址的映像,系统只需要验证有效性并填写映像信息,不必抓取实体页。
0910 } else {
0911 if (!HoldPages(cpAlloc, FALSE)) {
0912 err = ERROR_NOT_ENOUGH_MEMORY;
0913 goto setError;
0914 }
0915 ixBlock = (ixBlock*PAGES_PER_BLOCK+ixPage+cPages-1)/PAGES_PER_BLOCK;
0916 ix = ((ixPage + cPages - 1) % PAGES_PER_BLOCK) + 1;
0917 //
0918 // Walk the page tables to map in the physical pages.
0919 //
0920 for (; cPages ; --ixBlock) {
0921 pmb = (*pscn)[ixBlock];
0922 for (; cPages && ix-- > 0 ; --cPages) {
0923 if (pmb->aPages[ix] == 0) {
0924 DWORD dwRetries;
0925 for(dwRetries=0; (dwRetries < 20) && !(ulPFN = GetHeldPage()); dwRetries++)
0926 Sleep(100);
0927 if (ulPFN) {
0928 DEBUGMSG(ZONE_VIRTMEM, (TEXT("Mapping %8.8lx @%3.3x%x000 perm=%x/r/n"),
0929 ulPFN, ixBlock, ix, ulPgMask));
0930 pmb->aPages[ix] = ulPFN | ulPgMask;
0931 } else {
0932 InterlockedIncrement(&PageFreeCount);
0933 RETAILMSG(1,(L"--->>> VirtualAlloc: FATAL ERROR! COMPLETELY OUT OF MEMORY (%8.8lx)!/r/n",PageFreeCount));
0934 }
0935 } else
0936 pmb->aPages[ix] = (pmb->aPages[ix] & ~PG_PERMISSION_MASK) | ulPgMask;
0937 }
0938 ix=PAGES_PER_BLOCK; /*start with last page of block */
0939 }
0940 }
0941 DEBUGMSG(ZONE_VIRTMEM, (TEXT("Calling InvalidateRange (%8.8lx, %d)/n"), dwAddr, cbSize));
0942 InvalidateRange((LPVOID) dwAddr, cbSize); // in case we changed permissionsabove
0943 LeaveCriticalSection(&VAcs);
0944 ERRORMSG(!dwAddr, ( L"FailedVirtualAlloc(%8.8lx) of %8.8lx bytes/r/n", fdwAllocationType, cbSize));
0945
0946 if (IsCeLogEnabled()) {
0947 CELOG_VirtualAlloc(dwAddr, dwAddr, cbSize, fdwAllocationType, fdwProtect);
0948 }
0949 DEBUGMSG(ZONE_VIRTMEM, (TEXT("Returing %8.8lx/n"), dwAddr));
0950 return (LPVOID) dwAddr;
0951
0952 //
0953 // There was an error reserving or commiting a range of pages. If reserving
0954 // pages, release any pages which were reserved before the failure occured.
0955 //
0956 invalidParm:
0957 err = ERROR_INVALID_PARAMETER;
0958 setError:
0959 DEBUGMSG(ZONE_VIRTMEM, (TEXT("VirtualAlloc failed. error=%d/r/n"), err));
0960 KSetLastError(pCurThread, err);
0961 cleanUp:
0962 if (fdwAllocationType & MEM_RESERVE && ixFirB != UNKNOWN_BASE) {
0963 /* Remove the reservation */
0964 ReleaseRegion(pscn, ixFirB);
0965 }
0966 LeaveCriticalSection(&VAcs);
0967 ERRORMSG(!dwAddr, (L"Failed VirtualAlloc(%8.8lx) of %8.8lx bytes/r/n", fdwAllocationType, cbSize));
0968 return 0;
0969 }
这段原始码要向系统申请物理内存资源,并建立实体地址和虚拟内存地址的映像,并提供了整个操作的不可分割性保证,在出现错误的情况下支持回朔。