《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);

示例:

复制代码
#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;
}
复制代码

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

复制代码
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显示分配的内存
    }
复制代码

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

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 标识出区域中页面类型

 

复制代码
    //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;
}
复制代码

 


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 源码下载 
相关文章
|
3月前
|
存储 缓存 Java
释放C盘空间:释放Windows休眠文件和关闭虚拟内存
在 Windows 11 专业版中,可以通过以下步骤来释放休眠文件(Hibernate File),以释放磁盘空间。休眠文件是系统休眠(Hibernate)功能所需要的文件,它保存了系统的当前状态,以便在休眠状态下恢复。如果你不使用休眠功能,如果因为C盘空间不足,可以考虑释放这个文件来腾出磁盘空间。
3683 0
|
3月前
|
C++ Windows
windows下内存检测工具
windows下内存检测工具
|
4月前
|
缓存 C# Windows
一款.NET开源的小巧、智能、免费的Windows内存清理工具 - WinMemoryCleaner
一款.NET开源的小巧、智能、免费的Windows内存清理工具 - WinMemoryCleaner
|
4月前
|
存储 安全 数据安全/隐私保护
3.2 Windows驱动开发:内核CR3切换读写内存
CR3是一种控制寄存器,它是CPU中的一个专用寄存器,用于存储当前进程的页目录表的物理地址。在x86体系结构中,虚拟地址的翻译过程需要借助页表来完成。页表是由页目录表和页表组成的,页目录表存储了页表的物理地址,而页表存储了实际的物理页框地址。因此,页目录表的物理地址是虚拟地址翻译的关键之一。在操作系统中,每个进程都有自己的地址空间,地址空间中包含了进程的代码、数据和堆栈等信息。为了实现进程间的隔离和保护,操作系统会为每个进程分配独立的地址空间。在这个过程中,操作系统会将每个进程的页目录表的物理地址存储在它自己的CR3寄存器中。当进程切换时,操作系统会修改CR3寄存器的值,从而让CPU使用新的页
54 0
3.2 Windows驱动开发:内核CR3切换读写内存
|
5月前
|
存储 安全 API
3.5 Windows驱动开发:应用层与内核层内存映射
在上一篇博文`《内核通过PEB得到进程参数》`中我们通过使用`KeStackAttachProcess`附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这次我们将实现一个更加有趣的功能,在某些情况下应用层与内核层需要共享一片内存区域通过这片区域可打通内核与应用层的隔离,此类功能的实现依附于MDL内存映射机制实现。
57 0
3.5 Windows驱动开发:应用层与内核层内存映射
|
5月前
|
安全 Windows
3.3 Windows驱动开发:内核MDL读写进程内存
MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式。在Windows操作系统中,每个进程都有自己独立的虚拟地址空间,不同进程之间的内存空间是隔离的。因此,要在一个进程中读取或写入另一个进程的内存数据,需要先将目标进程的物理内存映射到当前进程的虚拟地址空间中,然后才能进行内存读写操作。
41 0
3.3 Windows驱动开发:内核MDL读写进程内存
|
5月前
|
安全 PHP 网络虚拟化
windows内存取证-简单
作为 Security Blue 团队的成员,您的任务是使用 Redline 和 Volatility 工具分析内存转储。您的目标是跟踪攻击者在受感染计算机上采取的步骤,并确定他们如何设法绕过网络入侵检测系统“NIDS”。您的调查将涉及识别攻击中使用的特定恶意软件系列及其特征。此外,您的任务是识别和缓解攻击者留下的任何痕迹或足迹。
|
5月前
|
安全 文件存储 数据安全/隐私保护
windows内存取证-中等难度-下篇
承接上文,我们继续学习
|
6月前
|
存储 安全 数据安全/隐私保护
Windows内存取证-中等难度 -上篇
此篇文章,主要学习到windows内存取证知识,此次学习将有3个场景,涉及内网横向域控等
|
6月前
|
存储 缓存 分布式数据库
[笔记]Windows核心编程《十七》内存映射文件(二)
[笔记]Windows核心编程《十七》内存映射文件(二)

相关课程

更多