windows核心编程13-16 18

来源:互联网 发布:平安证券行情软件下载 编辑:程序博客网 时间:2024/05/22 10:33

用户模式分区:

进程无法通过指针来读取、写入或以任何方式,访问驻留在这一分区中其他进程的数据。对所有应用程序来说,进程的大部分数据都保存在这一分区。由于每个进程都有自己的数据分区,因此一个应用程序破坏另一个应用程序的可能性就非常小,从而使得整个系统更加坚固。

在Windows中,所有.exe和运态链接库都载入到这一区域。每个进程都有可能将这些DLL载入到这一分区内的不同地址(虽然这种可能性很小)。系统同时会把该进程可以访问的所有内存映射文件映射到这一分区。

 

当应用程序调用VirtualAlloc函数来把物理存储器调拨给地址空间区域时,该空间实际上是从硬盘上的页交换文件分配得到的。

 

当一个线程试图访问所属进程的地址空间中的一块数据时,有可能会出现两种情况:

第一种情况是,线程要访问的数据就在内存中。在这种情况下,CPU会先把数据的虚拟内存地址映射到内存的物理地址,接下来就可以访问内存中的数据了。

第二种情况是,线程要访问的数据不在内存中,而是位于页交换文件中的某处。在这种情况下,这次不成功的访问被称为页面错误。然后。。。。。。

 

当用户要求执行一个应用程序时,系统会打开该应用程序对应的.exe文件并计算出应用程序的代码和数据的大小。然后系统会预订一块地址空间,并注明与该区域相关联的物理存储器就是.exe文件本身。是的,系统并没有从页交换文件中分配空间,而是将.exe文件的实际内容(或文件映像,即file image)用作程序预订的地址空间区域。这样一来,不但载入程序非常快,而且页交换文件也可以保持一个合理的大小。

 

当把一个程序位于硬盘上的文件映像(即一个.exe或DLL文件)用作地址空间区域对应的物理存储器时,我们称这个文件映像为内存映射文件(memory mapped file)。当载入一个.exe或DLL时,系统会自动预订地址空间区域并把文件映像映射到该区域。但是,系统也提供了一组函数,可以让开发人员把数据文件映射到地址空间。

 

当Windows从软盘载入.exe或DLL文件时,系统会把整个文件从软盘复制到内存中。此外,系统还会从页交换文件中分配足够的存储空间来存放文件映像。只有当系统需要把一个页面换出内存,而页面又包含该文件映像的一部分时,系统才会写入页交换文件。如果系统的内存负载很轻,那么文件总是从内存中直接运行。

 

我们可以给每个已分配的物理存储页指定不同的页面保护属性。

 

为了节省磁盘空间,链接器会尽可能地对所生成的PE文件进行压缩。但是,当Windows将PE文件映射到进程的虚拟地址空间时,每一段必须另起一页,而且起始地址必须是系统页面大小的整数倍。这意味着PE文件所需虚拟地址空间的大小一般来说要大于文件本身的大小。

 

操作系统中有许多值是由系统所运行的主机决定的,如页面大小和分配粒度等。我们绝对不应该在代码中将这些值写死(将这些值硬编码到代码中),而是应该在进程初始化时取得这些值,然后在代码中使用它们。GetSystemInfo函数用来取得与主机相关的值。

 

GlobalMemoryStatusEx

 

Windows提供了以下三种机制来对内存进行操控。

虚拟内存:最适合用来管理大型对象数组或大型结构数组。

内存映射文件:最适合用来管理大型数据流(通常是文件),以及在同一机器上运行的多个进程之间共享数据。

堆:最适合用来管理大量的小型对象。

 

PVOID VirtualAlloc(PVOID pvAddress, SIZE_TdwSize, DWORD fdwAllocationType, DWORD fdwProtext);

 

Windows认为用MEM_LARGE_PAGE标志分配得到的内存是不可换页的(unpagable):也就是说必须驻留在内存中。这也是为什么以这种方式分配得到的内存能提供更好的性能。但由于内在是稀缺资源,用MEM_LARGE_PAGE标志调用VirtualAlloc需要调用方具有内存中锁定页面的用户权限。

 

与虚拟内存和内存映射文件相比,堆是用来管理链表和树的最佳方式。堆的优点是它能让我们专心解决手头上的问题,而不必理会分配粒度和页面边界这类的事情。堆的缺点是分配和释放内存块的速度比其他方式慢,而且也无法再对物理存储器的调拨和撤销进行直接控制。

 

在系统内部,堆就是一块预订的地址空间区域。刚开始,区域内的大部分页面都没有调拨物理存储器。随着我们不断地从堆中分配内存,堆管理器会给堆调拨越来越多的物理存储器。这些物理存储器始终是从页交换文件中分配的。释放堆中的内存块时,堆管理器会撤销已调拨的物理存储器。

 

进程初始化的时候,系统会在进程的地址空间中创建一个堆。这个堆被称为进程的默认堆。在默认的情况下,这个堆的地址空间区域的大小是1MB。但是,系统可以增大进程的默认堆,使它大于1MB。我们也可以在创建应用程序的时候用/HEAP链接器开关来改变默认的区域大小。由于动态链接库(DLL)没有与之关联的堆,因此在创建DLL的时候不应该使用/HEAP开关。

 

我们可以通过调用GetProcessHeap来得到进程的默认堆的句柄:

HANDLE GetProcessHeap();

 

如果始终从堆中分配同样大小的对象,创建多个堆就可以对它们进行更加有效的管理。

 

快速释放。把一些数据结构存入在一个专门的堆中还有另一个好处,即我们可以直接释放整个堆而不必显示地释放堆中的每个内存块。

 

HANDLE HeapCreate(DWORD fdwOptions, SIZE_TdwInitialSize, SIZE_T dwMaximumSize);

如果想在堆中存放可执行代码,则必须使用标志HEAP_CREATE_ENABLE_EXECUTE。这对“数据执行保护”这项特性来说特别重要。如果不设置这个标志,那么当我们试图在来自堆的内存块中执行代码时,系统会抛出EXCEPTION_ACCESS_VIOLATION异常。

 

HeapCreate的第2个参数表示一开始要调拨给堆的字节数。如果需要,HeapCreate会把这个值向上取整到CPU页面大小的整数倍。最后一个参数dwMaximumSize表示堆所能增长到的最大大小。如果dwMaximumSize为0,那么创建的堆将是可增长的,它没有一个指定上限,从堆中分配内存会使堆不断增长,直到用尽所有的物理存储器为止。

 

PVOID HeapAlloc(HANDLE hHeap, DWORDfdwFlags, SIZE_T dwBytes);

第1个参数hHeap是一个堆的句柄,表示要从哪个堆中分配内存。参数dwBytes表示要从堆中分配多少个字节。中间的参数fdwFlags用来指定一些标志,这些标志会对分配产生影响。目前,系统只支持3个标志:HEAP_ZERO_MEMORY, HEAP_GENERATE_EXCEPTIONS和HEAP_NO_SERIALIZE。

 

PVOID HeapReAlloc(HANDLE hHeap, DWORDfdwFlags, PVOID pvMem, SIZE_T dwBytes);

如果一个内存块是链表或树的一部分,则需要指定HEAP_REALLOC_IN_PLACE_ONLY标志。在这种情况下,链表或树中的其他节点可能包含指向当前节点的指针,把节点移动到堆中其他的地方会破坏链表或树的完整性。

 

分配一块内存后,可以调用HeapSize函数来得到这块内存的实际大小。

SIZE_T HeapSize(HANDLE hHeap, DWORDfdwFlags, LPCVOID pvMem);

 

BOOL HeapFree(HANDLE hHeap, fdwFlags, PVOIDpvMem);

 

BOOL HeapDestroy(HANDLE hHeap);调用HeapDestroy会释放堆中包含的所有内存块,同时系统会收回堆所占用的物理存储器和地址空间区域。

 

 

CSomeClass * pSomeClass = new CSomeClass();

C++编译器编译这一行代码的时候,它会首先检查CSomeClass是否包含一个重载的new操作符的成员函数。如果找到,编译器将生成代码来调用这个成员函数。如果编译器无法找到哪个函数重载了new操作,编译器将生成代码来调用C++的标准new操作符。

通过对C++类的new和delete操作符进行重载,我们可以非常容易地将堆函数加以运用。

 

 

 


0 0
原创粉丝点击