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

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

      每个进程都拥有自己的虚拟地址空间,那么怎样才能访问这个空间呢?这就需要用到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为实际写入的字节数。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内存管理是指操作系统对计算机内存资源进行有效利用和管理的过程。在内存管理中,存在着物理地址、虚拟地址、逻辑地址和UNC(统一编号字符集)等概念。 物理地址是指计算机内存中的实际物理位置,它是由计算机硬件直接管理的,用来标志内存中存储单元的位置。通过物理地址,操作系统可以直接访问内存中的数据。 虚拟地址是在逻辑地址和物理地址之间的中间层,它是由操作系统通过地址映射技术生成的。虚拟地址空间将内存分为若干个虚拟页面,每个虚拟页面与物理页面一一对应。在程序运行时,操作系统根据需要将虚拟页面映射到物理页面,进而实现对内存访问。 逻辑地址是程序中使用的地址,它是相对地址,相对于程序的起始位置。逻辑地址通常是由编译器或连接器进行生成和管理的,它们会根据程序的需要分配逻辑地址空间,并将逻辑地址映射到虚拟地址或物理地址。 UNC(统一编号字符集)是一种用于标识计算机网络资源的命名方式。UNC地址是由多个部分组成的,包括计算机名称、共享资源名称和文件/文件夹路径。UNC地址不直接与内存管理相关,而是用于网络中对计算机资源进行唯一标识和访问。 总之,内存管理是操作系统对计算机内存进行有效利用和管理的过程,其中物理地址、虚拟地址、逻辑地址和UNC等概念在内存管理中扮演不同的角色,分别用于标志内存中存储单元的位置、实现地址映射和唯一标识计算机网络资源等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值