Richie

Sometimes at night when I look up at the stars, and see the whole sky just laid out there, don't you think I ain't remembering it all. I still got dreams like anybody else, and ever so often, I am thinking about how things might of been. And then, all of a sudden, I'm forty, fifty, sixty years old, you know?

[翻译] - Inside SQL Server 2000's Memory Management Facilities

    原文地址:Inside SQL Server 2000's Memory Management Facilities
    翻译:RicCC

    Ken Henderson
    Microsoft Corporation
    January 2004
 
    本专栏摘选自Ken Henderson的《The Guru's Guide to SQL Server Architecture and Internals》(Addison-Wesley, 2003)。使用许可。版权所有。
    摘要:Ken Henderson从开发者的角度探讨了SQL Server内存管理内幕。

    介绍
    在这篇专栏里,我们将从开发者的角度来探讨SQL Server内存管理内幕。就是说,我们将讨论SQL Server使用API和操作系统功能管理内存的方式及其工作原理。通过这种方式探讨一个产品,将有助于我们理解产品开发者的思路,以及他们设计的使用方法。理解一个产品的工作原理和它的设计用途,是掌握这个产品的关键。
    我们将从基础的Windows内存管理基本原理介绍开始。和所有32位Windows应用程序一样,SQL Server使用Windows内存管理功能分配、释放、管理内存资源,它调用Win32内存管理API函数,与操作系统提供的内存资源进行交互。
    由于SQL Server中几乎所有的内存分配都使用虚拟内存(不是内存堆),因此绝大部分内存分配代码最终都是通过调用Win32的VirtualAlloc或者是 VirtualFree函数完成。SQL Server调用VirtualAlloc预留、提交虚拟内存,调用VirtualFree释放虚拟内存。

    虚拟内存与物理内存
    在x86系列处理器上,Windows为所有进程提供一个4GB虚拟内存工作空间。用"虚拟"这个词,意思是这个内存并不是通常意义上的内存,它只是一个地址范围,并没有和物理存储单元关联在一起。当进程请求内存分配时,这些地址空间才被使用,和具体的物理存储单元关联起来。然而这些物理存储单元并不一定是物理内存,它通常可能是磁盘空间,确切的说,是操作系统的分页文件(System Paging Files)。这就是为什么多个应用程序可以同时运行在一个128M内存的系统上,每个应用程序都有一个4GB的虚拟内存地址空间--它不是真正的内存,但对应用程序来说可以理解为内存。Windows透明的处理分页文件(paging files)的数据交换,使应用程序能够使用的内存可以超过机器的实际物理内存,并使应用程序能够公平的存取机器的物理内存。
    这个4GB的地址空间被分成两部分:用户模式(user mode)部分和内核模式(kernal mode)部分。默认情况下,每个部分的大小为2GB,在Windows NT系列的操作系统上,可以通过BOOT.INI中的开关来改变这个默认设置(Windows NT, Windows 2000, Windows XP和Windows Server 2003属于Windows NT系列,Windows 9x和Windows ME不属于)。
  
    图1:Windows将进程的虚拟地址空间分成用户模式(应用程序)和内核模式(操作系统)两个部分
    每个应用程序拥有自己的虚拟内存地址空间,但操作系统和设备驱动程序共享同一个私有地址空间。每一个虚拟内存页都和特定的处理器模式相关联,为了存取某个虚拟内存页,处理器必须工作在要求的模式下。这意味着应用程序不能直接存取内核模式的虚拟内存,系统必须切换到内核模式才能存取内核模式的内存空间。

    应用程序内存调整
    3GB启动选项(Windows 2000的Advanced Server和DataCenter及后续Windows版本中可用)允许改变这两个地址空间部分的默认大小。它允许将进程的用户模式地址空间从2GB扩展到3GB,相应的代价是内核模式的地址空间从2GB减小到1GB。用Windows的说法,这个功能叫做应用程序内存调整,或者叫4GB调整(4GT)。你可以通过在BOOT.INI文件的[Operating Systems]部分添加/3GB开关启用应用应用程序内存调整。通常情况下,人们通过设置BOOT.INI文件的[Operating Systems]部分,将系统配置为可以使用3GB或者不使用3GB启动,以使在系统启动时可以进行选择。
    警告:你也可以在Windows 2000 Professional和Windows 2000 Server上使用/3GB开关,这样做的负面结果是,将内核模式的空间减小到了1GB,但并不会增加用户模式的空间。换句话说,你减小了内核模式的空间但并没有获得任何好处。
    注意:Windows XP和Windows Server 2003引入了一个新的启动选项/USERVA,和/3GB一起使用,比单独使用/3GB能够更好的控制。你在BOOT.INI中添加/3GB的时候可以同时添加/USERVA,/USERVA比单独使用/3GB的优点是它允许你指定一个准确的地址空间大小值供用户模式存取。例如,/USERVA=2560为用户模式配置2.5G的空间,剩余的1.5G用于内核模式。上面的警告信息在使用/USERVA选项时同样适用。

    声明了大地址存取的可执行程序
    在/3GB支持加入Windows之前,应用程序无法使用指针的最高位,用户模式的应用程序只能够对32位指针的前31位表示的地址空间进行存取。对于剩下的1位,一些聪明的开发者不希望浪费进程空间里的这1个位,把它用于了其它的目的,例如用于标识那些应用程序特定的地址分配类型的指针。这在引入/3GB后带来一个难题,因为这种类型的应用程序无法区分引用2GB以上内存的指针,和那些引用2GB以下内存但是最高位用于其它目的的指针。基本上,使用/3GB启动机器,会使这样的应用程序崩溃。为了解决这个问题,微软在Win32 PE文件格式(定义Windows下可执行文件Exe和Dll结构的格式)的Characteristics字段加入一个新标识位的支持,用于指示应用程序是否支持大的寻址能力。设置可执行文件头中Characteristics字段的第32位启用IMAGE_FILE_LARGE_ADDRESS_AWARE标识位。通过设置应用程序头的这个标识位,表明应用程序能够处理那些最高位被设置的指针,不会由于这个位带来任何多意性。当设置了这个标识位,在正确的Windows版本上使用/3GB选项启动,系统将为进程提供一个私有的扩展用户模式地址空间。你可以使用DumpBin、ImageCfg等可以分析可执行文件头的工具,查看应用程序是否启用了这个标识位。Visual C++通过/LARGEADDRESSAWARE连接开关提供对IMAGE_FILE_LARGE_ADDRESS_AWARE的支持。SQL Server启用了这个标识位,因此当你在正确的Windows版本上使用/3GB开关启动,系统将扩展SQL Server的用户模式地址空间。
    注意:Windows在进程启动时检查可执行文件的IMAGE_FILE_LARGE_ADDRESS_AWARE标识,忽略Dll的标识。对那些最高位被设置的指针,dll代码必须能够正确处理。

    物理地址扩展
    从Pentium Pro开始,Intel处理器提供一种叫做物理地址扩展(Physical Address Extension-PAE)的内存映射模式。PAE支持高达64GB的物理内存存取。PAE模式下,内存管理单元(Memory Management Unit - MMU)仍然实现了页目录条目(Page Directory Entries - PDEs)和页表条目(Page Table Entries - PTEs),但是在这个之上有一个新的层级:页目录指针表(Page Directory Pointer Table)。PAE模式下系统能够寻址更大的内存,因为PDEs和PTEs为64位宽,是之前标准宽度的两倍,而并不是通过PAE模式下的页目录指针表实现。页目录指针表把这些高存储容量的表和索引管理起来。使用PAE模式需要一个特殊版本的Windows内核,在Windows 2000及后续版本中均有提供,单处理器机器上位于Ntkrnlpa.exe中,多处理器机器上位于Ntkrnlpamp中。和/3GB、/USERVA一样,在BOOT.INI文件中添加/PAE启用PAE模式。

    地址窗口扩展
    Widnows中的地址窗口扩展(Address Windowing Extensiongs)功能允许应用程序存取超过4GB的物理内存。32位的指针是一个整型,只能够存储小于等于0xFFFFFFFF的值,因此只能够引用一个4GB的线性内存地址空间。AWE使应用程序可以突破这个限制,存取所有操作系统支持的内存。
    在概念上,AWE并不是一个新的东西,实际上,从计算机诞生开始,操作系统和应用程序就围绕指针限制开始使用类似的机制来处理。例如回到DOS时代,32 位扩展(象Phar Lap、Plinks及其它的一些)就普遍运用于16位应用程序,以存取正常地址空间之外的内存。用于扩展内存的特殊管理器、API非常普遍。也许你还记得象Quarterdeck QEMM-386这样的产品,在那个时代普遍的用于这类用途中。在这些允许指针存取超过本身表达范围的内存的机制中,具有代表性的方式,是在指针可直接存取的地址空间中提供一个窗口或者是区域,用于和指针无法直接存取的内存区域的转换。这正是AWE的工作原理:在进程地址空间中提供一个区域,或者说一个窗口,用于用户模式的代码无法直接存取的内存区域进行内存存取交换的中转站。
    为了使用AWE,应用程序必须:
    1. 使用Win32的AllocateUserPhisycalPages API函数分配扩展物理内存。该函数需要调用者具有将内存页锁定的权限。
    2. 使用VirtualAlloc API函数在进程的地址空间中创建一个区域,作为与扩展物理内存进行映射的一个窗口。
    3. 使用MapUserPhysicalPages或者MapUserPhysicalPagesScatter API函数,将扩展物理内存映射到这个虚拟内存窗口中。
    Windows 2000及后续版本支持AWE,尽管可以在低于2G物理内存的机器上使用AWE,但一般只是在2G或者超过2G内存的机器上使用,因为AWE是32位进程存取超过3GB内存的唯一方法。如果你在低于3GB物理内存的系统上,在SQL Server中启用AWE支持,系统会忽略这个选项并使用正常的虚拟内存管理方法。AWE内存一个比较有意思的特性是它不会使用磁盘,你将注意到AWE相关的API函数只对物理内存进行存取,这就是说AWE内存就是物理内存,不会与系统分页文件发生交换。
    用于AWE的虚拟内存窗口,需要具有读、写存取权限,因此当你设置这个虚拟窗口时,传给VirtualAlloc的保护属性只能是 PAGE_READWRITE。这也意味着你无法使用VirtualProtect保护这个区域中的内存页,来防止被修改或存取。
    注意:你常用的一些检测应用程序内存使用的工具,例如任务管理器、Perfmon/Sysmon等,都无法显示各个进程AWE内存的使用量。没有什么工具可以指示各个进程AWE内存的使用量,就是说没有什么工具可以报告给定进程的工作区中AWE内存的大小。

    /3GB与AWE
    在Windows的内存管理功能中,应用程序调整(/3GB)可以给私有进程增加50%的地址空间,使用方便,因此成为一种常用方法,但AWE功能更具有弹性和扩展性。前面提到,当你为私有进程地址空间增加1GB,这1GB来自内核模式的地址空间,内核模式地址空间也由2GB被压缩到1GB。对于内核模式的代码,完整2GB的工作空间已经显得狭窄,压缩这部分空间意味着某些内部核心结构也必须要压缩。这些结构中主要有机器上用于管理内存的表窗口(table Windows)。当你将内核模式部分压缩到1GB后,这个表最大就只能管理16GB的物理内存了。例如你在一台具有64GB物理内存的机器上运行Windows 2000 DataCenter,启动时使用了/3GB选项,你就只能够存取这台机器25%的内存,剩余的48BG将无法被操作系统和应用程序使用。AWE允许你访问超过3GB的内存,而通过/3GB,你仅仅为私有进程空间获得额外的1GB。Large Address Aware自动透明的使得这个额外空间对应用程序可用,但它被限制在1GB之内。理论上,AWE通过Win32 AWE API函数,使得所有对操作系统可用的物理内存对应用程序可用。尽管AWE更难于使用和存取,但它更具弹性和扩展。
    并不是说任何情况下AWE都比/3GB好,只是通常状况下是这样。比如说当你需要很多内存分配空间,而又不能放在AWE内存中(例如象线程栈 Thread Stacks、锁内存Lock Memory、存储过程计划Procedure Plans等),你也许会发现/3GB更合适。

    内存区域
    SQL Server将分配的内存组织成两个独立的区域:BPool和MemToLeave。实际上如果你使用AWE模式,还有另外一个区域:在Windows AWE支持下可以存取的3GB以上的扩展物理内存区域。
    BPool在这三个区域中是比较突出的一个,它是SQL Server主要的分配池,主要用于数据和索引页的缓存,也用于小于8K的内存分配。MemToLeave包含用户模式地址空间中BPool没有使用的那部分虚拟内存空间。3GB之上的AWE内存作为BPool的扩展,为数据和索引页缓存提供额外的空间。
    当你启动SQL Server的时候,SQL Server基于机器的物理内存和用户模式地址空间的大小计算BPool的上限。在计算出这个值后,MemToLeave区域被预留,这有利于防止随后的BPool预留造成内存碎片。接下来进行BPool预留,它可以分成多达32个独立预留块,用于满足在BPool预留时SQL Server进程中那些正在请求虚拟地址空间的dll及其它分配请求。在预留完BPool区域之后,MemToLeave区域被释放。MemToLeave 用于SQL Server内部超过8KB的连续空间分配请求,以及象OLEDB Provider、进程内COM对象等外部客户(指SQL Server主要引擎之外,驻留在SQL Server进程中的那些内存请求者)分配请求。
    因此,一旦SQL Server启动,BPool就被预留下来,但未被分配,MemToLeave基本就是进程的虚拟内存地址空间中的空闲部分。如果你在SQL Server启动之后查看SQL Server进程的Virtual Bytes Perfmon计数器,你将发现它反映的是BPool的预留值。我曾经看到人们因为这个数字经常很高而惊慌,毕竟,它通常是机器总的物理内存或者是最大 用户模式地址空间,减去MemToLeave区域的大小。这没什么担心的,因为它仅仅是预留但没有被分配的空间。之前提到过,预留的空间仅仅是一个地址空间,直到被分配时才会真正的和物理存储单元关联。在这之后,被分配到BPool中的内存将会增加,直到达到SQL Server启动时计算出的BPool上限值。

    监控SQL Server虚拟内存使用
    你可以通过SQL Server:Buffer Manager\Target Pages Perfmon计数器跟踪计算BPool最大值。SQL Server不同部分需要内存时,BPool从启动时被预留的区域中分配8KB大小的页,直到达到上限值,你可以通过SQL Server:Buffer Manager\Total Pages Perfmon计数器跟踪BPool中被分配的虚拟内存使用状况。另外你可以通过Private Bytes计数器跟踪SQL Server进程中所有被分配的虚拟内存的使用状况。
    因为SQL Server中绝大部分虚拟内存的使用都来自BPool,因此通常情况下,这两个计数器将一前一后的增加或平稳下来(记住,当启用AWE支持后, Private Bytes计数器不会反映SQL Server全部的内存使用)。如果Total Pages计数器平稳下来,而Private Bytes持续增加,这通常表明MemToLeave区域中连续的内存分配。这种内存分配可能比较常见,例如可能是SQL Server创建额外的工作线程时相关的内存分配,或者是进程内COM对象、扩展存储过程等外部请求者的内存泄漏等。如果由于内存泄漏或者内存使用过大,导致MemToLeave区域耗尽,使SQL Server进程用完了虚拟内存地址空间的内存(或者是MemToLeave区域中的最大空闲块低于0.5M的默认进程栈大小),就算是并没有达到使用sp_configure配置的最大工作进程数,SQL Server将无法再创建新的工作进程。这种情况下,如果SQL Server需要创建一个新的工作进程来执行请求,例如处理SQL Server新的连接请求等,那么这个请求将被延缓,直到服务器有足够的资源创建工作进程,或者是其它工作进程被释放出空间。这可能会导致用户无法连接到服务器,因为在从MemToLeave中获得足够的空闲空间,或者其它工作进程释放足够的资源之前,连接可能会超时。

    分配
    SQL Server中的内存请求者在初始化内存请求时,先创建一个内存对象管理当前的请求,当内存对象执行请求时,它调用SQL Server中相应的内存管理器从BPool或者是MemToLeave区域获取内存。请求小于8KB时,通常从BPool中获取内存;当请求8KB或者更大的连续空间时,通常从MemToLeave区域中获取。因为一个内存对象可能会产生多个分配请求,因此有可能会从MemToLeave区域中分配小于 8KB的分配请求。向SQL Server进程空间请求内存一般情况下都是内部请求者,就是说SQL Server的内部对象需要内存以执行某个任务,当然不是绝对的,象上面提到过的也有可能是外部请求者。通常,这些外部请求者使用Win32内存API函数分配和管理内存,因此是从MemToLeave区域中分配,因为(对于操作系统而言,译者注)SQL Server进程中只有MemToLeave区域可用(BPool区域被SQL Server预留,译者注)。但对于扩展存储过程是个特殊情况,扩展存储过程调用ODS的srv_alloc API函数实现,这使得它同SQL Server内部请求者被同等的对待,通常srv_alloc请求小于8KB的内存是从BPool中分配,大的内存分配则来自MemToLeave区域。
   
    内存管理器
    服务器运行时,内存管理器进行检查,以确保为服务器预留了一定数量的可用物理内存,使Windows和服务器上其它应用程序能够继续平稳的运行。这个数量从4M到10M左右(Windows Server 2003上接近10M),基于系统负载和BPool中内存页生命期得出。如果服务器上可用物理内存开始低于这个极限值,服务器释放BPool中的部分内存页,以收缩BPool的内存使用量(假设SQL Server的动态内存配置被启用)。内存管理器也确保任何时候保留了一定数量的空闲内存页,以使新的分配请求到达时,不必等待内存分配。这里的空闲,意思是指这些内存页被分配了,但是未使用。被分配但未被使用的BPool内存页通过一个空闲列表跟踪,当列表中的页被使用时,内存管理器从BPool的预留中分配更多的内存页,直到整个BPool预留被分配完。你将看到Process:Private Bytes Perfmon计数器由于这个行为而逐渐的增长(通常是线性增长)。
    系统中对应每一个CPU都有一个单独的空闲列表,当需要使用空闲页用于满足一个分配请求时,先检查和当前分配请求CPU相关的空闲列表,然后再检查系统中其它CPU相关的列表。这在多处理器系统上,有利于各个处理器更好的使用本地缓存,提高扩展性。你可以使用SQL Server:Buffer Partition Perfmon计数器监控特定的BPool分区,通过SQL Server:Buffer Manager\Free Pages Perfmon计数器监控所有分区的空闲列表。
    整个运行过程中,SQL Server内存管理器进程(可能运行在内存管理器线程或其它服务器线程中)监控系统内存状态,为系统其它应用程序保留合理数量的空闲物理内存,为新的内存分配请求预留一个安全数量的内存页。当在服务器上使用AWE时,其中的某些方面必须改变。在使用AWE时,BPool一开始就获取并锁定机器的物理内存,锁定的内存数量根据是否设置了maximum server memory确定。如果设置了,BPool尝试锁定由maximum server memory确定的数量;如果没有设置,BPool只留出大致128M,供其它进程使用,锁定机器上其余的全部物理内存。然后,BPool使用3GB之上的内存(AWE内存)作为数据和索引的分页文件(paging files),它将这些区域(3GB之上)的物理内存页映射到适当的虚拟内存地址空间中,使32位指针能够引用到。

    回顾
    这部分未翻译...
 
    作为一个丈夫和父亲的Ken Henderson,居住在德克萨斯州的达拉斯郊区。他是8本不同技术主题书籍的作者,包括最近发行的《The Guru's Guide to SQL Server Architecture and Internals》。Ken Henderson是达拉斯小牛队的球迷,业余时间喜欢看着他的孩子们玩闹,喜欢体育运动、园艺。

posted on 2006-09-01 16:18  riccc  阅读(1347)  评论(3编辑  收藏  举报

导航