再谈计算机内存访问之2:虚拟内存访问

转载 2013年06月15日 15:55:41

        转自:计算机内存访问,清华出版社

      每个进程都拥有自己的虚拟地址空间,那么怎样才能访问这个空间呢?这就需要用到Windows API函数。这些函数直接与编写程序相关,因而更受软件工程师的关注。有关这方面的函数较多,这里介绍几个重要的函数。


1 .获取系统信息

      在一个程序中不能直接应用某个系统的设备参数,否则将不利于程序的移植。因此,如果确实需要用到这样的设备参数,则需要一个系统信息函数来获得。VC++ 编译器所提供这样的函数为GetSystemInfo()。该函数需要一个指向SYSTEM_INFO结构的指针作为参数。其原型表示为:

void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);

其中lpSystemInfo返回LPSYSTEM_INFO结构的地址,用于装载适当的系统信息,这个结构体定义为:

typedef struct _SYSTEM_INFO
{ 
	union { 
		DWORD dwOemId; 
		struct { 
			WORD wProcessorArchitecture; 
			WORD wReserved; 
		}; 
	}; 
	DWORD dwPageSize; 
	LPVOID lpMinimumApplicationAddress; 
	LPVOID lpMaximumApplicationAddress; 
	DWORD_PTR dwActiveProcessorMask; 
	DWORD dwNumberOfProcessors; 
	DWORD dwProcessorType; 
	DWORD dwAllocationGranularity; 
	WORD wProcessorLevel; 
	WORD wProcessorRevision;
} SYSTEM_INFO;

其中参数含义如下所述。

dwOemId:是一个过时选项,用于与Windows NT 3.5以及以前的版本兼容。
wProcessorArchitecture:指明处理的结构,如Intel、Alpha、Intel 64位或Alpha 64位。
dwPageSize:用于显示CPU的页面大小。在x86 CPU上,这个值是4096字节。在Alpha CPU上,这个值是8192字节。在IA-64上,这个值是8192字节。
lpMinimumApplicationAddress:用于给出每个进程可用地址空间的最小内存地址。在Windows 98上,这个值是0x400000,因为每个进程的地址空间中下面的4MB是不能使用的。在Windows 2K/XP上,这个值是0x10000,因为每个进程的地址空间中开头的64KB总是空闲的。
lpMaximumApplicationAddress:用于给出每个进程可用地址空间的最大内存地址。在Windows 98上,这个地址是0x7FFFFFFF,因为共享内存映射文件区域和共享操作系统代码包含在上面的2GB分区中。在Windows XP上,这个地址是0x7FFEFFFF。
dwActiveProcessorMask:位屏蔽,指明哪个CPU是活动的。
dwNumberOfProcessors:计算机中CPU的数目。
dwProcessorType:处理器类型。
dwAllocationGranularity:保留的地址空间区域的分配粒度。
wProcessorLevel:进一步细分处理器的结构。
wProcessorRevision:用于进一步细分处理器的级别。
wReserved:保留供将来使用。

在以上参数中只有lpMinimumApplicationAddress、lpMaximumApplicationAddress、dwPageSize和dwAllocationGranularity与内存有关。


2. 在应用程序中使用虚拟内存

      对内存分配可以采用不同的方法,常用的方法有:用C/C++语言的内存分配函数,例如,用malloc() 和 free()、new 和 delete 函数分配和释放堆内存;用Windows传统的全局或者局部内存分配函数,如GlobalAlloc()和GlobalFree();用Win32的堆分配函数,如HeapAlloc()和HeapFree();用Win32的虚拟内存分配函数,如VirtualAlloc()和VirtualFree ()。注意,用不同的方法分配内存后,要用相对应的函数来释放所占用的内存。这里只介绍Win32的虚拟内存分配函数。

      在进程创建之初并被赋予地址空间时,其虚拟地址空间尚未分配,处于空闲状态。这时地址空间内的内存是不能使用的,必须通过VirtualAlloc()函数来分配其中的各个区域,对其进行保留。

VirtualAlloc()函数原型为:

LPVOID VirtualAlloc(
					LPVOID lpAddress, 
					DWORD dwSize, 
					DWORD flAllocationType,
					DWORD flProtect
					); 
      该函数用来分配一定范围的虚拟页。参数1指定起始地址;参数2指定分配内存的长度;参数3指定分配方式,取值MEM_COMMINT或者 MEM_RESERVE;参数4指定控制访问本次分配的内存的标识,取值为PAGE_READONLY、PAGE_READWRITE或者 PAGE_NOACCESS。
分配完成后,即在进程的虚拟地址空间中保留了一个区域,可以对此区域中的内存进行保护权限许可范围内的访问。当不再需要访问此地址空间区域时,应释放此区域,由VirtualFree()负责完成。其函数原型为:

BOOL VirtualFree(
				 LPVOID lpAddress,
				 DWORD dwSize,
				 DWORD dwFreeType
				 ); 
其中参数含义如下所述。
lpAddress:指向待释放页面区域的指针。如果参数dwFreeType指定了MEM_RELEASE,则lpAddress必须为页面区域保留由VirtualAlloc()所返回的基地址。
dwSize:指定了要释放的地址空间区域的大小,如果参数dwFreeType指定了MEM_RELEASE标志,则将dwSize设置为0,由系统计算在特定内存地址上的待释放区域的大小。
dwFreeType:为所执行的释放操作的类型,其可能的取值为MEM_RELEASE和MEM_DECOMMIT,其中MEM_RELEASE标志指明要释放指定的保留页面区域,MEM_DECOMMIT标志则对指定的占用页面区域进行占用的解除。

如果VirtualFree()执行完成,将回收全部范围的已分配页面,此后如再对这些已释 放页面区域内存进行访问将引发内存访问异常。释放后的页面区域可供系统继续分配 使用。


3.获取虚存状态

       Windows API函数GlobalMemoryStatus()可用于检索关于当前内存状态的动态信息。在软件的About对话框中,通常用这个函数来获取系统内存的使用情况。其函数原型为:

void GlobalMemoryStatus(LPMEMORYSTATUS lpmstMemStat);

其中lpmstMemStat返回MEMORYSTATUS结构的地址,这个结构体的定义为:

typedef struct MEMORYSTATUS{
	DWORD dwLength;
	DWORD dwMemoryLoad; 
	DWORD dwTotalPhys; 
	DWORD dwAvailPhys; 
	DWORD dwTotalPageFile; 
	DWORD dwAvailPageFile;
	DWORD dwTotalVirtual; 
	DWORD dwAvailVirtual; 
} MEMORYSTATUS ,* LPMEMORYSTATUS;
其中参数含义如下所述。
dwLength:MEMORYSTATUS结构大小。
dwMemoryLoad:已使用内存所占的百分比。
dwTotalPhys:物理存储器的总字节数。
dwAvailPhys:空闲物理存储器的字节数。
dwTotalPageFile:页文件包含的最大字节数。
dwAvailPageFile:用户模式分区中空闲内存大小。
dwTotalVirtual:用户模式分区大小。
dwAvailVirtual:表示当前进程中还剩下的自由区域的总和。
在调用GlobalMemoryStatus()之前,必须将dwLength成员初始化为用字节表示的结构的大小,即一个MEMORYSTATUS结构的大小。这个初始化操作使得Microsoft能够在新版本Windows系统中将新成员添加到这个结构中,而不会破坏现有的应用程序。当调用 GlobalMemoryStatus()时,它将对该结构的其余成员进行初始化并返回。
如果某个应用程序在内存大于4GB的计算机上运行,或者合计交换文件的大小大于4GB,那么可以使用新的GlobalMemoryStatusEx()函数。其函数的原型为:

BOOL GlobalMemoryStatusEx(MEMORYSTATUSEX &mst);

      其中mst返回MEMORYSTATUSEX结构的填充信息,该结构体与原先的MEMORYSTATUS结构基本相同,差别在于新结构的所有成员的大小都是64位宽,因此它的值可以大于4 GB。


4. 确定虚拟地址空间的状态

      对内存的管理除了对当前内存的使用状态信息进行获取外,还经常需要获取有关进程的虚拟地址空间的状态信息。例如,如何得到一个进程已提交的页面范围?这就要用到两个 API函数VirtualQuery()或VirtualQueryEx()来进行查询。这两个函数的功能相似,不同就是VirtualQuery()只是查询本进程内存空间信息,而VirtualQueryEx()可以查询指定进程的内存空间信息。

VirtualQuery()函数原型如下:

DWORD VirtualQuery(
				   LPVOID lpAddress, 
				   PMEMORY_BASIC_INFORMATION lpBuffer, 
				   DWORD dwLength
				   );
VirtualQueryEx()函数原型如下:
DWORD VirtualQueryEx(
					 HANDLE hProcess , 
					 LPCVOID lpAddress , 
					 PMEMORY_BASIC_INFORMATION lpBuffer , 
					 DWORD dwLength 
					 ); 
其中参数含义如下所述。
hProcess:进程的句柄。
lpAddress:想要了解其信息的虚存地址。
lpBuffer:返回MEMORY_ BASIC_INFORMATION结构的地址。
dwLength:返回的字节数。
PWEMORY_BASIC_INFORMATION的定义如下:

typedef struct _MEMORY_BASIC_INFORMATION{
	PVOID BaseAddress; 
	PVOID AllocationBase;
	DWORD AllocationProtect;
	DWORD RegionSize;
	DWORD State;
	DWORD Protect;
	DWORD Type;
} MEMORY_BASIC_INFORMATION, * PMEMORY_BASIC_INFORMATION;
其中参数含义如下所述。

BaseAddress:被查询内存块的基地址。

AllocationBase:用VirtualAlloc()分配该内存时实际分配的基地址。

AllocationProtect:分配该页面时,页面的一些属性,如PAGE_READWRITE、PAGE_EXECUTE等(其他属性可参考 Platform SDK)。

RegionSize:从BaseAddress开始,具有相同属性的页面的大小。

State:页面的状态,有3种可能值:MEM_COMMIT、MEM_FREE和MEM_ RESERVE,这个参数是最重要的,从中可知指定内存页面的状态。

Protect:页面的属性,它可能的取值与 AllocationProtect 相同。

Type:指明了该内存块的类型,有3种可能值:MEM_IMAGE、MEM_MAPPED和MEM_PRIVATE。


5.改变内存页面保护属性

      在进行进程挂钩时,经常要向内存页中写入部分代码,这就需要改变内存页的保护属性。有幸的是Win32提供了两个API函数VirtualProtect ()和VirtualProtectEx(),它们可以对改变内存页保护。例如,在使用这两个函数时,可以先按PAGE_READWRITE属性来提交一个页的地址,并且立即将数据填写到该页中,然后再把该页的属性改变为PAGE_READONLY,这样可以有效地保护数据不被该进程中的任何其他线程重写。在调用这两个函数之前最好先了解有关页面的信息,可以通过VirtualQuery()来实现。

VirtualProtect()与VirtualProtectEx()函数的区别在于VirtualProtect()只适用于本进程,而VirtualProtectEx()可以适用于其他进程。

VirtualProtect()函数原型如下:

BOOL VirtualProtect(
					PVOID pvAddress,
					DWORD dwSize,
					DWORD flNewProtect,
					PDWORD pflOldProtect
					);
VirtualProtectEx()函数原型如下:
BOOL VirtualProtectEx(
					  HANDLE hProcess,
					  PVOID pvAddress,
					  DWORD dwSize,
					  DWORD flNewProtect,
					  PDWORD pflOldProtect
					  );
其中参数的含义如下所述。
hProcess:要修改内存的进程句柄。
pvAddress:指向内存的基地址(它必须位于进程的用户方式分区中)。
dwSize:用于指明想要改变保护属性的字节数。
flNewProtect:代表PAGE_*保护属性标志中的任何一个标志,但PAGE_ WRITECOPY和PAGE_EXECUTE_WRITECOPY这两个标志除外。

pflOldProtect:是DWORD大小的地址,VirtualProtect()和VirtualProtectEx()将用原先与pvAddress位置上的字节相关的保护属性填入该地址。尽管许多应用程序并不需要该信息,但是必须为该参数传递一个有效地址,否则该函数的运行将会失败。


6. 进行一个进程的内存读写

      前面已经说明了如何获得一个进程的内存属性、如何分配内存和如何改变内存页的保护属性,其最终的目的是要对一个进程中内存内容进行读写。要完成此工作,需要用到两个函数:ReadProcessMemory() 和WriteProcessMemory(),这两个函数非常有用。如果知道了一个进程的句柄和内存地址,就可以用ReadProcessMemory ()函数来得到该进程和该地址中的内容,此函数的原型为:

BOOL ReadProcessMemory(
					   HANDLE hProcess,
					   LPCVOID lpBaseAddress,
					   LPVOID lpBuffer,
					   DWORD nSize, 
					   LPDWORD lpNumberOfBytesRead
					   );
      其中hProcess为要读入的进程句柄,lpBaseAddress为读内存的起始地址,lpBuffer为读入数据的地址,nSize为要读入的字节数,lpNumberOfBytesRead为实际读入的字 节数。

      同样,如果知道了一个进程的句柄和内存地址,可以用WriteProcessMemory()函数向该进程和该地址中写入新的内容,这个函数的原型为:

BOOL WriteProcessMemory(
						HANDLE hProcess, 
						LPVOID lpBaseAddress,
						LPVOID lpBuffer,
						DWORD nSize,
						LPDWORD lpNumberOfBytesWritten
						);
      其中参数hProcess为要写入的进程句柄,lpBaseAddress为写内存的起始地址,lpBuffer为写入数据的地址,nSize为要写入的字节数,lpNumberOfBytesWritten为实际写入的字节数。

windows 核心编程之在应用程序中使用虚拟内存

Microsoft Windows 提供了以下三种机制来对内存进行操控: 虚拟内存 最适合用来管理大型对象数组或大型结构数组 内存映射文件 最适合用来管理大型数据流(通常是文件),以及在同一台机...

再谈计算机内存访问

  • 2013年08月05日 15:16
  • 299KB
  • 下载

再谈计算机内存访问之1:内存管理概述

1. 引言       要成为一个程序编写高手,不仅需要熟悉各种计算机语言,而且还需要懂得计算机是如何工作的。虽然不必对计算机的各个部件了解得十分清楚,但至少需要懂得计算机操作系统对内存是如何管理的。...

用虚拟内存来访问大文件

这段时间在论坛上看见一些人问起如何读取很大的文件或者数据结构,我一时也想不出什么办法,只有建议更换策略,不要为难内存。   在32位机上(64位也是一样的,但是空间大很多),一个进程可以分配到4G...
  • zhubosa
  • zhubosa
  • 2012年08月08日 11:21
  • 700

利用MMU通过访问虚拟内存来实现流水灯

实验目的:               利用MMU通过访问虚拟内存来实现流水灯 预备知识:            (1):熟悉协处理器的作用,CP15中的十六个寄存器,以及寄存器中的位格式和含义...

计算机底层知识拾遗(一)理解虚拟内存机制

这个系列会总结计算机,网络相关的一些重要的底层原理。很多底层原理大家上学的时候都学过,但是在学校的时候大部分的同学都是为了应付考试而学习,过几天全忘了。随着工作的时间越久,越体会到这些基础知识的重要性...
  • ITer_ZC
  • ITer_ZC
  • 2015年01月12日 16:33
  • 7863

计算机底层知识拾遗(一)理解虚拟内存机制

计算机底层知识拾遗(一)理解虚拟内存机制 http://blog.csdn.net/iter_zc/article/details/42644229 这个系列会总结计算机,网络相关...

操作系统精髓与设计原理--虚拟内存(2)

8.2操作系统软件 操作系统的内存管理设计取决于三个基本方面的选择: (1)是否使用虚存技术 (2)使用分页还是分段,或者是两者的组合 (3)为各种存储管理特征采用的算法 前两项的选择取决于...

2-6案列分析: memdev虚拟内存设备驱动

2-6案列分析: memdev虚拟内存设备驱动1、memdev虚拟内存字符设备:在驱动中分配一片指定大小的内存空间,作为虚拟字符设备。并在驱动中提供只对该片内存的读写、控制和定位函数seek,以供用户...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:再谈计算机内存访问之2:虚拟内存访问
举报原因:
原因补充:

(最多只允许输入30个字)