1. 概述
INtime使用物理内存加载并运行INtime内核及其相关应用程序。INtime内核为各进程提供物理内存分配服务,也为各进程提供虚拟内存服务机制,通过虚拟内存使进程能够访问分配给它们的物理内存。
当INtime内核启动时,它需要接管的分配给INtime的物理内存池。在INtime和Windows共享(Shared)内存机制中,INtime接管的内存必须来自于Windows非分页内存池。当Windows引导时,或者在某些情况下,INtime内存可由Windows引导加载程序预先分配。INtime获取的内存首先用于加载INtime实时内核,其余部分作为INtime根进程的内存池进行管理。在内核初始化后完成后,INtime创建的所有其它应用程序进程都会从根进程中获取内存的初始分配,这些被分配的内存将成为新进程的初始内存池。
2. 物理内存
2.1 物理地址扩展
INtime是32位操作系统,因此内核提供32-bit的物理地址空间,即最大提供4GB的可访问物理内存范围。但是从INtime 6.2开始,通过CPU扩展物理内存机制的使用,INtime能够访问所有安装在计算机中的物理内存。这个功能是通过CPU分页系统的PAE(“物理地址扩展”)机制实现的,即通过配置可将一个或多个存储器区域分配给每个INtime节点。
物理地址扩展PAE技术首先需要CPU支持,也就是指64位CPU(现在市面上基本都是这种类型)允许32位的操作系统识别到64GB(236)的物理内存。CPU的这项技术主要是通过页表寄存器来实现的。PAE技术通过主板芯片组(地址总线)、操作系统(段页式内存管理)提供支持。
对于32位操作系统,经过段式地址转换后仍为32位线性地址,需要经过页式地址转换后,才能将32位的线性地址映射到最大64GB的物理内存上。
对于32位操作系统,每个应用程序(进程)自己的虚拟地址空间仍然为最大4GB。
2.2 系统内存映射
下图说明了一个典型的PC系统的内存映射。某些地址范围(橙色)保留给I/O和固件,以及其他方面可以访问的RAM部分。
PC架构的顶部内存地址空间和能对已安装的物理内存重叠映射,使更多的已安装内存得到分配和应用。这意味着顶部内存地址实际上比安装的总内存略高一些。例如,在安装了8GB内存的典型PC上,“top”或最高物理ram地址通常为8.5GB。这一点之所以重要,是因为在计算内存分配大小时,以及内存在4GB边界以上和以下具有不同的用途。具体可网上搜索相关的4GB内存的重叠与局限内容。
没有扩展的物理内存功能,只能生成32位物理地址,将物理内存限制在4GB以下。在扩展物理内存功能的情况下,INtime应用程序仍然是32位应用程序,并且只寻址32位线性范围的地址。然而,CPU的分页硬件机制可以为应用在CPU的最大物理地址范围内寻址。这意味着应用程序运行的内存不必局限于4GB以下物理空间。在XM过程中(参见下面的定义)可以一次将高达3.75GB的内存映射到应用程序。这样,每个进程都可以访问其自己的3.75GB。
2.3 应用兼容性
除一些特例之外,大多数现有应用程序不需要修改即可使用扩展物理内存,因为这些应用程序不需要知道物理内存的实际位置。
虽然取消了32位物理内存地址限制,仍有一些应用程序必须使用4GB以下的内存,例如使用硬件的应用程序本身只能为DMA生成32位地址。一些低于4GB的内存将需要为该硬件提供可访问的缓冲区,例如较老的10/100 Mbit以太网接口中和大多数USB控制器。在对这些硬件设备进行编程时,需要使用一些新的系统调用来操作4GB以上的物理内存访问:例如,需参见 MapRtPhysicalMemory64和GetRtPhysicalAddress64函数。
2.4 INtime节点内存配置
利用扩展的物理内存特性,将内存分配给多个物理区域中的每个节点。这些区域在Windows启动阶段作为连续的非分页内存分配,并根据系统配置分配给每个节点。第一个区域(“内核区域”)是固定的,用于加载内核和分配任何固定的系统内存资源。第二个和后续区域(“应用程序区域”)由内核的内存管理器管理,供应用程序使用。每个应用程序区域都有一个可配置的大小,可以从低于或高于4GB边界的内存中分配,也可以将分配留给Windows。内核区域留下的任何内存都由内核管理,并在应用程序区域之后添加到区域列表中。
新节点的默认分配是内存不足4GB的单个32MB区域。由于兼容性原因,从版本6.2之前的INtime版本导入到INtime当前版本的任何以前的内存配置都会创建一个内核区域,然后再创建一个等于先前配置的内核区域大小的第二个区域。
在分配内存时,按照内存管理器的指定分配顺序搜索。此外,应用程序可以指定内存管理器分配内存的来源。扩展的系统调用(AllocateRtMemoryEx)具有参数,用于设置分配内存的物理地址范围的下限和上限。例如,要确保从4GB以下分配内存,可在调用中设置0x100000000L上限:
DWORD memsize = (3 * 1024 * 1024);
LPVOID p = AllocateRtMemoryEx(memsize, 0L, 0x100000000L, 0);
这确保从0到4GB之间分配内存。
为节点配置多个内存区域的原因包括:
在第二种情况下,如果应用程序需从Windows非分页池分配的内存相对较多,则Windows可能无法分配所需的内存。如果有大量内存添加到系统中,则可以使用4GB以上的内存,或者将分配其分成更小的部分,例如,四个512 kB的区域,而不是一个2GB的区域。这样更容易配置成功。这两种配置之间的操作差异是,单次分配内存的最大值将减少到512 kB,没有其他不同。
对于单个映射,Windows的限制为4GB,如果从非分页池中分配,则将单个区域的大小限制为4GB。如果需要更大的区域,请为该区域使用“非Windows内存(Exclude memory from Windows)”属性。
2.5 非托管内存区域
另一个配置选项创建内存区域,不将其分配给内核进行管理,而是允许应用程序直接管理它。例如,如果需要非常大的内存来进行数据缓冲,那么可以直接从应用程序映射该内存并直接对其进行管理,并通过 GetRtSystemMemoryInfo系统调用发现该内存区域。非托管区域将有一个标志值,表示非托管状态,并给出该区域的物理地址和大小。
2.6 非Windows内存
Windows引导加载程序可以配置为不是所有的系统RAM都由Windows管理。这些非Windows内存通过从RAM顶部开始分配。INtime可以配置使用此非Windows内存区。如果在从Windows非分页池分配足够的内存时遇到困难,或者需要非常大的区域(4GB以上),则可使用非Windows管理内存。
在引入扩展物理内存功能之前,INtime总是配置内存上限4GB以下。因为INtime最多只能寻址4GB。它还被配置成所有INtime的内存必须从非分页池的窗口中分配,或者所有INtime的内存在非Windows内存区域之外分配。在64位Windows上启用新内存机制后,内存上限可设置在4GB以上,可根据需要决定是否使用非Windows内存或非分页内存池,允许应用更大的灵活性和实用性。
3. 虚拟内存
3.1 内存映射
INtime进程不能直接访问物理内存,每个进程都拥有一个内存空间,从零开始并延伸到一定上限的内存地址范围。这些内存地址称为虚拟内存,进程可用的地址范围被组织在一个称为虚拟内存段(VSEG)的系统对象中。VSEG最初是空的,物理内存被映射到VSEG,或者通过进程创建时的加载程序,或者通过分配例程。映射物理存储器的过程从VSEG分配一个地址范围,并通过该地址范围使物理内存可见。返回给调用方的虚拟地址用于访问物理内存。如果进程试图映射或分配内存,而VSEG中没有足够的空间来完成操作,则系统将返回状态E_VMEM。
并非VSEG中的每个地址都具有映射到它的有效物理内存,如果尝试访问无效的地址,则生成页故障异常。地址0到4095空间总是被系统保留,任何尝试引用无效区域的指针都会生成页错误异常。
进程的VSEG大小由多个因素决定,默认设置为8MB。当需要VSEG足够大,以覆盖加载到应用程序中的代码和数据区域。如果进程的内存池最大值设置为不同于默认值的值,则加载器将增加VSEG的大小以匹配-假定分配给进程的所有物理内存都可以立即访问。给定进程的默认VSEG大小也可以由链接器配置,并且可以使用/vseg命令行参数由加载程序重置它。也可以在CreateRtProcess (ntxCreateRtProcess)系统调用的PROCESSATTRIBUTES结构中的VsegSize字段传递给调用重置。
INtime系统的最初设计(在版本6之前)是这样的,即内核为整个内核和加载到它上的所有应用程序管理总共4GB的虚拟内存。因此,所有VSEG的总大小不能超过4GB。此外,内核也使用一些虚拟内存空间来管理物理内存池,从而减少了应用程序可用的总虚拟内存。在INtime 6中引入了一个名为XM进程的新特性,以克服这些限制。
3.2 虚拟内存映射
虚拟内存以页的单位(4096字节)组织。高级组织单元是页面目录,在PAE模式下为2MB,非PAE模式为4MB。虚拟内存的分配将首先尝试指向到页目录区域;如果页目录的剩余空间不适合分配需求,则将在空页目录区域直接分配。不从页面目录开头开始的分配将在分配之前获得一个额外的虚拟内存页,作为“保护页”,这是分配之间的一个空页。
在某些情况下,上述操作将导致某些分配非最优。例如,在启用PAE(扩展物理内存)配置中,一系列的1MB分配将“浪费”大约一半的可用虚拟内存空间。这是因为第一次分配将从2 MB页目录区域的开始,但是第二次分配将不适合,因为有额外的保护页,因此它将在下面的页面目录的起始重新分配。相反,由于页目录中的第一个分配没有添加保护页,所以2MB分配将适合于页目录,因此一系列2MB分配将在虚拟内存分配方案中提供最佳匹配。
3.3 物理内存映射
在许多系统中,外接板卡包含INtime的RT线程需要访问的内存空间。使用MapRtPhysicalMemory (或 MapRtPhysicalMemoryEx/MapRtPhysicalMemory64)访问此空间连续、边界对齐、粒度固定的内存空间。如果INtime中的其他进程需要访问此内存,则可以通过CreateRtMemoryHandle将地址转换为句柄,并将其映射为其他共享内存句柄,如下所述,否则第二个进程也可以调用MapRtPhysicalMemory。一旦不再需要映射,就使用FreeRtMemory删除映射并释放相关的虚拟内存。
默认情况下,这种I/O卡上的内存不被CPU存储器控制器缓存,而是使用MapRtPhysicalMemoryEx/MapRtPhysicalMemory64系统调用通过额外标志来进一步控制映射内存的缓存属性。可以通过调用GetRtVirtualMemoryInfo来查找有关进程VSEG的使用信息。
3.4 内存XM扩展模式
XM(“扩展内存”)进程是在INtime6中引入的一类新的进程。XM进程有自己独立的4GB虚拟内存空间,不使用从系统虚拟内存空间分配的VSEG。XM进程虚拟内存范围的前256 MB保留给系统使用,剩余的3.75GB可由进程使用。
当将INtime for Windows内核配置为以共享模式加载时,XM进程不可使用。在共享模式系统上,默认情况下所有进程都以非XM模式加载。
XM进程有两个主要优点,即对映射内存的进程可用的虚拟内存空间没有实际限制,且英特尔Atom家族的处理器也有性能上的优势。
在使用XM进程时也有几个小不足。由于XM进程有自己独立的虚拟地址空间,一些系统调用会导致进程和系统地址空间之间频繁切换。对于XM进程中的线程和另一个进程中的线程(包括默认内存空间中的进程)之间的线程切换,也是如此。在当前运行XM进程时,在处理中断时也会增加少量额外开销,这同样是因为需要在不同的地址空间之间切换。每一种情况下的开销都很小,但在负载很重的系统中可能很需要注意。
当使用/XM开关时,或者如果XM标志已在INtime项目已设置,则加载程序将创建XM进程。默认情况下,在版本6之前在INtime版本中创建的应用程序将作为非XM进程加载。
右图显示了内核和传统应用程序仍然使用的原始系统内存空间,以及为XM进程创建的新内存空间。每个XM进程都有自己的4GB虚拟内存空间。第一内存空间称为默认内存空间,或称系统内存空间。
从INtime6.2开始,不再需要用于“Memory mgmt”的区域。
4. 进程内存管理
4.1 物理内存
每个INtime进程都有一个物理内存池,以及与池关联的两个参数。
内存池最小值是进程创建时最初分配给进程的内存量。加载过程时,加载程序确保内存池最小值足以加载和启动应用程序。默认设置是允许加载程序计算它。
每个进程也有一个分配给它的内存池最大值。这是进程可以分配给自己的最大物理内存量。当进程分配内存时,该内存首先来自分配给该进程的最小值内存池。如果所有内存都用完了,则进程从父进程借用内存,直到达到内存池最大值为止。之后,试图分配更多的物理内存将导致E_MEM错误状态。此参数的默认值是允许进程尽可能不受限制地从其父进程借用内存。
此图显示了两个进程,其内存池已从根进程的内存池中分配。
当线程使用INtime内核系统调用创建对象时,与这些对象关联的内存来自调用线程进程的内存池。类似地,线程通过使用INtime系统调用将内存分配到和重新分配到它们自己的虚拟地址空间,从而满足它们的动态内存需求(请参阅上面的虚拟内存部分)。
在应用程序的VisualStudio项目设置中,可以重置内存池最小值和池最大值。这些值可以在INtime项目中设置,也可以在加载时被重置。对ldrta.exe命令使用/pmin和/pmax参数,或者更改CreateRtProcess调用的PROCESSATTRIBUTES结构字段(或ntxCreateRtProcess)。
应用程序可以使用GetRtPhysicalMemoryInfo系统调用读取内存池的基本统计信息,该系统调用返回有关内存池最小值、最大值、进程池大小、分配给进程的内存数量和可用剩余内存的信息。
4.2 内存分配
进程池中的内存当且仅当进程中的线程提出分配请求才被分配,否则一直不可使用。调用AllocateRtMemory分配内存时,内存请求是显式的,创建系统对象时内存分配是隐式的。其他内存管理接口(如c中的malloc或c++中的new函数)最终调用AllocateRtMemory来分配物理内存。
系统对象具有各种大小,具体取决于类型,边界对齐(16-byte)。使用AllocatRtMemory的内存分配始终是系统页面的倍数(4096字节),并且始终在页面边界上对齐。分配存储器(例如C库malloc函数)的其它系统调用最终导致对AllocateRtMemory的内部调用。
扩展调用AllocateRtMemoryEx允许对分配的物理内存(如上、下边界和物理地址对齐)施加限制。
4.3 内存借用
当尝试创建系统对象或分配内存时,进程池的未分配部分太小,无法满足请求时,INtime内核将尝试从当前进程的父进程借用更多内存,最多达到池的指定最大值。此图显示借用内存:
当进程c被删除时,其池中的内存将被取消分配,并返回到父进程的池中。
借用增加了借用进程的内存池大小,并被限制在该进程允许的最大范围内。如果进程具有相同的最小和最大池属性,则其内存池固定在该值,并且进程不能从父进程借用内存。
调用 GetRtPhysicalMemoryInfo来获知一个进程分配了多少内存和借用了多少内存,这取代了一个以前旧版本的调用GetRtProcessPoolInfo,它仅限于32位物理内存字段,可能无法返回信息,特别是根进程的信息。
4.4 共享内存与句柄
线程可以通过为该内存创建共享RT内存句柄,将其内存的连续部分与另一个进程中的线程共享。然后,句柄可以通过邮箱(例如)传递到另一个进程中的线程,或者通过在对象目录中对其进行编目。同一进程中的线程已经共享相同的地址空间,因此可以通过全局变量将内存分配指针公开给对方。
使用CreateRtMemoryHandle创建共享RT内存句柄。在所有外部进程不再需要访问RT句柄定义的共享内存之后,请使用DeleteRtMemoryHandle删除RT句柄,从而将分配的内存限制为本地使用。
4.5 内存管理函数
下图显示了进行内存管理系统调用的顺序,并列出了内存操作经常使用的函数调用。
(1) 从需要额外动态内存的线程进行此调用。
(2) 从需要访问本地内存或物理内存的线程调用不同进程中的线程。
(3) 从需要访问分配给其他进程的内存或物理内存的线程进行这些调用。
(4) 从创建共享内存句柄的线程中进行这些调用。
(5) 从分配内存到其虚拟地址空间的线程进行此调用。
欢迎访问 http://www.synwell.com.cn