天天看点

Windows核心编程——》第十三章 Windows内存架构(Windows Memory Architecture)

1.进程虚拟地址空间

                每个进程都有自已的私有的虚拟地址空间,在32位机器上是4G,在64位机器上是16EB。

                进程内的线程只能访问其所属进程所占的内存,其它进程的内存对其而言是不可见的,无法访问到。

2.虚拟地址空间是如何划分的

                以32位 x86进程的虚拟地址被分成四个区域(Partition)

                (1)空指针区域

              范围:0x00000000 ~ 0x0000FFFF

              地址空间中的以上部分是专门留出来帮助程序员捕获空指针操作,如果线程试图读、写该区域所占的内存会导致进程异常退出。

              (2)用户模式区域

                范围:0x00010000 ~ 0x7FFEFFFF (共2G)

                任何进程都不可读、写其它进程在该区域存放的数据。

                所有的DLL和EXE文件都会加载到该区

                通过控制BCD可以增加用户模式区哉的范围,详见:

                http://www.microsoft.com/whdc/system/platform/firmware/bcd.mspx

                (3)内核模式区域

                范围:0x80000000 ~ 0xFFFFFFFF

                操作系统的代码存放在这里,如线程、内存管理、文件系统支持、网络支持、设备驱动。

                该区存放的所有东西被所有进程共享。

                该区存放的代码和数据会被保护起来,应用程序若试图读、写该区的内存会导致异常退出,

             除非应用程序的线程则用户模式切换到内核模式。

                (4)64KB禁上入内区域

                范围:0x7FFF0000 ~ 0x7FFFFFFF

3.地址空间内的区域

                VirtualAlloc该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页。

                根据CPU不同,VirtualAlloc分配的粒度也不同,但一般都是以64KB为基本单位。

                不同CPU分页大小也不同,X86和X64分页大小是4K

                (1)保留地址空间 Reserve address space

           保留地址空间:在进程地址空间中保留出一块地址空间也备后用。因为这块地址空间并没有实际的存储映射,所以若访问这块内存会导致进程异常退出。

         VOID GetSystemInfo(LPSYSTEM_INFO psi);取得系统分页大小、内存分配粒度(Allocation Granularity)

                                在保留地址空间时有两点限制:

                                1.被保留地址空间的首地址必须以内存分配粒度为边界(地址的值可以整除内存分配粒度的值)

                                2.被保留地址空间的大小必须是页大小的整数倍,若不是整数倍系统会自动对齐到整数倍。

                                例如,页大小为4K,若要保留10KB大小的空间,系统会自动对齐,保留12K(4*3)大小的空间

                (2)提交物理存储到保留的地址空间

                                物理存储包括:物理内存和用做虚拟内存的页文件。

                                物理存储总是以分页大小为基本单位提交的

4.物理内存和页文件

              物理内存(RAM):内存硬件的实际存储容量

              虚拟内存:用于当做内存来弥补计算机RAM空间缺乏的硬盘空间。当实际RAM满时(实际上,在RAM满之前),虚拟内存就在硬盘上创建了。

                              当物理内存用完后,虚拟内存管理器选择最近没有用过的,低优先级的内存部分写到交换文件上。

                              这个过程对应用是隐藏的,应用程序把虚拟内存和实际内存看作是一样的。虚拟内存大小是由系统的页文件(Paging file)限制的。

5.数据对齐

           如果一个变量的地址可以整除该变量所占内存大小,那么就称该变量是数据对齐的。

           如DWORD的大小是4 若其地址为0x00000004 那么它就是数据对齐的。

7.Copy-On-Write

                一个应用程序的多个实例在运行时共享代码段或数据段。但某个实例修改全局或静态变量时,系统会以Copy-on-write的方式修改全局变量。

                即,系统会新建一个页,然后所要修改的全局变量所在的页的内容拷贝到新的页上,再在新的页上修改全局变量。 

8.系统信息

              用虚拟内存编程时需要用到一些系统的一些信息,如分页大小,分配粒度大小等等。

              为了避免将这此信息Hard Code到代码中,使程序具有更好的移植性,我们可能通过调用下面的方法获取我们需要的信息。

              VOID GetSystemInfo(LPSYSTEM_INFO psi);

9.Common API

              VirtualAlloc             VirtualFree

              VirtualQuery

1.         在32位系统上,虚拟地址空间大致分为4段(64位系统也分为4段,只是大小不同):(1)0x00000000~0x0000ffff,空指针赋值区,辅助调试,禁止任何方式的访问。(2)0x00010000~0x7ffeffff,用户模式分区,各进程单独维护,同一地址值在不同的进程可以有不同解释,各种映像文件(dll、exe)和内存映射文件也载入本区,近2G。(3)0x7fff0000~0x7fffffff,64k禁入分区。(4)0x80000000~0xffffffff,内核模式分区,系统存放内核代码、设备驱动代码、输入输出高速缓存、进程页表等,2G。

2.         32位系统可以配置系统参数让进程用户模式分区达到3G,内核减小为1G。内核内存减小,会影响可以创建的总线程、内核对象数量等。(Visita系统以上,使用bcdedit /set IncreaseUserVa 3072;xp使用…)。

3.         链接选项-启用大地址(/LARGEADDRESSAWARE):因为过去32位系统用户地址空间固定为2G(直到可以设置用户地址最大到3G),所以有惯用法依赖于这种行为(系统对地址参数会先&0x7fffffff的行为)擅自将地址最高位用于其他目的,为了兼容大量的这种用法并且又允许选择使用3G用户内存,MS增加了这个链接选项。如果开启,表示承诺不使用最高位,想要访问超过2G的用户地址;关闭,表示只使用2G内存,最高位可能有其他解释(在实际的系统实现上,如果用户地址最高位非0会报错)。64位系统中,为了便于大量32位程序向64位移植(32位程序中有大量用法如:int i = (int)p; …; int *p = (int)i;),系统默认程序只使用2G用户空间,所以分配的用户地址总是小于2G,直到开启该连接选项。总之,无论32位或64位系统,如果只使用2G,关闭选项,否则开启。

4.         VirtualAlloc的MEM_RESERVE参数表示要预定一段空间(如线程栈,即使大部分时候栈都很小,但也需要预留1M左右),叫区域(region)。用户代码申请预留的起始地址必须按allocation granularity(分配粒度,因CPU而异,但当前CPU大都为64KB)对齐,系统的预留申请无限制(如PEB占用的内存是系统申请的)。预留的大小必须按页面大小对齐(x86、x64CPU的页面大小为4KB)。VirtualAlloc的MEM_COMMIT参数表示将区域commit给虚拟存储器,系统会在使用时将对应的页缓存到物理存储器。

5.         在操作系统内存管理模型中,虚拟地址用于访问虚拟存储器,后者存放于磁盘上,主存作为虚拟存储器和CPU之间的缓存(DRAM)被叫做物理存储器。当CPU要访问内存时,首先,检查该虚拟地址是否对应合法的虚拟存储器(是否commit),如果否则报错表示无效地址,如果是,然后判断该虚拟页(VP,Virtual Page)是否被缓存到内存,即是否有对应的物理页(PP, Physical Page),如果否则产生缺页错误(Page Fault)进而判断主存中是否有闲置页面,如果没有闲置页面,则尝试释放一个物理页,先判断要释放的物理页是否被修改,如果被修改了则Flush到对应的虚拟页上然后释放物理页,有了闲置的物理页后,将虚拟地址对应的虚拟页缓存到空闲的物理页上进而更新虚拟地址到物理地址的映射表,然后CPU的MMU(memory management unit,内存管理单元)将虚拟地址翻译为物理地址,再判断该地址对应的内容是否已经在Cache上,如果否则Cache Miss然后再将对应的Cache Line缓存到Cache中,最后读取到CPU寄存器中。在Windows中,虚拟存储器对应的磁盘空间进一步细分到页交换文件(page file)、映像文件(exe、dll)、内存映射文件(mapped file)中,后两者被当做虚拟存储器的时候还可以在多个进程间共享(写时拷贝),由于存在共享机制因而Windows的虚拟存储器占的磁盘空间远小于所有进程提交的用户模式内存之和。

6.         VirtualAlloc、VirtualProtect等函数可以设置页保护属性:PAGE_EXECUTE(只能运行代码不能读写)、PAGE_EXECUTE_READ(只读和运行代码)、PAGE_NOACCESS等。其中PAGE_WRITECOPY、PAGE_EXECUTE_WRITECOPY属性表示页面可以被多个进程共享,直到被修改,修改时是先拷贝到进程私有页中再修改私有页,这是copy-on-write。Reserve状态下的保护属性会被Commit下的属性覆盖,但两者都可以在VirtualQuery中查询到。

7.         在CPU体系结构中,CPU要访问的数据需要按数据大小对齐(WORD地址按2对齐,DWORD地址按4对齐),否则会产生异常。修复数据未对齐异常有几种途径:(1)x86 CPU会自动进行硬件修复,访问没对齐的数据只是更慢。(2)SetErrorMode传入SEM_NOALIGNMENTFAULTEXCEPT参数,通知Windows通过软件修复未对齐问题。(3)编译选项__unaligned会自动产生额外代码修复问题。综上,后两种软件修复方案适用于非x86 CPU速度更慢,最好还是按数据大小对齐内存。

继续阅读