《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存

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

本章内容
15.1 预定地址空间区域
15.2 给区域调拨物理存储器
15.3 同时预定和调拨物理存储器
15.4 何时调拨物理存储器
15.5 撤销调拨物理存储以及释放区域
15.6 改变保护属性
15.7 重置物理存储器的内容
15.8 地址窗口扩展

Microsoft Windows 提供一下三种机制来对内存进行操控。
虚拟内存  最适合用来管理大型对象数组或大型数据结构
内存映射文件 最适合用来管理大型数据流(通常是文件),以及在同一机器上运行的多个进程之间的共享数据。
最适合用来管理大量的小型对象

windows提供了以下用来操控虚拟内存的函数,可以通过这些函数直接预定地址空间区域,给区域调拨(来自页交换文件)物理存储器,根据需要来设置页面的保护属性。

15.1 预定地址空间区域

我们可以调用 VirtualAlloc函数来预定进程中的地址空间区域:
WINBASEAPI
_Ret_maybenull_ _Post_writable_byte_size_(dwSize)
LPVOID
WINAPI
VirtualAlloc(
    _In_opt_ LPVOID lpAddress,
    _In_ SIZE_T dwSize,
    _In_ DWORD flAllocationType,
    _In_ DWORD flProtect
    );

lpAddress 告知系统想要预定的地址空间中的哪一块。由于系统会记录所有闲置地址区间,通常只需要传递NULL就可以了。
通常分配方向是随机的,但是可以指定一些标志对分配方向进行一些控制( MEM_TOP_DOWN

对于大多数程序员来说,能够让系统在指定的内存地址预定区域是个不同寻常的概念,传统的概念应该是让系统帮我们寻找一块足够大的内存并分配之,然后返回这块内存的地址。但是由于现在每个进程都有自己的地址空间,因此可以要求操作系统在我们希望的内存区域预定区域。

例如想要在虚拟地址起始50MB的地方分配,传递52428800(50x1024x1024)给lpAddress 如果这个内存地址有足够大的闲置区域,则系统会把该区域预定下来。如果没有闲置的区域,或者区域不够大。则 VirtualAlloc返回NULL。另外若传递的lpAddress不在可供用户模式选择的地址空间也会直接返回NULL

因为系统是按照分配粒度来分配的。所以实际返回的地址是以lpAddress为准向下取整的(64KB分配粒度)基地址。

dwSize是我们想要预定区域的大小,以字节为单位。大小必须是页面的整数(4KB, 8KB 或16KB) 如果预定62KB 最终得到的区域大小会是64KB

flAllocationType 告知所分配的区域是否需要调拨物理存储器(这种区分是必要的,因为 VirtualAlloc也可以用于调拨物理存储器)
要预定地址空间必须传 MEM_RESERVE

如果想预定尽可能高的地址(因为要使用很长一段时间,防止引起内存碎片)传NULL给lpAddress 同时传 MEM_TOP_DOWN | MEM_RESERVE 给flAllocationType


flProtect给区域指定保护属性。区域的保护属性对于调拨给该区域的物理存储器不起任何作用。无论区域指定什么保护属性,只要还没给它调拨物理内存,试图访问区域内的任何内存地址都会引发访问违规。

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第1张图片

看一上例子,使用 VirtualAlloc预定了一块区域但是还没有调拨物理内存,试图写入整型数据直接导致异常。

在预定区域并制定保护属性时,应该考虑在调拨物理内存时最常用的保护属性。例如: PAGE_READWRITE保护属性来调拨物理存储器,则使用 PAGE_READWRITE来预定区域。当区域和物理存储器的保护属性一致时,系统内部处理的效率会更高。
可以使用以下保护属性。
PAGE_NOACCESS, PAGE_READWRITE, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ,或 PAGE_EXECUTE_READWRITE.
不能使用 PAGE_WRITECOPYPAGE_EXECUTE_WRITECOPY(否则 VirtualAlloc不会预定区域而直接返回NULL)
同时也不能使用 PAGE_GUARD, PAGE_NOCACHEPAGE_WRITECOMBINE(也会返回NULL)
这些都只能用来调拨物理存储器。

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第2张图片

15.2 给区域调拨物理存储器


预定了区域以后,还需要给区域调拨物理存储器,这样才能访问其中的内存地址。系统会从页交换文件中调拨物理存储器给区域。并且物理存储器的起始地址始终都是页面大小的整数倍,整个大小也是页面大小的整数倍。

使用 VirtualAlloc 并传入 MEM_COMMIT给其第二个参数flAllocationType(预定区域的值为 MEM_RESERVE
指定保护属性通常和预定区域相同。(当然也可以指定不同的保护属性)

通过参数lpAddress(起始地址)和dwSize(存储器的大小)告知系统需要调拨多少物理内存

例如一下例子:应用程序已经在5242880处预定了一个512KB的区域。从改区域的2KB地址开始调拨6KB的物理存储器。
char * p = (char*)VirtualAlloc((PVOID)(5242880 + (2 * 1024)), 
		6 * 1024, MEM_COMMIT, PAGE_READWRITE);
根据页对齐的特性,最终系统会调拨8KB的物理存储器。 5242880到5251071(5242880 + 8KB - 1)之间。这些页面都具有PAGE_READWRITE保护属性。
同一区域中的不同页面可以具有不同的保护属性。


15.3 同时预定和调拨物理存储器

有时候需要同时预定区域并调拨物理内存,代码如下:


PVOID pvMem = VirtualAlloc(NULL, 99 * 1024,
		MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

根据页对齐原则,系统实际会调拨100KB (在x86机器上, IA-64会调拨104KB)

以上代码将可以正常执行。
《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第3张图片

windows系统还提供了大页面支持。使用一下函数:
WINBASEAPI
SIZE_T
WINAPI
GetLargePageMinimum(
    VOID
    );

如果cpu不支持大页面分配, GetLargePageMinimum会返回0. 如果要分配的页面大于该函数的返回值,就可以使用Windows大页面支持。
只要在分配VirtualAlloc并将MEM_LARGE_PAGE和fdwAllocationType按位或运算即可。
还需要满足一下条件:
1) 要分配的内存大小必须是 GetLargePageMinimum函数返回的整数倍。
2)fdwAllocationType 必须传递 MEM_RESERVE | MEM_COMMIT 再或上 MEM_LARGE_PAGE (也就是必须同时预定+调拨物理内存)
3)fdwProtect必须传递PAGE_READWRIE


Windows认为MEM_LARGE_PAGE标志得到的内存是不可换页的(unpagable),也就是必须驻留在内存中。这种方式分配的内存能得到更好的性能。
使用MEM_LARGE_PAGE调用VirtualAlloc需要调用方具有内存中锁定页面(Lock Pages In Memory)的用户权限,否则函数会失败。通常应用程序是不具有此权限了。
在计算机-》控制面板-》管理工具-》本地安全策略中打开此权限。设置如下:

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第4张图片

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第5张图片

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第6张图片
例如以上将admin这个用户添加了此权限。
然后注销再登陆计算机。同时应用程序也必须以提升运行权限的方式来运行。
之后Virtual Alloc会返回所预定区域的虚拟地址。这个地址随后被保存在pvMem变量中。
如果系统无法找到足够大的空间,则VirtualAlloc会返回NULL

这种方式调拨物理内存给 VirtualAlloc函数的pvAddress指定一个地址仍然是可行的
也可以传递NULL,并把fdwAllocationType或上MEM_TOP_DOWN让系统来寻找合适的区域。

15.4 何时调拨物理存储器。

作者举了一个例子:
假设一个电子表格程序,支持200行,256列。 每个单元格需要维护一个CELLDATA的结构体数据大小是128字节。
如果一开始就申明好数据
CELLDATA CellData[200][256];

那么这个二维数据需要6553600个字节(200x256x128)

这样程序一开始就需要分配大量内存,而用户可能只会使用其中的少数几个单元格。内存使用率很低。
通常电子表格都是使用其他数据结构来实现的,比如链表。这样当某个电子表格确实存放了数据,才需要创建与之对应的CELLDATA结构。
但是同时存在一个问题,如果要访问第五行,第十列的单元格内容。这种遍历方法会很慢(事实上,可以采用二维索引表来记录对应单元格的数据地址,采用链表作为数据存储结构。这样能提升访问效率)
虚拟内存也可以提供一种折中方案。享受数组方法所带来的快速而便捷的访问,又能节省存储器

为了使用虚拟内存,需要执行以下步骤。
1)预定一块足够大的区域来容纳CELLDATA结构的整个数组,只预定根本不会消耗物理存储器。
2)当用户在某个单元格中输入数据时,首先确定CELLDATA结构在区域中的内存地址。由于这时还没有给该地址映射物理存储器,试图访问会引发内存错误。
3)给第二步中的内存地址调拨足够的物理存储器。
4)设置CELLDATA结构成员

有几个问题:
如果个某个单元格调拨过物理存储器,由于分配粒度和页面对齐等原因,实际上会给相邻的区域也调拨物理存储器。那么如何判断相邻的区域是否已经调拨了物理存储器呢?
1)总是尝试调拨物理存储器,这样如果该区域已经被调拨,系统并不会额外再给其调拨物理存储器。
2)使用 VirtualQuery来判断是否已经给CELLDATA结构调拨物理存储器。(其实这种方法会增大开销,因为VirtualQuery函数执行开销不低)
3)记录哪些页面已经调拨而哪些页面未调拨。这样可以使程序运行更快,同时避免了频繁调拨VirtualAlloc。
4)使用结构化异常处理(structured exception handling)SEH是操作系统的一项特性,可以在系统发生某种情况通知应用程序。给应用程序设置一个结构化异常处理程序,当程序试图访问未调拨物理存储器的内存时,系统会通知我们的应用程序。接着应用程序可以调拨物理存储器,并告知系统重新执行那条引发异常的指令。

15.5 撤销调拨物理存储器及释放区域

要撤销调拨给区域的物理存储器,或者释放地址空间中的一整块区域,调用以下函数
WINBASEAPI
BOOL
WINAPI
VirtualFree(
    _Pre_notnull_ _When_(dwFreeType == MEM_DECOMMIT, _Post_invalid_) _When_(dwFreeType == MEM_RELEASE, _Post_ptr_invalid_) LPVOID lpAddress,
    _In_ SIZE_T dwSize,
    _In_ DWORD dwFreeType
    );
例如进程不再需要访问区域中的物理存储器,只需要调用 VirtualFree一次,就能够释放整个区域以及调拨给该区域的物理存储器。


pvAddress必须是区域的基地址。也就是预定区域时 VirtualAlloc返回的值。
dwSize 传递0. 因为系统会记录每个分配区域的大小。传递非0值会导致失败。
dwFreeType 传递 MEM_RELEASE 告知系统撤销调拨给区域的所有物理存储器,并释放区域。
这种方式会释放整个已经预定的区域,例如预定了一个128KB的区域,并调拨了64KB的物理存储器,必须释放整个128KB区域。


如果仅想撤销调拨给区域的物理存储器但不想释放整个区域,则这样操作。
pvAddress传递要撤销调拨区域的第一个页地址
dwSize 指定想要释放的物理存储器的大小
dwFreeType传递 MEM_DECOMMIT

另外撤销调拨也是基于页面粒度的,如果给定的地址在一个页面的中间,那么系统会撤销调拨整个页面。
同样如果pvAddress+dwSize也位于一个页面的中间,那么系统会撤销调拨包含该地址的整个页面。

如果dwSize为0,pvAddress又是区域的基地址(起始地址)则 VirtualFree会撤销调拨该区域的所有页面。
一旦系统撤销调拨给页面的物理存储器,那么所释放的物理存储器就可以用于系统中的其他进程,试图访问已撤销调拨的内存地址将引发访问违规。


15.5.1 何时撤销调拨物理存储器

作者又举了电子表格的例子:
如果应用程序在x86机器上运行, 那么存储页面大小就是4KB,每个页面可以存放32(4096/128)个CELLDATA结构。如果用户删了CellData[0][1]的内容,只要CellData[0][0]到CellData[0][31]之间的所有单元格都不用了,就可以撤销调拨该物理存储页。如何知道这些单元格还没有用呢?
1)最理想的方案是将CELLDATA结构设计成正好等于一个页面的大小。这样每个页面只有一个结构,因此不再需要某个结构的数据时直接撤销调拨给该页面的物理存储器。但是实际情况可能很少会用到那么大的数据结构。
2)另一种方案是记录哪些结构正在使用。为了节省内存,可以用一个位图。如果一个包含100个结构的数据,那么只需要维护一个包含100个位的组。一开始所有位都被设置为0,表示没有任何结构。当用到任何结构时,将位设置为1.不需要某些结构时,把对应的位设置为0.接着检查位于同一个页中的相邻结构所对应的个个位。如果同一个页面所有相邻结构都不需要了,那么就可以撤销调拨该页面。
3)另一种解决方案是,实现一个垃圾收集函数。(需要系统在第一次调拨物理存储器时把所有字节都清0)。必须在结构中留出一个BOOL成员(例如bInUse)。每次把结构体放到已调拨内存中,需要把bInUse设置为TRUE。接着在程序运行时,定期调用垃圾回收函数。该函数会遍历所有潜在的数据结构并检查是否已经给各个结构调拨过物理存储器。如果已经调拨过会检查bInUse,看是否为0,如果为0.表示该结构没被用到。如果bInUse为TRUE表明结构还在使用。一旦垃圾收集函数检查完一个给定页面中所有结构,并发现所有结构都没被使用,他会调用 VirtualFree来撤销调拨物理存储器。

前两个方法适合大数据页面的情况。二第三个适合结构较小的方案。

15.5.2 虚拟内存分配示例程序

Virtual Memory Allocator

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第7张图片

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第8张图片

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第9张图片


这个例子在使用单元格数据结构以前采用总是调拨物理内存的方式。
该程序实现了一个 GarbageCollect函数用于垃圾回收,遍历所有结构体是否在使用并在恰当的时候执行 VirtualFree。该函数要求结构的第一个成员必须是BOOL值,用来标识该结构是否正在使用。
这里笔者对显示内存映射图的颜色进行了修改。


另外在程序退出时,虽然并没有提示信息,实际上他会撤销所有已调拨的内存,并释放已预订的区域。
该程序会在以下三个地方确定地址空间区域中内存的状态。
1)更改索引以后,程序需要启动Use并禁用Clear按钮,反之亦然。
2)在垃圾回收函数中,程序在检查bInUse标志之前需要检查是否已经调拨了物理存储器。
3)在更新内存映射图时,程序需要知道哪些页面是闲置的,哪些页面已经预定,而哪些已经调拨物理存储器。

源代码
/******************************************************************************
Module:  VMAlloc.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include 
#include 
#include 
#include "resource.h"

//


// The number of bytes in a page on this host machine.
UINT g_uPageSize = 0;

// A dummy data structure used for the array.
typedef struct {
	BOOL bInUse;
	BYTE bOtherData[2048 - sizeof(BOOL)];
} SOMEDATA, *PSOMEDATA;

// The number of structures in the array
#define MAX_SOMEDATA	(50)

// Pointer to an array of data structures
PSOMEDATA g_pSomeData = NULL;

// The rectangular area in the window occupied by the memory map
RECT g_rcMemMap;

// The pen for the 
HBRUSH g_hBrushWhite = NULL;
HBRUSH g_hBrushGreen = NULL;
HBRUSH g_hBrushYellow = NULL;

//

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

	chSETDLGICONS(hWnd, IDI_VMALLOC);

	// Initialize the dialog box by disabling all the nonsetup controls.
	EnableWindow(GetDlgItem(hWnd, IDC_INDEXTEXT), FALSE);
	EnableWindow(GetDlgItem(hWnd, IDC_INDEX), FALSE);
	EnableWindow(GetDlgItem(hWnd, IDC_USE), FALSE);
	EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), FALSE);
	EnableWindow(GetDlgItem(hWnd, IDC_GARBAGECOLLECT), FALSE);

	// Get the coordinates of the memory map display.
	GetWindowRect(GetDlgItem(hWnd, IDC_MEMMAP), &g_rcMemMap);
	MapWindowPoints(NULL, hWnd, (LPPOINT)&g_rcMemMap, 2);

	// Destroy the window that identifies the location of the memory map
	DestroyWindow(GetDlgItem(hWnd, IDC_MEMMAP));

	// Put the page size in the dialog box just for the user's information.
	TCHAR szBuf[10];
	StringCchPrintf(szBuf, _countof(szBuf), TEXT("%d KB"), g_uPageSize / 1024);
	SetDlgItemText(hWnd, IDC_PAGESIZE, szBuf);

	// Initialize the edit control.
	SetDlgItemInt(hWnd, IDC_INDEX, 0, FALSE);

	return TRUE;
}

//

void Dlg_OnDestroy(HWND hWnd) {

	if (g_pSomeData != NULL)
		VirtualFree(g_pSomeData, 0, MEM_RELEASE);
}


//

VOID GarbageCollect(PVOID pvBase, DWORD dwNum, DWORD dwStructSize) {

	UINT uMaxPages = dwNum * dwStructSize / g_uPageSize;
	for (UINT uPage = 0; uPage < uMaxPages; uPage++) {
		BOOL bAnyAllocsInThisPage = FALSE;
		UINT uIndex = uPage * g_uPageSize / dwStructSize;
		UINT uIndexLast = uIndex + g_uPageSize / dwStructSize;

		for (; uIndex < uIndexLast; uIndex++) {
			MEMORY_BASIC_INFORMATION mbi;
			VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi));
			bAnyAllocsInThisPage = ((mbi.State == MEM_COMMIT) &&
				*(PBOOL)((PBYTE)pvBase + dwStructSize * uIndex));

			// Stop checking this page, we know we can't decommit it.
			if (bAnyAllocsInThisPage)
				break;
		}

		if (!bAnyAllocsInThisPage) {
			// No allocated structures in this page; decommit it.
			VirtualFree(&g_pSomeData[uIndexLast - 1], dwStructSize, MEM_DECOMMIT);
		}
	}
}

//

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

	UINT uIndex = 0;

	switch (id) {
	case IDCANCEL:
		EndDialog(hWnd, id);
		break;

	case IDC_RESERVE:
		// Reserve enough address space to hold the array of structures.
		g_pSomeData = (PSOMEDATA)VirtualAlloc(NULL,
			MAX_SOMEDATA * sizeof(SOMEDATA), MEM_RESERVE, PAGE_READWRITE);

		// Disable the Reserve button and enable all the other controls.
		EnableWindow(GetDlgItem(hWnd, IDC_RESERVE), FALSE);
		EnableWindow(GetDlgItem(hWnd, IDC_INDEXTEXT), TRUE);
		EnableWindow(GetDlgItem(hWnd, IDC_INDEX), TRUE);
		EnableWindow(GetDlgItem(hWnd, IDC_USE), TRUE);
		EnableWindow(GetDlgItem(hWnd, IDC_GARBAGECOLLECT), TRUE);

		// Force the index edit control to have the focus.
		SetFocus(GetDlgItem(hWnd, IDC_INDEX));

		// Force the memory map to update
		InvalidateRect(hWnd, &g_rcMemMap, FALSE);
		break;

	case IDC_INDEX:
		if (codeNotify != EN_CHANGE)
			break;

		uIndex = GetDlgItemInt(hWnd, id, NULL, FALSE);
		if ((g_pSomeData != NULL) && chINRANGE(0, uIndex, MAX_SOMEDATA - 1)) {
			MEMORY_BASIC_INFORMATION mbi;
			VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi));
			BOOL bOk = (mbi.State == MEM_COMMIT);
			if (bOk)
				bOk = g_pSomeData[uIndex].bInUse;

			EnableWindow(GetDlgItem(hWnd, IDC_USE),		!bOk);
			EnableWindow(GetDlgItem(hWnd, IDC_CLEAR),	bOk);
		}
		else {
			EnableWindow(GetDlgItem(hWnd, IDC_USE),		FALSE);
			EnableWindow(GetDlgItem(hWnd, IDC_CLEAR),	FALSE);
		}
		break;

	case IDC_USE:
		uIndex = GetDlgItemInt(hWnd, IDC_INDEX, NULL, FALSE);
		// Note: new pages are always zeroed by the system
		VirtualAlloc(&g_pSomeData[uIndex], sizeof(SOMEDATA),
			MEM_COMMIT, PAGE_READWRITE);

		g_pSomeData[uIndex].bInUse = TRUE;

		EnableWindow(GetDlgItem(hWnd, IDC_USE), FALSE);
		EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), TRUE);

		// Force the Clear button control to have the focus.
		SetFocus(GetDlgItem(hWnd, IDC_CLEAR));

		// Force the memory map to update
		InvalidateRect(hWnd, &g_rcMemMap, FALSE);
		break;

	case IDC_CLEAR:
		uIndex = GetDlgItemInt(hWnd, IDC_INDEX, NULL, FALSE);
		g_pSomeData[uIndex].bInUse = FALSE;
		EnableWindow(GetDlgItem(hWnd, IDC_USE), TRUE);
		EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), FALSE);

		// Force the Use button control to have the focus.
		SetFocus(GetDlgItem(hWnd, IDC_USE));
		break;

	case IDC_GARBAGECOLLECT:
		GarbageCollect(g_pSomeData, MAX_SOMEDATA, sizeof(SOMEDATA));

		// Force the memory map to update
		InvalidateRect(hWnd, &g_rcMemMap, FALSE);
		break;
	}
}

//

void Dlg_OnPaint(HWND hWnd) {	// Update the memory map

	PAINTSTRUCT ps;
	BeginPaint(hWnd, &ps);

	UINT uMaxPages = MAX_SOMEDATA * sizeof(SOMEDATA) / g_uPageSize;
	UINT uMemMapWidth = g_rcMemMap.right - g_rcMemMap.left;

	if (g_pSomeData == NULL) {

		// The memory has yet to be reserved.
		Rectangle(ps.hdc, g_rcMemMap.left, g_rcMemMap.top,
			g_rcMemMap.right - uMemMapWidth % uMaxPages, g_rcMemMap.bottom);
	}
	else {

		// Walk the virtual address space, painting the memory map
		for (UINT uPage = 0; uPage < uMaxPages; uPage++) {

			UINT uIndex = uPage * g_uPageSize / sizeof(SOMEDATA);
			UINT uIndexLast = uIndex + g_uPageSize / sizeof(SOMEDATA);
			for (; uIndex < uIndexLast; uIndex++) {

				MEMORY_BASIC_INFORMATION mbi;
				VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi));

				HBRUSH hBrush = 0;
				switch (mbi.State) {
				case MEM_FREE:		hBrush = g_hBrushWhite;		break;
				case MEM_RESERVE:	hBrush = g_hBrushGreen;		break;
				case MEM_COMMIT:	hBrush = g_hBrushYellow;	break;
				}

				SelectObject(ps.hdc, hBrush);
				Rectangle(ps.hdc,
					g_rcMemMap.left + (uMemMapWidth / uMaxPages) * uPage,
					g_rcMemMap.top,
					g_rcMemMap.left + (uMemMapWidth / uMaxPages) * (uPage + 1),
					g_rcMemMap.bottom);
			}
		}
	}

	EndPaint(hWnd, &ps);
}


//

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	switch (uMsg) {
		chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,	Dlg_OnInitDialog);
		chHANDLE_DLGMSG(hWnd, WM_COMMAND,		Dlg_OnCommand);
		chHANDLE_DLGMSG(hWnd, WM_PAINT,			Dlg_OnPaint);
		chHANDLE_DLGMSG(hWnd, WM_DESTROY,		Dlg_OnDestroy);
	}
	return FALSE;
}

//

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) {

	// Get the page size used on this CPU.
	SYSTEM_INFO si;
	GetSystemInfo(&si);
	g_uPageSize = si.dwPageSize;

	// Init the brush.
	g_hBrushGreen= CreateSolidBrush(RGB(0, 255, 0));
	g_hBrushYellow = CreateSolidBrush(RGB(255, 255, 0));
	g_hBrushWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);

	DialogBox(hInstExe, MAKEINTRESOURCE(IDD_VMALLOC), NULL, Dlg_Proc);

	// Release the brush.
	DeleteObject(g_hBrushGreen);
	DeleteObject(g_hBrushYellow);
	// we don't need to release the white brush because it is the stockobject.
	return 0;
}

 End of File //


15.6 改变保护属性

虽然实际应用中很少需要改变保护属性,但这仍然是可行的。
假设有一段代码来管理一个链表,并把链表中的节点保存在一个已预订的区域中。然后设计一个处理函数,在每个函数的开头把物理存储页的保护属性改成 PAGE_READWRITE,并在函数结束前把保护属性改成 PAGE_NOACCESS
这样可以保护链表的数据,使他免受其他缺陷程序的影响。如果进程中的其他代码试图用一个错误指针来访问链表数据,将会引发访问违规。如果在应用程序中定位一个很难发现的缺陷,可以试试看保护属性,他可以发挥难以置信的作用。
WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
VirtualProtect(
    _In_ LPVOID lpAddress,
    _In_ SIZE_T dwSize,
    _In_ DWORD flNewProtect,
    _Out_ PDWORD lpflOldProtect
    );

lpAddress是内存的基地址(用户分区)
dwSize想要改变保护属性的大小,以字节为单位,
flNewProtect 除了PAGE_WRITECOPY和PAGE_EXECUTE_WRITECOPY以外的任何PAGE_*保护属性。
lpfOldProtect 返回原来的保护属性。必须传递一个有效的地址给lpfOldProtect,否则会调用失败。


保护属性是与整个物理存储页关联的,以下代码实际上是在给两个物理存储页指定保护属性 PAGE_NOACCESS

VirtualProtect(pvRgnBase + (3 * 1024), 2 * 1024,
		PAGE_NOACCESS, &flOldProtect);

若干连续的物理存储页跨越了不同的区域时, VirtualProtect是不能改变他们的保护属性的。如果有相邻的区域,有想改变跨区域的连续页面的保护属性,需要调用 VirtualProtect多次。

15.7 重置物理存储器的内容

当修改物理存储页的时候,系统尽量把改动保持在内存中。但是,当应用程序在运行时候,系统可能要从exe文件,dll文件或者页交换文件中载入新的页面到内存。为了满足最近的载入请求,系统会在内存中查找可用的页面,如果找到的页面已经被修改过,那么系统还必须将它们换出到页面文件中。

windows提供了一项特性(重置物理存储器)告知系统一个或几个物理存储页中的数据没有被修改过。如果系统正在查找一页闲置内存,并找到了一个修改过的页面,那么系统必须把该内存页写入到页交换文件中。这个操作比较慢,会影响性能。对于大多数应用程序来说,我们都希望系统把修改后的页面保存到页交换文件中。

但是,有些应用程序只在一小段时间使用存储器,之后也不需要保留存储器中的内容。为了提高性能,应用程序可以告知系统不要在交换文件中保存指定的存储页。这是一种告知系统一个页面未被修改过的一种方法。如果系统要将一个内存页挪做他用,他不会将页面的内容保存到页交换文件中,这样就提高的了性能。
为了重置存储器,应用程序调用 VirtualAlloc函数,并在第三个参数传入 MEM_RESET标志。


调用 VirtualAlloc时,如果被引用到的页面在页交换文件中,那么系统会直接抛弃这些页面。下次应用程序再访问存储器,会使用新的,全部清零的内存页。如果重置的页面在内存中,系统会将其标记为未修改过。这样系统就绝不会把他们写到页交换文件中。(注意,即使该内存页没有被清零,我们也不应该继续读取其中的内容,因为系统随时可能会把该页挪做他用。因此一旦做了页重置,就应当将其视为无效数据)

还有一些注意事项:
1)调用 VirtualAlloc,基地址通常会被向下取整到页面大小的整数倍,而大小会被向下取整到页面大小的整数倍;在重置存储器时,这种方式对基地址进行取整是非常危险的;如果传入 MEM_RESET那么VirtualAlloc会从相反的方向进行取整操作。
例如:
PINT pnData = (PINT)VirtualAlloc(NULL, 1024, 
		MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	pnData[0] = 100;
	pnData[1] = 200;
	VirtualAlloc((PVOID)pnData, sizeof(int), MEM_RESET, PAGE_READWRITE);

该代码先调拨了一页存储器,然后告知系统最前4个字节(sizeof(4))不再使用,可以被重置。事实上重置会调用失败。VirtualAlloc返回NULL,GetLastError返回ERROR_INVALID_ADDRESS
因为在执行 MEM_RESET的时候为了防止误操作把其他重要的数据抛弃。会把大小向下取整到0,而重置0字节是没有意义的。把大小向下取整到页面的整数倍也是出于同样的原因:如果垃圾数据并未占满整个页,那么我们并不希望重置整个页,因为其中可能还包含有效数据。这样系统就确保只会重置整个页面都是垃圾数据的内存页。

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第10张图片


2) MEM_RESET只能单独使用,不能和其他标志进行按位或。

PVOID pvMem = VirtualAlloc(NULL, 1024,
		MEM_RESERVE | MEM_COMMIT | MEM_RESET, PAGE_READWRITE);
该代码会失败并返回NULL

3)在使用 MEM_RESET调用 VirtualAlloc时,需要传递一个有效的保护属性值,实际上函数并没有用到这个值。


MemReset示例程序

该程序首先预定并调拨一块区域,然后写入一个数据。
之后弹出一个框提示用户是否以后还要访问该数据?

如果用户选择NO。则会将该区域标记为未修改(调用 VirtualAlloc MEM_RESET
之后,模拟了一个大内存写入的操作。目的是为了让系统抛弃刚才被重置的页面。
1)调用 GlobalMemoryStatus获得机器中的内存总量
2)调用 VirtualAlloc函数来调拨存储器,调用的数量为第一步中得到的数量。这个操作非常快,除非进程去试图访问野蛮,否则系统是不会为页面分配内存的。
如果 VirtualAlloc在最新的机器上可能会返回NULL,这可能是因为机器中的内存总量比进程可用地址空间还要多(例如在装配了4GB以上内存的机器运行一个32位程序)
3)调用 ZeroMemory函数以访问刚才调拨的内存,这会给系统很大的压力。并导致原来在内存中的一些页面被写入到交换文件。

如果用户选择了YES,表示以后还要用该数据,程序就不会对页面进行重置。当程序后来再访问该数据,页面会被换入到内存中。

ZeroMemory返回以后,程序会将页面中的数据与原先写入的字符串进行比较。如果页面没有被重置过。那么内容应该是相同的。如果被重置过,那么两者可能相同也可能不同。
但是在这个实例中由于模拟了内存大压力所有的页面都会被强制写入页交换文件,因此两者的内容绝对会不同。
但是如果pvDummy区域小于机器的内存总量,那么原先的内容可能还在内存中。

----------------
在笔者的机器上这个例子的源代码并不能起作用。原因是 32位应用跑在64位系统下只能使用2GB的用户模式空间,而笔者的机器安装了8GB的物理内存, VirtualAlloc试图预定并调拨超过2GB的内存必然导致失败。因此对源代码进行了修改并使用64位模式编译。同时开启了/LARGEADDRESSAWARE的链接选项。
《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第11张图片


后来发现每次调用VirtualAlloc申请大内存会报告这样一个错误0x000005AF 
《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第12张图片

如果用 VirtualAlloc预定并调拨和系统物理内存相同大小的内存空间,必然会导致大量的内存操作(系统会将大量页写入物理存储器的页面文件中)。页面文件的可用空间必须大于当前已经使用的内存的空间。因此将大量的进程关闭并释放足够的空闲内存(这将减少换页操作,使得这个应用能更快的运行。)
观察任务管理器,有一个极大的峰值。 实际上在执行ZeroMemory的时候内存使用率高达99%。
《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第13张图片

最后提示被执行过MEM_SET的数据并未被写入到物理页中。
《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第14张图片

主要代码修改点:

原书中的代码
   // Commit as much storage as there is physical RAM.
   MEMORYSTATUS mst;
   GlobalMemoryStatus(&mst);
   PVOID pvDummy = VirtualAlloc(NULL, mst.dwTotalPhys, 
      MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);


修改以后的代码:
	// Commit as much storage as there is physical RAM.
	MEMORYSTATUSEX mst = {sizeof(mst)};
	GlobalMemoryStatusEx(&mst);
	PVOID pvDummy = VirtualAlloc(NULL, mst.ullTotalPhys,
		MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

最后贴上完整的可以在x64机器大于4GB以上内存的机器中可运行的代码。(注意要编译成x64的可执行文件)
/******************************************************************************
Module:  MemReset.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include   


//

int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {

	TCHAR szAppName[]	= TEXT("MEM_RESET tester");
	TCHAR szTestData[]	= TEXT("Some text data");

	// Commit a page of storage and modify its contents.
	PTSTR pszData = (PTSTR)VirtualAlloc(NULL, 1024,
		MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	_tcscpy_s(pszData, 1024, szTestData);

	if (MessageBox(NULL, TEXT("Do you want to access this data later?"),
		szAppName, MB_YESNO) == IDNO) {

		// We want this page of storage to remain in our process but the
		// contents aren't important to us anymore.
		// Tell the system that the data is not modified.

		// Note: Because MEM_RESET destroys data, VirtualAlloc rounds
		// the base address and size parameters to their safest range.
		// Here is an example:
		//		VirtualAlloc(pvData, 5000, MEM_RESET, PAGE_READWRITE)
		// resets 0 pages on CPUs where the page size is greater than 4KB
		// and resets 1 page on CPUs with a 4 KB page. So that our call to
		// VirtualAlloc to reset memory below always succeeds, VirtualQuery
		// is called first to get the exact region size.
		MEMORY_BASIC_INFORMATION mbi;
		VirtualQuery(pszData, &mbi, sizeof(mbi));
		VirtualAlloc(pszData, mbi.RegionSize, MEM_RESET, PAGE_READWRITE);
	}

	// Commit as much storage as there is physical RAM.
	MEMORYSTATUSEX mst = {sizeof(mst)};
	GlobalMemoryStatusEx(&mst);
	PVOID pvDummy = VirtualAlloc(NULL, mst.ullTotalPhys,
		MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

	// Touch all the pages in the dummy region so that any
	// modified pages in RAM are written to the paging file.
	if (pvDummy != NULL)
		ZeroMemory(pvDummy, mst.ullTotalPhys);

	// Compare our data page with what we originally wrote there.
	if (_tcscmp(pszData, szTestData) == 0) {

		// The data in the page matches what we originally put there.
		// ZeroMemory forced our page to be written to the paging file.
		MessageBox(NULL, TEXT("Modified data page was saved."),
			szAppName, MB_OK);
	}
	else {

		// The data in the page does NOT match what we originally put there
		// ZeroMemory didn't cause our page to be written to the paging file
		MessageBox(NULL, TEXT("Modified data page was NOT saved."),
			szAppName, MB_OK);
	}

	// Don't forget to release part of the address space.
	// Note that it is not mandatory here since the application is exiting.
	if (pvDummy != NULL)
		VirtualFree(pvDummy, 0, MEM_RELEASE);
	VirtualFree(pszData, 0, MEM_RELEASE);
	return 0;
}

 End of File //


15.8 地址窗口扩展

随着时间的推移,应用程序需要越来越多的内存。服务器程序尤其如此,将数据保存在内存中而减少磁盘和内存的页交换以提高性能。32位地址空间不大够用

Windows提供了一项特性,即地址窗口扩展(Address Windowing Extension,也称作AWE)在创建AWE以后,Microsoft有以下两个目标:
1)允许应用程序以一种特殊的方式分配内存,操作系统保证不会将以这种方式分配的内存换出到磁盘上。
2)允许应用程序访问比进程地址空间还要多的内存。

AWE可以让应用程序分配一块或多块内存。当一开始分配时,在进程的地址空间中是看不到这些内存块的。应用程序通过调用 VirtualAlloc预定地址空间区域,这就是地址窗口。然后程序调用一个函数,每调用一个函数,就把一块内存指定到该地址窗口。(把内存地址指定到地址窗口是毫秒级别的)

使用地址窗口,一次只能访问一块内存。因此需要在代码中显式调用函数来把不同的内存块指定到地址窗口中。(这增加了代码编写的难度)

以下代码显示了如何使用AWE:
	// First, reserve a 1MB region for the address window
	ULONG_PTR ulRAMBytes = 1024 * 1024;
	PVOID pvWindow = VirtualAlloc(NULL, ulRAMBytes,
		MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);

	// Get the number of bytes in a page for this CPU platform
	SYSTEM_INFO sinf;
	GetSystemInfo(&sinf);

	// Calculate the required number of RAM pages for the
	// desired number of bytes
	ULONG_PTR ulRAMPages = (ulRAMBytes + sinf.dwPageSize - 1) / sinf.dwPageSize;

	// Allocate array for RAM page's page frame numbers
	ULONG_PTR * aRAMPages = (ULONG_PTR *) new ULONG_PTR[ulRAMPages];

	// Allocate the pages of RAM (requires Lock Pages in Memory user right)
	AllocateUserPhysicalPages(
		GetCurrentProcess(),	// Allocate the storage for our process
		&ulRAMPages,			// Input: # of RAM pages, Output: # pages allocated
		aRAMPages);				// Output: Opaque array indicating pages allocated

	// Assign the RAM pages to our window
	MapUserPhysicalPages(
		pvWindow,				// The address of the address window
		ulRAMPages,				// Number of entries in array
		aRAMPages);				// Array of RAM pages
	
	// Access the RAM pages via the pvWindow virtual address

	// Free the block of RAM pages
	FreeUserPhysicalPages(
		GetCurrentProcess(),	// Free the RAM allocated for our process
		&ulRAMPages,			// Input: # of RAM pages, Output: # pages freed
		aRAMPages);				// Input: Array indicating the RAM pages to free

	// Destroy the address window
	VirtualFree(pvWindow, 0, MEM_RELEASE);
	delete[] aRAMPages;

程序通过调用 VirtualAlloc函数来预定1MB的地址窗口(实际应用会更大,当然他受限制与进程地址空间中最大的,连续的闲置区域) MEM_RESERVE表示要预定一块地址区域。 MEM_PHYSICAL标志表示该区域最终会以物理内存(RAM)作为后备(不使用物理页交换文件作为后备)。 AWE的一个限制是所有映射到地址窗口的存储器必须是可读可写 的。 因此页保护属性传递 PAGE_READWRITE.
不用使用 VirtualProtect函数来改变页保护属性。

分配物理存储器调用一下函数:
WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
AllocateUserPhysicalPages(
    _In_ HANDLE hProcess,
    _Inout_ PULONG_PTR NumberOfPages,
    _Out_writes_to_(*NumberOfPages, *NumberOfPages) PULONG_PTR PageArray
    );

这个函数会根据NumberOfPages参数所指向的值来分配相应数量的的内存页,然后将这些页分配给hProcess参数所标识的进程。

操作系统会给每个页面指定一个页框号(page frame number)系统会将每个页面的页框号保存到PageArray所指向的数组。页框号本身对于应用程序来说没有什么用处。
同时函数返回以后NumberOfPages的值表示成功分配的页面数量。通常这个值和传递值相同也可能会更小。

只有当前进程才能通过 AllocateUserPhysicalPages分配得到内存页面, AWE不允许将内存页面映射到其他进程的地址空间中。(不能在进程中共享内存块)

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第15张图片
经过测试按照15.3节中设定的提供了锁定内存的权限并重启了计算机。 AllocateUserPhysicalPages函数仍然调用失败(getLastError返回 ERROR_PRIVILEGE_NOT_HELD)。虽然本书后续的例子中有讲解如何提升权限。但是笔者经过网上资料的查找这一步还是很重要的。这里单独讲一下。

// 设置锁住物理内存的权限,此代码在调用AllocateUserPhysicalPages之前执行
//if (!AWESetLockPagesPrivilege(GetCurrentProcess(), TRUE))
//{
//	// 输出错误信息
//	..........
//}
/// 
///        设置或清除启用AWE( Address Windowing Extensions )所需要的锁住内存的权限。
/// 
/// 
///        进程句柄。
/// 
/// 
///        设置或者清除标志。
/// 
/// 
///        如果成功,则返回TRUE,否则返回失败。
/// 
BOOL AWESetLockPagesPrivilege(HANDLE hProcess, BOOL Enable)
{
	HANDLE                Token = NULL;
	BOOL                Result = FALSE;
	TOKEN_PRIVILEGES    Info = { 0 };
	// 打开令牌
	Result = OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &Token);
	if (!Result)
		return FALSE;
	// 设置权限信息
	Info.PrivilegeCount = 1;
	Info.Privileges[0].Attributes = Enable ? SE_PRIVILEGE_ENABLED : 0;
	// 获得锁定内存权限的ID
	Result = LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &(Info.Privileges[0].Luid));
	if (!Result)
	{
		CloseHandle(Token);
		return FALSE;
	}
	// 调整权限
	Result = AdjustTokenPrivileges(Token, FALSE, (PTOKEN_PRIVILEGES)&Info, 0, NULL, NULL);
	if ((!Result) || (GetLastError() != ERROR_SUCCESS))
	{
		CloseHandle(Token);
		return FALSE;
	}
	// 成功返回
	CloseHandle(Token);
	return TRUE;
}

在调用 AllocateUserPhysicalPages之前调用此函数来提升应用程序的权限。即可解决此问题。

-----
接下来调用一下函数把内存块指定给地址窗口

WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
MapUserPhysicalPages(
    _In_ PVOID VirtualAddress,
    _In_ ULONG_PTR NumberOfPages,
    _In_reads_opt_(NumberOfPages) PULONG_PTR PageArray
    );

VirtualAddress是指定给地址窗口的虚拟地址。
NumberOfPages表示要通过这个地址窗口看到多少个页面的内存。
PageArray 表示通过该地址窗口看到哪些页面的内存。
如果地址窗口的大小小于我们试图映射的页面数,则会调用失败。一般该函数的执行时间是毫秒级别。
《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第16张图片

一旦将内存块指定到地址窗口,就可以用一个相对于地址窗口的基地址来访问。不需要该内存块时,使用 FreeUserPhysicalPages来释放。
WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
FreeUserPhysicalPages(
    _In_ HANDLE hProcess,
    _Inout_ PULONG_PTR NumberOfPages,
    _In_reads_(*NumberOfPages) PULONG_PTR PageArray
    );

hProcess表示要释放哪个进程的内存页。
NumberOfPages表示要释放多少个页面
PageArray表示要释放的页面框号。
如果内存块已经被映射到窗口,那么系统会取消映射并释放内存块。

最后为了完成清理工作,程序调用VirtualFree并传入窗口的虚拟地址,以0位区域大小,并传入 MEM_RELEASE标志。

这个例子创建了一个地址窗口和一个内存块。使得访问内存并不需要和磁盘进行页交换。应用程序也可以创建多个地址窗口,并分配多个内存块。并把内存块指定给任何一个地址窗口。但系统不允许一个内存块同时出现在两个地址窗口中。

AWE运行应用程序不会和磁盘进行页交换。

AWE示例程序

以下实例演示了如何创建多个地址窗口并给他们制定不同的内存块。
该程序有3个类
1. CSystemInfo 对GetSystemInfo函数进行封装
2. CAddrWindow 封装了地址窗口
3. CAddrWindowStorage 对内存块进行了封装。同时可以将其制定给CAddrWindow对象。
Allocate方法能启动内存锁定的用户权限,然后试图分配内存并禁用权限。
Free用来释放内存块。
HowManyPagesAllocated方法返回已经成功分配的页面数量。
MapStorage用于把内存块映射给一个CAddrWindow对象
UnmapStorage方法用于撤销映射。

另外实例程序有一个manifest组件清单,windows会始终弹出要求提升运行权限的窗口。

《Windows核心编程》读书笔记十五 在应用程序中使用虚拟内存_第17张图片

AddrWindow.h

/******************************************************************************
Module:  AddrWindow.h
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/


#pragma once


///

#include "..\CommonFiles\CmnHdr.h"
#include 

//

class CSystemInfo : public SYSTEM_INFO {
public:
	CSystemInfo() { GetSystemInfo(this); }
};

//

class CAddrWindow {
public:
	CAddrWindow() { m_pvWindow = NULL; }
	~CAddrWindow() { Destroy(); }

	BOOL Create(SIZE_T dwBytes, PVOID pvPreferredWindowBase = NULL) {
		// Reserve address window region to view physical storage
		m_pvWindow = VirtualAlloc(pvPreferredWindowBase, dwBytes,
			MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);
		return (m_pvWindow != NULL);
	}

	BOOL Destroy() {
		BOOL bOk = TRUE;
		if (m_pvWindow != NULL) {
			// Destroy address window region
			bOk = VirtualFree(m_pvWindow, 0, MEM_RELEASE);
			m_pvWindow = NULL;
		}
		return bOk;
	}

	BOOL UnmapStorage() {
		// Unmap all storage from address window region
		MEMORY_BASIC_INFORMATION mbi;
		VirtualQuery(m_pvWindow, &mbi, sizeof(mbi));
		return (MapUserPhysicalPages(m_pvWindow,
			mbi.RegionSize / sm_sinf.dwPageSize, NULL));
	}

	// Returns virtual address of address window
	operator PVOID() { return (m_pvWindow); }

private:
	PVOID m_pvWindow;		// Virtual address of address window region
	static CSystemInfo sm_sinf;
};


//

CSystemInfo CAddrWindow::sm_sinf;

//

class CAddrWindowStorage {
public:
	CAddrWindowStorage() { m_ulPages = 0; m_pulUserPfnArray = NULL; }
	~CAddrWindowStorage() { Free(); }

	BOOL Allocate(ULONG_PTR ulBytes) {
		// Allocate storage intended for an address window

		Free();		// Clean up this object's existing address window

		// Calculate number of pages from number of bytes
		m_ulPages = (ulBytes + sm_sinf.dwPageSize - 1) / sm_sinf.dwPageSize;

		// Allocate array of page frame numbers
		m_pulUserPfnArray = (PULONG_PTR)
			HeapAlloc(GetProcessHeap(), 0, m_ulPages * sizeof(ULONG_PTR));

		BOOL bOk = { m_pulUserPfnArray != NULL };
		if (bOk) {
			// The "LockPages in Memory" privilege must be enabled
			EnablePrivilege(SE_LOCK_MEMORY_NAME, TRUE);
			bOk = AllocateUserPhysicalPages(GetCurrentProcess(),
				&m_ulPages, m_pulUserPfnArray);
			EnablePrivilege(SE_LOCK_MEMORY_NAME, FALSE);
		}
		return bOk;
	}

	BOOL Free() {
		BOOL bOk = TRUE;
		if (m_pulUserPfnArray != NULL) {
			bOk = FreeUserPhysicalPages(GetCurrentProcess(),
				&m_ulPages, m_pulUserPfnArray);
			if (bOk) {
				// Free the array of page frame numbers
				HeapFree(GetProcessHeap(), 0, m_pulUserPfnArray);
				m_ulPages = 0;
				m_pulUserPfnArray = NULL;
			}
		}
		return bOk;
	}

	ULONG_PTR HowManyPagesAllocated() { return m_ulPages; }

	BOOL MapStorage(CAddrWindow & aw) {
		return (MapUserPhysicalPages(aw,
			HowManyPagesAllocated(), m_pulUserPfnArray));
	}

	BOOL UnmapStorage(CAddrWindow & aw) {
		return (MapUserPhysicalPages(aw,
			HowManyPagesAllocated(), NULL));
	}

private:
	static BOOL EnablePrivilege(PCTSTR pszPrivName, BOOL bEnable = TRUE) {

		BOOL bOk = FALSE;			// Assume function fails
		HANDLE hToken;

		// Try to open this process' access token	
		if (OpenProcessToken(GetCurrentProcess(),
			TOKEN_ADJUST_PRIVILEGES, &hToken)) {

			// Attempt to modify the "Lock pages in Memory" Privilege
			TOKEN_PRIVILEGES tp = { 1 };
			LookupPrivilegeValue(NULL, pszPrivName, &tp.Privileges[0].Luid);
			tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
			AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
			bOk = (GetLastError() == ERROR_SUCCESS);
			CloseHandle(hToken);
		}
		return bOk;
	}

private:
	ULONG_PTR	m_ulPages;			// Number of storage pages
	PULONG_PTR	m_pulUserPfnArray;	// Page frame array

	static CSystemInfo sm_sinf;
};

///


CSystemInfo CAddrWindowStorage::sm_sinf;


 End of File //


AWE.cpp

/******************************************************************************
Module:  AWE.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include 
#include 
#include 
#include "AddrWindow.h"
#include "Resource.h"

//

CAddrWindow g_aw[2];				// 2 memory address windows
CAddrWindowStorage g_aws[2];		// 2 storage blocks
const ULONG_PTR g_nChars = 1024;	// 1024 character buffers
const DWORD g_cbBufferSize = g_nChars * sizeof(TCHAR);

//


BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

	chSETDLGICONS(hWnd, IDI_AWE);

	// Create the 2 memory address windows
	chVERIFY(g_aw[0].Create(g_cbBufferSize));
	chVERIFY(g_aw[1].Create(g_cbBufferSize));

	// Create the 2 storage blocks
	if (!g_aws[0].Allocate(g_cbBufferSize)) {
		chFAIL("Failed to allocate RAM.\nMost likely reason: "
			"you are not granted the Lock Pages in Memory user right.");
	}
	chVERIFY(g_aws[1].Allocate(g_nChars * sizeof(TCHAR)));

	// Put some default text in the 1st storage block
	g_aws[0].MapStorage(g_aw[0]);
	_tcscpy_s((PTSTR)(PVOID)g_aw[0], g_cbBufferSize, TEXT("Text in Storage 0"));

	// Put some default text in the 2nd storage block
	g_aws[1].MapStorage(g_aw[0]);
	_tcscpy_s((PTSTR)(PVOID)g_aw[0], g_cbBufferSize, TEXT("Text in Storage 1"));

	// Populate the dialog box controls
	for (int n = 0; n <= 1; n++) {
		// Set the combo box for each address window
		int id = ((n == 0) ? IDC_WINDOW0STORAGE : IDC_WINDOW1STORAGE);
		HWND hWndCB = GetDlgItem(hWnd, id);
		ComboBox_AddString(hWndCB, TEXT("No storage"));
		ComboBox_AddString(hWndCB, TEXT("Storage 0"));
		ComboBox_AddString(hWndCB, TEXT("Storage 1"));

		// Window 0 shows Storage 0, Window 1 shows Storage 1
		ComboBox_SetCurSel(hWndCB, n + 1);
		FORWARD_WM_COMMAND(hWnd, id, hWndCB, CBN_SELCHANGE, SendMessage);
		Edit_LimitText(GetDlgItem(hWnd,
			(n == 0) ? IDC_WINDOW0TEXT : IDC_WINDOW1TEXT), g_nChars);
	}

	return TRUE;
}

//


void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

	switch (id) {

	case IDCANCEL:
		EndDialog(hWnd, id);
		break;

	case IDC_WINDOW0STORAGE:
	case IDC_WINDOW1STORAGE:
		if (codeNotify == CBN_SELCHANGE) {

			// Show different storage in address window
			int nWindow = ((id == IDC_WINDOW0STORAGE) ? 0 : 1);
			int nStorage = ComboBox_GetCurSel(hWndCtl) - 1;

			if (nStorage == -1) { // Show no storage in this window
				chVERIFY(g_aw[nWindow].UnmapStorage());
			}
			else {
				if (!g_aws[nStorage].MapStorage(g_aw[nWindow])) {
					// Couldn't map storage in window
					chVERIFY(g_aw[nWindow].UnmapStorage());
					ComboBox_SetCurSel(hWndCtl, 0);	// Force "No storage"
					chMB("This storage can be mapped only once.");
				}
			}

			// Update the address window's text display
			HWND hWndText = GetDlgItem(hWnd,
				((nWindow == 0) ? IDC_WINDOW0TEXT : IDC_WINDOW1TEXT));
			MEMORY_BASIC_INFORMATION mbi;
			VirtualQuery(g_aw[nWindow], &mbi, sizeof(mbi));
			// Note: mbi.State == MEM_RESERVE if no storage is in address window
			EnableWindow(hWndText, (mbi.State == MEM_COMMIT));
			Edit_SetText(hWndText, IsWindowEnabled(hWndText) ?
				(PCTSTR)(PVOID)g_aw[nWindow] : TEXT("No storage"));
		}
		break;

	case IDC_WINDOW0TEXT:
	case IDC_WINDOW1TEXT:
		if (codeNotify == EN_CHANGE) {
			// Update the storage in the address window
			int nWindow = ((id == IDC_WINDOW0TEXT) ? 0 : 1);
			Edit_GetText(hWndCtl, (PTSTR)(PVOID)g_aw[nWindow], g_nChars);
		}
		break;
	}
}

//

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	switch (uMsg) {
		chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
		chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
	}

	return FALSE;
}

//

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) {

	DialogBox(hInstExe, MAKEINTRESOURCE(IDD_AWE), NULL, Dlg_Proc);
	return 0;
}


你可能感兴趣的:(Windows)