《windows 核心编程》 -探索虚拟内存

14.1 系统信息

操作系统中有许多值 是由系统所运行的主机所决定的。如果页面大小和分配粒度等。我们决对不应该在代码中将这些值写死。

此函数得到系统信息VOID GetSystemInfo(LPSYSTEM_INFO ps)

如果想得到机器中与处理器有关的详细信息可以调用GetLogicalProcesorInfomation函数

为了让32位应用程序在64位版本的Windows运行,Microsoft提供了一个称为windows 32 bit On Windows 64 的模拟层又称为WOW。当32 位应用程序通过WOW64运行时,GetSystemInfo的返回值在64位应用程序可能会有所不同。如果想知道进程是否在WOW64运行可以调用下面的函数

BOOL IsWow64Process(HANDLE hProcess PBOOL pbWow64Process); 
只有32位程序在WOW64上运行时该布尔值才会被设为TRUE,在这种情况下我们需要调用void GetNativeSystemInfo来取得原来的SYSTEM_INFO结构。

14.1 虚拟内存状态

Windows函数GlobalMemoryStatus可以用来取得当前内存动态信息 
如果预计应用程序会在装有4GB的内存的机器上运行,或者页交换文件的大小可能会大于4GB,那么就应该调用GlobalMemoryStatusEx函数。

14.3 NUMA机器中的内存管理

NUMA(Non-Uniform Memory Acess),非统一内存访问机器中的CPU既能访问自己节点的内存,也能访问其它节点的内存。但是,对CPU来说,访问自己节点的内存比方访问外节点的内存要快的多。在默认情况下,当线程调拨物理存储器时,操作系统会尽量用CPu自己节点的内存来支持物理存储器,以提高内存访问的性能。但是,如果没有足够的内存,那么Windows也会使用其它结点的内存来支持物理存储器。

在调用GlobalMemoryStatusEx函数时,在ullAvailPhys参数中返回的值是所有节点可用内存总量。如果要知道某个特定NUMA节点的内存数量,那么可以调用下面的函数

BOOL GetNumaAvailableMemoryNode(UCHAR uNode, //标识节点 
        PULONGLONG pulAvailableBytes);  //用来返回该节点可用的内存总量。

只需要调用GetNumaProcessorNode函数就可以得到一个CPU驻留在哪个NUMA 节点中。

可以用GetNumaHighestNodeNumber(PULONG pulHighestNodeNumber);得到系统中节点的总数。

对于任何一个指定的节点来说,他的值价于0和pulHighestNodeNumber参数所指变量值之间。我们可以调用下面的函数来得到驻留在某个节点中的CPU列表;

BOOL GetNumaNodeProcessorMark(UCHAR uNode, 
    PULONGLONG pulProcessorMask);

示例:

《windows 核心编程》 -探索虚拟内存
#include "stdafx.h"
#include <iostream>
#include <windows.h>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    //获取内存状态的动态信息
    MEMORYSTATUS memStatus = {0};
    memStatus.dwLength = sizeof(MEMORYSTATUS);
    GlobalMemoryStatus(&memStatus);

    cout<<"MemLoaded:"<<memStatus.dwMemoryLoad<<endl;  //告诉我们内存管理系统有多忙,它可以是从0~100之间的任何数值
    cout<<"TobalPhys:"<<memStatus.dwTotalPhys<<endl;   //物理内存总量  如果是1G内存 则会小于1G 因为系统在启动过程中会为非页面缓冲池保留一部分内存
    cout<<"AvailPhy :"<<memStatus.dwAvailPhys<<endl;   //有效物理内存

    cout<<"TotalPageSize:"<<memStatus.dwTotalPageFile<<endl; //表示硬盘页交换文件最多能存放多少字节数据
    cout<<"AvailPageSize:"<<memStatus.dwAvailPageFile<<endl; //页交换文件中尚未调拨的字节

    cout<<"TotalVirtual :"<<memStatus.dwTotalVirtual<<endl; //表示地址空间中为各进程私有的那部分的字节数
    cout<<"AvailVirtual :"<<memStatus.dwAvailVirtual<<endl; //与进程相关,GlobalMemoryStatus会把调用进程的地址空间中所有闲置的区域都加起来


    system("pause");
    return 0;
}
《windows 核心编程》 -探索虚拟内存

没有哪个成员能表示物理存储器的数量。我们把一个进程的地址空间中被保存在内存里的那些页面称为它的工作集
对于一个进来来说我们可能通过GetProcessMemoryInfo来得到正在使用

《windows 核心编程》 -探索虚拟内存
HANDLE hCurProcess = ::GetCurrentProcess();
    PROCESS_MEMORY_COUNTERS_EX pmc;
    if(!GetProcessMemoryInfo(hCurProcess,(PROCESS_MEMORY_COUNTERS*)&pmc,sizeof(PROCESS_MEMORY_COUNTERS_EX)))
    {
        cout<<"GetProcessMemoryInfo Failed!"<<endl;
    }
    else
    {
        cout<<"WorkingSetSize:"<<pmc.WorkingSetSize<<endl; //进程程序集正在使用的字节数
        cout<<"PeakWorkSetSize:"<<pmc.PeakWorkingSetSize<<endl;//程序集目前曾使用过的内存数量最大值
        cout<<"PrivateUsage:"<<pmc.PrivateUsage<<endl; //应用程序通过new.malloc,VirtualAlloc显示分配的内存
    }
《windows 核心编程》 -探索虚拟内存

知道进程工作集大小是极其有用的,因为它可以告诉我们一旦程序到达稳定状态会需要多少内存,将应用程序的工作集减少到最小有助于提高程序性能

14.4 确定地址空间状态

下面函数查询与地址空间中内存地址有关的特定信息(比如大小、存储器类型、保护属性等)。

SIZE_T WINAPI VirtualQuery(
  __in_opt  LPCVOID lpAddress,
  __out     PMEMORY_BASIC_INFORMATION lpBuffer,
  __in      SIZE_T dwLength
);

下面函数可以查询另一个进程的与内存地址相关的特定信息

SIZE_T WINAPI VirtualQueryEx(
  __in      HANDLE hProcess,
  __in_opt  LPCVOID lpAddress,
  __out     PMEMORY_BASIC_INFORMATION lpBuffer,
  __in      SIZE_T dwLength
);
MEMORY_BASIC_INFORMATION 成员结构说明
成员 描述
BaseAddress 它的值等于将参数pvAddress向下取整到页面的大小
AllocationBase 标识出区域的基地址,该区域包含参数pvAddress 所指定的地址
AllocationProtect 标识出最开始预订区域时为该区域指定的保护属性
RegionSize 标识出区域大小(字节),区域起始地址为BaseAddress区域中的所有页面拥有相同的保护属性、状态及类型
State 标识出区域中的页面状态
Proctect 针对所有相邻页面
Type 标识出区域中页面类型

 

《windows 核心编程》 -探索虚拟内存
    //VirtualQuery
    cout<<"-------------------VirtualQuery----------------------"<<endl;
    MEMORY_BASIC_INFORMATION mbi;
    cout<<"Test Addr: "<<0<<endl;
    SIZE_T bufSize = ::VirtualQuery(0,&mbi,sizeof(MEMORY_BASIC_INFORMATION));
    if(bufSize != 0)
        ShowMemBasicInfo(&mbi);
    int nTemp = 1;
    cout<<"Test Addr: "<<"nTemp"<<endl;
    bufSize = ::VirtualQuery(&nTemp,&mbi,sizeof(MEMORY_BASIC_INFORMATION));
    if(bufSize != 0)
        ShowMemBasicInfo(&mbi);

VOID ShowMemBasicInfo(MEMORY_BASIC_INFORMATION * pmbi)
{
    cout<<"BaseAddress:"<<hex<<pmbi->BaseAddress<<endl;
    cout<<"AllocationBase:"<<hex<<pmbi->AllocationBase<<endl;
    cout<<"AllocationProtect:"<<hex<<pmbi->AllocationProtect<<endl;
    cout<<"RegionSize:"<<hex<<pmbi->RegionSize<<endl;
    cout<<"Protect:"<<hex<<pmbi->Protect<<endl;
    cout<<"Type:"<<hex<<pmbi->Type<<endl;
    cout<<"State:"<<hex<<pmbi->State<<endl;
}
《windows 核心编程》 -探索虚拟内存

 


14.4.1 VMQuery 函数
虽然VirtualQuery函数和PMEMORY_BASIC_INFORMATION能帮我们更加深刻的理解Windows内存管理,但现在我知道,它们提供的信息尚不足以让我们透彻的理解。
问题在于PMEMORY_BASIC_INFORMATION 结构并没有返回系统保存在内部的所有信息。如果想了解一些关于某个内存地址的简单信息,那么VirtualQuery就够用了。举个例子,如果想要知道有没有给某个地址调拨物理存储器,或者是否能读取某个内存地址或者能否写入某个内存地址,那么用VirtualQuery正好。但如果想知道某个已预订区域的大小,或者某个区域中的块的数量,或者某个区域是否包含有线程栈,那么光是调用VirtualQuewry是无法得到我们想要的信息的
为了得到更完整的内存信息,作者创建了自己的VMQuery函数:
BOOL VMQuery(HANDLE hProcess,
LPCVOID pvAddress,
PVMQUERY pVMQ);
VMQuery源码见 《windows核心编程》 第394页
 VMQuery 源码下载 
上一篇:谷歌华人用时间晶体解开数十年尘封谜题!永「动」机再登Nature(上)


下一篇:区块链、无人驾驶、量子计算、感知智能……2050 年的技术什么样?