代码改变世界

第十五章 在应用程序中使用虚拟内存

2018-03-05 22:19  szn好色仙人  阅读(316)  评论(0编辑  收藏  举报
//1.Windows提供了三种机制对内存进行操控:
(A):虚拟内存:最适合用来管理大型对象或大型结构数组
(B):内存映射文件:最适合用来管理大型数据流(通常是文件),以及在同一机器上运行的多个进程间共享数据
(C):堆:最适合用来管理大量的小型对象

//2.
(A):
LPVOID WINAPI VirtualAlloc
(
	__in_opt LPVOID lpAddress,			//告诉系统我们预定的地址空间地址,会向下取整到分配粒度的整数倍,一般传递NULL,由系统决定预定的地址
	__in     SIZE_T dwSize,				//预定区域大小,以字节为单位,系统始终以CPU页面大小的整数倍来进行预定
	__in     DWORD flAllocationType,
	__in     DWORD flProtect
);
Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process.
Memory allocated by this function is automatically initialized to zero.

flAllocationType:
MEM_COMMIT:
Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved memory pages. 
The function also guarantees that when the caller later initially accesses the memory, the contents will be zero. 
Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.
To reserve and commit pages in one step, call VirtualAlloc with MEM_COMMIT | MEM_RESERVE.
Attempting to commit a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the entire range has already been reserved. 
The resulting error code is ERROR_INVALID_ADDRESS.
An attempt to commit a page that is already committed does not cause the function to fail.
This means that you can commit pages without first determining the current commitment state of each page.
If lpAddress specifies an address within an enclave, flAllocationType must be MEM_COMMIT.

MEM_RESERVE:
Reserves a range of the process's virtual address space without allocating any actual physical storage in memory or in the paging file on disk.
You can commit reserved pages in subsequent calls to the VirtualAlloc function. To reserve and commit pages in one step, call VirtualAlloc with MEM_COMMIT | MEM_RESERVE.
Other memory allocation functions, such as malloc and LocalAlloc, cannot use a reserved range of memory until it is released.

MEM_RESET:
Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages should not be read from or written to the paging file. However, 
the memory block will be used again later, so it should not be decommitted. This value cannot be used with any other value.
Using this value does not guarantee that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit the memory and then recommit it.
When you specify MEM_RESET, the VirtualAlloc function ignores the value of flProtect. However, you must still set flProtect to a valid protection value, such as PAGE_NOACCESS.
VirtualAlloc returns an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable if it is mapped to a paging file.

MEM_RESET_UNDO:
MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier. 
It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to the caller and attempts to reverse the effects of MEM_RESET. 
If the function succeeds, that means all data in the specified address range is intact. If the function fails, 
at least some of the data in the address range has been replaced with zeroes.
This value cannot be used with any other value. If MEM_RESET_UNDO is called on an address range which was not MEM_RESET earlier, 
the behavior is undefined. When you specify MEM_RESET, the VirtualAlloc function ignores the value of flProtect.
However, you must still set flProtect to a valid protection value, such as PAGE_NOACCESS.

MEM_LARGE_PAGES
Allocates memory using large page support.
The size and alignment must be a multiple of the large-page minimum. To obtain this value, use the GetLargePageMinimum function.
If you specify this value, you must also specify MEM_RESERVE and MEM_COMMIT.
//使用此标志位需要调用方有内存中锁定页面的权限,默认情况下是没有的,获取方法见书,此处并不关心此标志位

MEM_PHYSICAL
Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages.
This value must be used with MEM_RESERVE and no other values.

MEM_TOP_DOWN
Allocates memory at the highest possible address. This can be slower than regular allocations, especially when there are many allocations.

MEM_WRITE_WATCH
Causes the system to track pages that are written to in the allocated region. If you specify this value, you must also specify MEM_RESERVE.
To retrieve the addresses of the pages that have been written to since the region was allocated or the write-tracking state was reset, 
call the GetWriteWatch function. To reset the write-tracking state, call GetWriteWatch or ResetWriteWatch. 
The write-tracking feature remains enabled for the memory region until the region is freed.

flProtect:一般可以是下面的任意一个
PAGE_NOACCESS
PAGE_READWRITE	//最常用
PAGE_READONLY
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE

https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887(v=vs.85).aspx
(B):在利用 VirtualAlloc 预定了区域后还必须用此函数调拨物理存储器,这样才能访问其中的内存地址
	存储器指定页保护属性的时候,通常使用和预定区域时相同,但我们也可以指定一个完全不同的保护属性
	我们无需一次性给整个区域调拨物理存储器
	只预订区域是不会消耗物理存储器的

//3.
(A):
BOOL WINAPI VirtualFree 
(
	__in LPVOID lpAddress,
	__in SIZE_T dwSize,
	__in DWORD dwFreeType
);
Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling process.

lpAddress:
A pointer to the base address of the region of pages to be freed.
If the dwFreeType parameter is MEM_RELEASE, this parameter must be the base address returned by the VirtualAlloc function when the region of pages is reserved.

dwSize:
The size of the region of memory to be freed, in bytes.
If the dwFreeType parameter is MEM_RELEASE, this parameter must be 0 (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAlloc.
If the dwFreeType parameter is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes in the range from the lpAddress parameter to (lpAddress+dwSize). 
This means, for example, that a 2-byte region of memory that straddles a page boundary causes both pages to be decommitted. 
If lpAddress is the base address returned by VirtualAlloc and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAlloc.
After that, the entire region is in the reserved state.

dwFreeType:
MEM_DECOMMIT:
Decommits the specified region of committed pages. After the operation, the pages are in the reserved state.
The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit a range of pages without first determining the current commitment state.
Do not use this value with MEM_RELEASE.
The MEM_DECOMMIT value is not supported when the lpAddress parameter provides the base address for an enclave.

MEM_RELEASE:
Releases the specified region of pages. After this operation, the pages are in the free state.
If you specify this value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function when the region is reserved. 
The function fails if either of these conditions is not met.
If any pages in the region are committed currently, the function first decommits, and then releases them.
The function does not fail if you attempt to release pages that are in different states, some reserved and some committed. 
This means that you can release a range of pages without first determining the current commitment state.
Do not use this value with MEM_DECOMMIT.
(B):
BOOL WINAPI VirtualProtect
(
	__in  LPVOID lpAddress,		//基地址
	__in  SIZE_T dwSize,		//大小
	__in  DWORD flNewProtect,	//不可以是 PAGE_WRITECOPY 或 PAGE_EXECUTE_WRITECOPY
	__out PDWORD lpflOldProtect	//保存旧属性,不能传 nullptr
);
Changes the protection on a region of committed pages in the virtual address space of the calling process.

(C):
{
	auto pMemory = reinterpret_cast<char*>(VirtualAlloc(nullptr, 65536 * 5, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));

	auto nRe = VirtualFree(pMemory + 65536, 65536, MEM_DECOMMIT);
	nRe = VirtualFree(pMemory + 65536 * 3, 65536, MEM_DECOMMIT);

	*pMemory = 10;						//运行正常
	//pMemory[65536] = 1;				//运行出错
	pMemory[65536 * 2] = 1;				//运行正常

	VirtualAlloc(pMemory + 65536, 65536, MEM_COMMIT, PAGE_READWRITE);
	pMemory[65536] = 1;					//运行正常

	DWORD nOldProject = 0;
	nRe = VirtualProtect(pMemory, 65536, PAGE_READONLY, &nOldProject);	//nOldProject = PAGE_READWRITE(4)
	//pMemory[0] = 1;					//运行出错
	nRe = VirtualFree(pMemory, 0, MEM_RELEASE);	//运行正常
}

//4.
地址窗口扩展(AWE):
(A):允许应用程序以一种特殊的方式分配内存,操作系统保证不会将以这种方式分配的内存换出到磁盘上
(B):允许应用程序访问比进程地址空间还多的内存
(C):基本上,AWE可以让应用程序分配一块或多块内存。当一开始分配的时候,在进程地址空间时看不到这些内存块的。应用程序然后预定地址空间区域,这就是地址窗口,
应用程序然后调用一个函数,每调用一次把一块内存指定到该地址窗口
(D):很明显,通过同一个地址窗口,同一时间只能访问一块内存
(E):AWE不允许把内存页面映射到其他进程的地址空间,因此我们不能在进程间共享内存块
(F):使用AWE需开启 内存中锁定页面用户权限 
(G):
//win32项目
#include <Windows.h>
#include <Psapi.h>

#pragma comment(lib, "psapi")

BOOL LoggedSetLockPagesPrivilege (HANDLE hProcess, BOOL bEnable)
{
	struct {
		DWORD Count;
		LUID_AND_ATTRIBUTES Privilege [1];
	} Info;

	HANDLE Token;
	BOOL Result;

	// Open the token.

	Result = OpenProcessToken ( hProcess,
		TOKEN_ADJUST_PRIVILEGES,
		& Token);

	if( Result != TRUE ) 
	{
		_tprintf( _T("Cannot open process token.\n") );
		return FALSE;
	}

	// Enable or disable?

	Info.Count = 1;
	if( bEnable ) 
	{
		Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
	} 
	else 
	{
		Info.Privilege[0].Attributes = 0;
	}

	// Get the LUID.

	Result = LookupPrivilegeValue ( NULL,
		SE_LOCK_MEMORY_NAME,
		&(Info.Privilege[0].Luid));

	if( Result != TRUE ) 
	{
		_tprintf( _T("Cannot get privilege for %s.\n"), SE_LOCK_MEMORY_NAME );
		return FALSE;
	}

	// Adjust the privilege.

	Result = AdjustTokenPrivileges ( Token, FALSE,
		(PTOKEN_PRIVILEGES) &Info,
		0, NULL, NULL);

	// Check the result.

	if( Result != TRUE ) 
	{
		_tprintf (_T("Cannot adjust token privileges (%u)\n"), GetLastError() );
		return FALSE;
	} 
	else 
	{
		if( GetLastError() != ERROR_SUCCESS ) 
		{
			_tprintf (_T("Cannot enable the SE_LOCK_MEMORY_NAME privilege; "));
			_tprintf (_T("please check the local policy.\n"));
			return FALSE;
		}
	}

	CloseHandle( Token );
	return TRUE;
}

int main()
{
	if (!LoggedSetLockPagesPrivilege)
	{
		printf("缺少权限\n");
	}

	const unsigned long nByteC = 1024 * 1024 * 1024;
	auto pMemory = VirtualAlloc(nullptr, nByteC, MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);
	//此处 MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE 是必须的,不能是其他值

	MEMORYSTATUSEX MemoryInfo = {};
	MemoryInfo.dwLength = sizeof MemoryInfo;
	GlobalMemoryStatusEx(&MemoryInfo);
	PROCESS_MEMORY_COUNTERS ProcessMemoryInfo = {};
	GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemoryInfo, sizeof ProcessMemoryInfo);
	unsigned long aUseByteGB[3] = {ProcessMemoryInfo.PeakWorkingSetSize};
	unsigned long aUsePercent[3] = {MemoryInfo.dwMemoryLoad};

	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	const unsigned long nPageC = nByteC / sysInfo.dwPageSize;
	unsigned long nTemPage = 0;
	const int nPageNumberC = 5;
	unsigned long* aPage[nPageNumberC] = {};
	for (int i = 0; i < nPageNumberC; ++i)
	{
		aPage[i] = new unsigned long[nPageC];		//程序不应该去更改这里的值
		nTemPage = nPageC;
		if (!AllocateUserPhysicalPages(GetCurrentProcess(), &nTemPage, aPage[i]) || nTemPage != nPageC)
		{
			printf("Error 0 \n");
			system("pause");
			return 0;
		}
	}

	GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemoryInfo, sizeof ProcessMemoryInfo);
	GlobalMemoryStatusEx(&MemoryInfo);
	aUseByteGB[1] = ProcessMemoryInfo.PeakWorkingSetSize;
	aUsePercent[1] = MemoryInfo.dwMemoryLoad;

	for (int i = 0; i < nPageNumberC; ++i)
	{
		if (!MapUserPhysicalPages(pMemory, nPageC, aPage[i]))
		{
			printf("Error 1 \n");
			system("pause");
			return 0;
		}
		else
		{
			sprintf_s(reinterpret_cast<char*>(pMemory), nByteC, "Page:%d\n", i);
		}
	}

	for (int i = 0; i < nPageNumberC; ++i)
	{
		if (!MapUserPhysicalPages(pMemory, nPageC, aPage[i]))
		{
			printf("Error 2 \n");
			system("pause");
			return 0;
		}
		else
		{
			printf(reinterpret_cast<char*>(pMemory));
		}
	}

	for (int i = 0; i < nPageNumberC; ++i)
	{
		nTemPage = nPageC;
		if (!FreeUserPhysicalPages(GetCurrentProcess(), &nTemPage, aPage[i]))
		{
			printf("Error 0 \n");
			system("pause");
			return 0;
		}
		delete[] aPage[i];
	}

	VirtualFree(pMemory, 0, MEM_RELEASE);

	GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemoryInfo, sizeof ProcessMemoryInfo);
	GlobalMemoryStatusEx(&MemoryInfo);
	aUseByteGB[2] = ProcessMemoryInfo.PeakWorkingSetSize;
	aUsePercent[2] = MemoryInfo.dwMemoryLoad;

	printf("分配前:本进程峰值工作内存:%d, 系统已使用的物理内存百分比:%d\n", aUseByteGB[0], aUsePercent[0]);
	printf("释放前:本进程峰值工作内存:%d, 系统已使用的物理内存百分比:%d\n", aUseByteGB[1], aUsePercent[1]);
	printf("释放后:本进程峰值工作内存:%d, 系统已使用的物理内存百分比:%d\n", aUseByteGB[2], aUsePercent[2]);
	printf("内存释放前后使用内存增量:%lld\n", (aUsePercent[1] - aUsePercent[0]) * MemoryInfo.ullTotalPhys / 100);
	system("pause");
}

结果: