第10 章虚拟内存.docx
《第10 章虚拟内存.docx》由会员分享,可在线阅读,更多相关《第10 章虚拟内存.docx(24页珍藏版)》请在冰豆网上搜索。
第10章虚拟内存
第10章
虚拟内存
虚拟内存储存管理是现代操作系统的核心技术之一,它是整个系统高效合理地管理储存资源的基本保障,尤其是在嵌入式系统中,系统自身资源管理占的比例相对较大的情况下,就显得更为重要。
在WindowsCE中,除了个别对实时性能要求很高的应用程序需要关闭分页以外,大部分的应用程序都要求系统在虚拟储存上花费很大的比例。
本章从系统的角度在特定的环境下跟踪虚拟内存的配置部分,因为虚拟内存配置是系统和使用者程序连接的主要渠道之一,配置的算法也在很大程度上决定了系统的性能。
在分析过程中,还要对实体页的获取过程进行详细地分析,因为实体页面是系统极其珍贵的资源,对其的管理要格外小心。
本章将会对在这个过程中可能出现的情况逐一分析,力图完整的向读者展示从系统的视角看到的储存管理技术。
10.1配置过程概述
在本书的第4章初步介绍了虚拟内存的配置,下面再详细地介绍一下配置的各个具体过程(总体分析及流程图参见第4章)。
虚拟内存的状态分为提交(committed)和保留(reserved),这两部分功能都是在DoVitualAlloc中实现的。
先看一下该函数的入口参数
LPVOIDlpvAddress0,/*要保存或提交的区域地址*/
DWORDcbSize,/*区域大小*/
DWORDfdwAllocationType,/*配置方式*/
DWORDfdwProtect,/*存取保护方式*/
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小实验:
虚拟内存配置的直观印象
用PlatformBuilder建立一个应用程序,下载到终端机上,观察它的储存配置。
在控制台的命令行下键入mifull可得到有关储存管理的全部信息,关于虚拟内存配置的部分信息摘录如下:
MemoryusageforProcess81a9711c:
'device.exe'pid3
Slotbase08000000Sectionptr83fca000
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
080a0000(0):
---------------S
080b0000(0):
---------------S
080c0000(0):
--------------SS
080d0000(0):
---------------S
080e0000(0):
---------------S
080f0000(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):
---------------
081a0000(0):
---------------S
081b0000(0):
W---------------
081d0000(0):
---------------
081e0000(0):
---------------S
081f0000(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
083a0000(0):
---------------S
083b0000(0):
---------------S
083c0000(0):
---------------S
083d0000(0):
---------------S
083e0000(0):
-------SSSSSSSSS
083f0000(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---------
09f50000(0):
-----WW---------
09f60000(0):
----------W-----
09f70000(0):
----W-----W-W---
09f80000(0):
W---W-W--W-W-W-W
09f90000(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
Pagesummary:
code=6
(1)datar/o=0r/w=193stack=52reserved=1216
这是系统分配给行程device.exe的虚拟内存储存槽(Slot),可以看出每个Slot的第一行(每行16个页)用来存放该Slot的信息,堆栈都是在每行末尾向前增长,经过几次分配回收,Slot中出现了一些碎片。
在该例子中,最长的一段虚拟内存是09e70000~09ff0000共384页。
10.2物理内存的获取
10.2.1分配过程
下面详细地分析一下系统在调度实际物理内存时的行为,之所以选择这部分做详细地分析,是因为它是整个系统对公共资源进行分配的底层行为,从系统作为资源管理者的角度,可以从宏观上把握控制各微观行为之间的关系,可以很好地呈现系统的整体性,以及各部分之间的交互作用。
而且这部分虽然重要,却只有短短的100多行原始码,因为系统把对物理内存的分配简化成对一个全局变量(PageFreeCount)的切割,很好地实现了系统设计的模块化想法。
原始码中几个关于控制的阈值的选取也是经过深思熟虑的,可以帮助理解系统设计者在各个方面的权衡,也能帮助理解嵌入式系统对性能的要求与通用系统的差异。
下面对Holdpages函数这段原始码做详细分析,这部分原始码定义在[CEROOT]private\winceos\coreos\nk\kernel\physmem.c中,原始码的流程图参见第4四章图4-11。
0684BOOL
0685HoldPages(
0686intcpNeed,
0687BOOLbForce
0688)
0689{
0690BOOLfSetGweOomEvent=FALSE;
0691BOOLbRet=FALSE;
0692WORDprio;
0693EnterPhysCS();
0694//Checkifthisrequestwilldropthepagefreecountbelowthe
0695//pageouttriggerandsignalthecleanupthreadtostartdoing
0696//pageoutsifso.
0697if((cpNeed+PageOutTrigger>PageFreeCount)&&
0698(!
PageOutNeeded||(PagedInCount>PAGE_OUT_TRIGGER))){
0699PageOutNeeded=1;
0700PagedInCount=0;
0701if(prio=GET_CPRIO(pCurThread))
0702prio--;
0703if(prio0704KCall((PKFN)SetThreadBasePrio,pCleanupThread->hTh,(DWORD)prio);
0705SetEvent(hAlarmThreadWakeup);
0706}
这段原始码首先进入物理内存临界区,检测内存分配是否低于一定值,来决定是否要启动换页行程,将部分实体页换出。
具体表现是将PCleanupThread的优先级设置为比目前行程略低,这样就可以在目前行程执行完毕之后马上启动。
请注意,PageOutNeeded,PagedInCount,PageOutTrigger都是与schedule.c和loader.c通信的变量,其值和系统当时的执行状态密切相关。
0707do{
0708if(cpNeed+GwesLowThreshold<=PageFreeCount){
0709DWORDpfc,pfc2;
0710do{
0711pfc=PageFreeCount;
0712}while(InterlockedTestExchange(&PageFreeCount,pfc,pfc-cpNeed)!
=(LONG)pfc);
0718pfc-=cpNeed;
0719do{
0720pfc2=KInfoTable[KINX_MINPAGEFREE];
0721}while((pfc2>pfc)
0722&&(InterlockedTestExchange((PLONG)&KInfoTable[KINX_MINPAGEFREE],pfc2,
pfc)!
=(LONG)pfc2));
0723LogPhysicalPages(PageFreeCount);
0724bRet=TRUE;
0725gotohpDone;
0726}
0727}while(ScavengeStacks(cpNeed+GwesLowThreshold));
第二层阈值,如果超出则清空一些无用行程占用的堆栈,注意在这个过程中会修改PageFreeCount的值,因为如果有其它行程释放物理内存则会增加这个值,所以修改过程必须是不可分割的执行,否则可能会出现不一致,造成以后的增加无效。
修改系统的核心数据结构KInfoTable中的闲置页项的时候同样是这个道理,请读者仔细体会这段原始码为什么要进行两次不可分割的执行,以及执行顺序。
对协调系统工作的临界变量的使用有个初步的认识。
0729//Evenafterscavengingstacks,wewereunabletosatisfytherequest
0730//withoutgoingbelowtheGWElowthreshold.
0731
0732//DonotallowarequestofsizeGwesLowBlockSizetosucceedif
0733//doingsowouldleavelessthanthelowthreshold.Samewith
0734//GwesCriticalBlockSizeandGwesCriticalThreshold.
0735if(bForce||!
((cpNeed>GwesLowBlockSize
0736&&cpNeed+GwesLowThreshold>PageFreeCount)
0737||(cpNeed>GwesCriticalBlockSize
0738&&cpNeed+GwesCriticalThreshold>PageFreeCount))){
0739
第三层阈值,如果清空行程堆栈之后仍然不能满足条件,则要启动这层阈值,由于此时实体资源已经接近枯竭,所以除了要预留部分物理内存维持系统运行以外,还要限制每次分配的内存大小,由GwesLowBlockSize限定。
分配之后向GWE系统发出内存资源不足的警告。
0740//Memoryislow.NotifyGWE,sothatGWEcanask
0741//theusertoclosesomeapps.
0742if(GwesOOMEvent&&
0743((PageFreeCount>=GwesLowThreshold)||
0744((PageFreeCount0745fSetGweOomEvent=TRUE;
0746
0747if((cpNeed+(bForce?
0:
STACK_RESERVE))<=PageFreeCount){
0748DWORDpfc,pfc2;
0749do{
0750pfc=PageFreeCount;
0751}while(InterlockedTestExchange(&PageFreeCount,pfc,pfc-cpNeed)!
=(LONG)pfc);
0757pfc-=cpNeed;
0758do{
0759pfc2=KInfoTable[KINX_MINPAGEFREE];
0760}while((pfc2>pfc)
0761&&(InterlockedTestExchange((PLONG)&KInfoTable[KINX_MINPAGEFREE],pfc2,
pfc)!
=(LONG)pfc2));
0762LogPhysicalPages(PageFreeCount);
0763bRet=TRUE;
0764}
0765}
0766hpDone:
0767LeaveCriticalSection(&PhysCS);
0768if(fSetGweOomEvent)
0769SetEvent(GwesOOMEvent);
0770returnbRet;
0771}
本节详细分析了HoldPages函数的原始码,在对物理内存的配置上,总体上分为三层阈值,这种层次结构可以较好地满足各种不同层次的需要,使得系统在时间和空间的总体效率比较好,较多地使用了系统的全域环境变量,能灵活地适应系统在不同状态下的需求。
10.2.2小实验:
HoldPages函数跟踪
在PlatformBuilder中配置一个应用程序,建立其debug版本,在HoldPages函数入口设置断点,可以跟踪其执行,由于在debug模式下无法仿真多行程协调工作的环境,只能简略的看看这个函数的工作过程。
图10-1察看变数
这是在程序启动入口处设置断点时的变量值,由于系统刚刚启动,各阈值还都是0,由于PlatformBuilder的命名关联的原因,有些变量的值不能直接观察到,可以在汇编码中找到其地址,在除错窗口中直接读取内存的值。
察看特定地址的内容如图10-2所示。
图10-2察看特定地址的内容
从上图可以看出,PageFreeCount的值是0x00000c26,即3100个空闲页,大约有12M的空闲物理内存,跟实际试验机的空闲内存数相仿,接下来的除错工作就变得相对简单,可以设置变量cpNeed的值来仿真各种实用条件。
也可以通过在进入临界区后改变PageFreeCount的值(可以在Memory1窗口中直接进行修改)来做一致性检测。
这部分工作留给读者自己去完成。
10.3虚拟内存配置原始码片段
这部分的流程和提交部分的原始码分析在第4章已经给了,读者可再对照上下文读一下这段原始码,另外作者在认为需要的地方适当地增加了一些中文注释,为了节约篇幅,有些常数、变量的定义和一些简单的英文注释、除错信息、系统日志信息也略去。
原始码定义在private\winceos\coreos\nk\kernel\virmem.c中。
0645LPVOID
0646DoVirtualAlloc(
0647LPVOIDlpvAddress0,/*addressofregiontoreserveorcommit*/
0648DWORDcbSize,/*sizeoftheregion*/
0649DWORDfdwAllocationType,/*typeofallocation*/
0650DWORDfdwProtect,/*typeofaccessprotection*/
0651DWORDfdwInternal,
0652DWORDdwPFNBase
0653)
0654{
0681if(IsKernelVa(dwAddr)//invalidaddress?
0682||!
cbSize//size==0?
0683||((cbSize>=(1<(okaytoreserver32Mwithspecificparameters)
0684&&(dwSlot0Addr||(fdwAllocationType!
=MEM_RESERVE)||(fdwProtect!
=
PAGE_NOACCESS)))
0685||!
(ulPgMask=MakePagePerms(fdwProtect))){//invalidflag?
0686KSetLastError(pCurThread,ERROR_INVALID_PARAMETER);
0687returnNULL;
0688}
这段原始码进行了参数验证,其原始码结构安排比较巧妙,将最经常发生的或比较时间最短的非法情况放在前面比较,可以让总合的平均比较时间最小。
0694EnterCriticalSection(&VAcs);
0695
0696if(IsSecureVa(dwAddr)){
0697aky=ProcArray[0].aky;
0698pscn=&NKSection;
0699dwBase=SECURE_VMBASE;
0700}else{
0701ix=dwAddr>>VA_SECTION;
0702DEBUGCHK(ix<=SECTION_MASK);//can'tbe0,butcanbeNULL_SECTION
0703if((pscn=SectionTable[ix])==NULL_SECTION)
0704gotoinvalidParm;
0705dwBase=dwAddr&(SECTION_MASK<0706//Makesurethatwhenremotelyallocatingmemoryinanotherprocessthat
0707//weusetheaccesskeyfortheremoteprocessinsteadofthecurrentone.
0708if(dwAddr0709if(ix&&ix<