65.9K
CodeProject 正在变化。 阅读更多。
Home

助记符: 辅助您的( 虚拟) 内存

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (10投票s)

2011 年 11 月 24 日

CPOL

4分钟阅读

viewsIcon

38540

downloadIcon

1070

一个用于可视化 Windows 进程使用的虚拟内存的工具。

引言

本文描述了我编写的一个工具,用于以图形和动态的方式显示 Windows 进程对虚拟内存的消耗。

背景

最近,我需要调查一个 Windows 进程消耗虚拟内存 (VM) 的方式。 我想在我的脑海中形成一个关于可用 VM 及其如何被进程分配、释放和映射,以及诸如虚拟内存碎片等现象的画面。

我遇到了 Charles Bailey 的这个工具:http://hashpling.org/asm/。 它运行良好并提供了帮助,但我想要更多关于特定类型分配的信息(那些对应于内存映射文件以及进程加载的托管和非托管程序集),并且我想能够自己理解“幕后”发生的事情。 所以我写了自己的工具。

使用工具

只需运行可执行文件即可。 它需要 .NET Framework 4 客户端配置文件,但不需要其他安装。

从下拉列表中选择一个正在运行的进程。 或者,键入进程的名称(例如“Excel”),并等待它启动; 助记符将自动扫描该进程,只要它在运行。

您将看到这样的屏幕

Screenshot_chrome.png

请注意,图表右侧的“刻度”显示了 3071 MB - 大约 3GB。 这是因为此屏幕截图是在 32 位 Windows 机器上运行的,并带有 /3GB 开关,它将用户模式虚拟地址空间扩展到近 3GB。 在标准 32 位 Windows 环境中,此限制为 2GB,而在 64 位 Windows 下,则为 4GB(因为 Chrome 是一个 Win32 进程)。

请注意右侧的大(大约 1GB)黄色区域,该区域被保留但未提交。 如果您在 32 位 Windows 下运行带有 /3GB 的 Win32 进程,或在 64 位 Windows 下运行,除非您的进程被标记为“大地址感知”,您将看到它。 如果您的进程使用 LARGEADDRESSAWARE 标记,则此区域将被标记为“空闲”(至少最初是这样); 如果没有,Windows 会保留它以防止该进程访问它。

将鼠标悬停在图表上以查看有关分配的详细信息,包括地址范围和在该范围内加载的任何模块(托管或非托管)。

按住 Control 键单击图表以将其另存为文件。 指定文件的根名称(和文件夹); 将追加一个序列号和 .png 扩展名。 只需单击图表即可保存后续快照; 序列号将递增。

Using the Code

该代码由一个可重复使用的用于枚举虚拟内存内容的类组成:VirtualMemoryExplorer,以及一个简单的前端界面,允许选择进程和信息的图形表示。

VirtualMemoryExplorer.Scan 中发生了两个“枚举”循环,它们构建了感兴趣的 VM 区域列表及其特征。

第一个查看 VM 分配

// Use UInt32 so that we can cope with addresses above 2GB in a /3GB 
// or "4GT" environment, or 64-bit Windows
UInt32 address = 0;
for (; ; address = (UInt32)m.BaseAddress + (UInt32)m.RegionSize)
{
    if (0 == VirtualQueryEx(processHandle, (UIntPtr)address, out m, 
                (uint)Marshal.SizeOf(m)))
    {
        // Record the 'end' of the address scale
        // (Expect 2GB in the case of a Win32 process running under 32-bit Windows, 
        // but may be extended to up to 3GB         
        // if the OS is configured for "4 GT tuning" with the /3GB switch
        // Expect 4GB in the case of a Win32 process running under 64-bit Windows)
        addressLimit = address;
        break;
    }
    VMChunkInfo chunk = new VMChunkInfo();
    chunk.regionAddress = (UInt32)m.BaseAddress;
    chunk.regionSize = (UInt32)m.RegionSize;
    chunk.type = (PageType)m.Type;
    chunk.state = (PageState)m.State;
    if ((chunk.type == PageType.Image) || (chunk.type == PageType.Mapped))
    {
        // .Net 4 maps assemblies into memory using the memory-mapped file mechanism;
        // they don't show up in Process.Modules list
        string fileName = GetMappedFileName(processHandle, chunk.regionAddress);
        if (fileName.Length > 0)
        {
            fileName = Path.GetFileName(fileName);
            chunk.regionName = fileName;
        }
    }
    chunkInfos.Add(chunk);
};

第二个查看进程加载的模块 (DLL)。 我使用了 Process.Modules 方法。 如此页面所述,从 .NET 4 开始,此列表不再包含托管程序集 - 仅包含非托管程序集。 (发现托管程序集的唯一方法是结合使用 GetMappedFileName VirtualQueryEx,这在上面的代码片段中完成)。

mappingInfos = new List<VMRegionInfo>();
foreach (ProcessModule module in process.Modules)
{
    VMRegionInfo mappingInfo = new VMRegionInfo();
    mappingInfo.regionAddress = (UInt32)module.BaseAddress;
    mappingInfo.regionSize = (UInt32)module.ModuleMemorySize;
    mappingInfo.regionName = Path.GetFileName(module.FileName);
    mappingInfos.Add(mappingInfo);
}
// Sort by address
mappingInfos.Sort(delegate(VMRegionInfo map1, VMRegionInfo map2)
{
    return Comparer<UInt32>.Default.Compare(map1.regionAddress, map2.regionAddress);
}); 

关注点

我一直努力解决进程选择机制,它使用一个 DropDown。 如果按下下拉按钮,我想显示正在运行的进程列表。 如果在 textbox 中键入名称,我希望助记符在匹配键入名称的进程启动后立即开始扫描。 为了实现我想要的行为,我最终选择在一个后台线程中扫描可用进程。

有趣的是,.NET 应用程序默认情况下未标记为 LARGEADDRESSAWARE,并且主流应用程序(如 Office 2007 套件)也未如此标记。 我有兴趣知道为什么会这样。

我对在托管应用程序中使用内存映射文件(.NET 4 中对此技术有新的支持)的可能性很感兴趣,因此我很高兴看到 CLR 使用内存映射文件来访问它加载到进程中的程序集 (DLL)。 它们在助记符中显示为紫色,并且程序集名称显示在状态栏中。

历史

  • 2011 年 11 月 24 日:第一个版本
  • 2012 年 2 月 9 日:更新了代码,使其在 32 位和 64 位 Windows 下运行,并支持 32 位和 64 位进程
© . All rights reserved.