14

我正在使用 VMMap 分析混合模式(托管和非托管)应用程序中的虚拟/进程地址空间利用率。我了解 Windows VMM 和虚拟内存 API 的工作原理,我也了解堆内存 API 的工作原理。我已经查看了我正在使用的 CRT 实现(不是很详细)并且(我想我 - 这可能是我的失败)了解它如何使用上述 Win32 API。

我希望了解这个“私人数据”统计数据向我展示了什么。我的应用程序没有直接调用任何 Win32 内存 API 函数,它只在本机 C++ 中使用“malloc/new”,在 C# 中使用“new”(深入使用 Win32 内存管理 API)。

VMMap 给出的“Private Data”的定义是:

私有内存是由 VirtualAlloc 分配的内存,而不是由堆管理器或 .NET 运行时分配的内存。它不能与其他进程共享,根据系统提交限制收费,并且通常包含应用程序数据。

所以我想这个定义让我问,好吧,那么是谁在调用 VirtualAlloc?是堆管理器还是 .Net 运行时?

我可以获取一些已提交的私有数据的地址并使用 WinDbg 来找出......嗯......事实证明,微软在他们的智慧中已经提高了 ntdll 公共符号,所以 WinDbg 不能很好地工作 - 我如果需要,可以提供更多详细信息,但基本上像 !address -summary 这样的命令由于缺少符号而不再起作用。

这个问题的另一种说法可能是:我可以编写哪些 C++ 或 C# 代码来导致该私有数据统计量增加或减少?还是这一切都由操作系统、C++ 运行时或 .Net 运行时管理,因此任由其一时兴起?

我可以从 VMMap 的性质(其他内存类型是互斥的)推断出这种“私有数据”,因此不能是以下任何类型的地址空间:

  • 堆(请注意,这包括已提交和保留的堆空间 - 通过调用 VirtualAlloc 保留,如上面私有数据的描述中所述)。
  • 托管堆
  • 可分享
  • 映射文件
  • 图片
  • 页表
  • 无法使用
  • 自由的

(我找不到定义 VMMap 认为上述所有类型的在线帮助文​​件,但这里有一个下载帮助文件的链接:https ://technet.microsoft.com/en-us/library/dd535533.aspx )

我注意到在我的应用程序中,私有数据的总(保留和提交)大小在我的应用程序生命周期中保持相当稳定,尽管堆/托管堆/堆栈大小按预期变化。我还注意到,在私有数据使用的约 250Mb 总量中,实际上只有约 33Mb 被提交。请注意,我的测量方法相当初级,因此值可能在我的每次测量之间发生变化,而我只是没有看到它(如果我知道这是在测量什么,我可以使用 DebugDiag 来获取进程的转储相关计数器达到一定的阈值,鸡和蛋)。

我目前的推测理论是,这是保留的空间,用于在本地(或我想是管理的?)堆达到其容量时增长,但我没有任何证据可以证明这一点。所以它仍然牢牢地留在投机堆中。

在互联网上搜索这方面的详细信息可能会很痛苦,那里有很多令人困惑的帖子/文章/博客,使用自引用定义(Performance Monitor 对工作集的定义的第一句就是一个很好的例子),是不完整或完全错误。许多地方模糊了定义或使用了不一致的术语(请注意,VMMaps 对字段私有数据的定义,继续将其称为私有内存,也许有点像肛门抱怨,但模棱两可)。

既然我已经批评互联网的其他部分让事情变得混乱和不正确...... ,让我知道,我也会把自己列入罪犯名单!我认为尝试在网上向某人解释记忆问题的前半部分是确保我们都在谈论同一件事。

最后这个问题:VMMap 如何知道给定的内存区域是线程堆栈,具体来说?建议我可能永远找不到答案:/

更新/编辑:我发现通过打开 gflags 用户堆栈跟踪(gflags -i myapp.exe +ust),您可以增加私有数据的大小,我假设这是回溯数据库,但即使没有 gflags ,仍然有我正在努力解释的私人数据。

4

2 回答 2

5

我知道这是一个很老的问题。但由于某种原因,它仍然没有得到答复。Sasha Goldstein 在 DotNext 会议上谈论 WinDbg 时提到了这个问题 - https://www.youtube.com/watch?v=8t1aTbnZ2CE。关键是它可以在 WinDbg 的帮助下轻松回答。

为了回答 CLR 是否使用 VirtualAlloc 作为其堆,我们将在此函数处设置一个断点,并使用一个打印当前堆栈(本机和托管)的脚本。

bp kernelbase!VirtualAlloc ".printf \"allocating %d bytes of virtual memory\", dwo(@esp+8);.echo;  k 5; !clrstack; gc"

此处:k 5打印本机调用堆栈的最后 5 帧并!clrstack(来自 SOS)打印托管堆栈。gc继续执行。

请注意,此脚本仅适用于 x86 进程。对于 x64,您将需要其他一些(注册表和调用约定不同)。

然后我创建了一个简单的程序,它分配一个对象并将其添加到一个列表中。

    static void Main(string[] args)
    {
        var list = new List<string[]>();
        while (true) {
            var a = Console.ReadLine();
            if (a == "q" || a == "Q") break;
            var arr = new string[100];
            list.Add(arr);
        }
    }

在 WinDbg 下运行它并开始按 Enter。在某些时候断点命中 - 列表扩展并在堆中分配额外的内存:

在此处输入图像描述

所以很明显,CLR 使用 VirtualAlloc 为其堆分配内存。

于 2018-02-15T22:53:48.477 回答
1

进程的折叠 VMMap 视图显示所有 VAD 条目。可以使用kd/ windbg/查看 VAD livekd

让我们来看看calc.exe

在此处输入图像描述

lkd> !process 0n12876
PROCESS fffffa802e058600
    SessionId: 1  Cid: 324c    Peb: 7fffffdf000  ParentCid: 0bec
    DirBase: 22211000  ObjectTable: fffff8a00e9b1310  HandleCount:  85.
    Image: calc.exe
    VadRoot fffffa8039b76500 Vads 176 Clone 0 Private 1852. Modified 1. Locked 0.
.
.

lkd> !vad fffffa8039b76500
VAD             level      start      end    commit
.
.
fffffa803c9da680 ( 6)      ff7b0    ff892         6 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\calc.exe
.
.

VAD 只有一个范围,并显示出虚假保护,EXECUTE_WRITECOPY尽管我认为这确实一般性地描述了所有部分,因此允许其中的部分是 CoW 或只读和可执行的。VMMap 试图提供更多信息,不仅显示图像子部分对象,还显示这些子部分内的不同保护范围。例如,它显示了 1.data个小节内的所有保护范围。它最初是 5 份在写入页面上的副本,现在 3 份已被读/写 PTE 替换,2 份仍然保持原样。

中有一个私有页面.rdata,并且有读/写和 CoW.data页面private。的私有部分将.rdata是包含导入地址表(IAT)的页面,因为它是由加载器修改的,并且对于每个进程都是不同的。因此,这也是在写入页面上制作的副本,然后被写入,现在它已被加载器只读。它不需要是可执行的,因为它是使用相对内存间接跳转或调用来访问的。

所有其他部分都有文件支持并在进程之间共享。可共享工作集是可以被其他进程映射入的工作集,共享工作集是至少被其他进程实际映射入的工作集,是可共享WS的子集。

Private意味着数据是由进程修改的,它只与该进程相关,与其他进程无关,因此不会写回共享映射文件以供其他进程查看。因此,它必须创建一个页面,该页面将存储在页面文件中,而不是在需要分页时写回文件。这方面的一个例子是 IAT,其中每个图像将具有不同的导入地址,具体取决于加载的模块在进程地址空间中的位置,这因进程而异,并且在另一个进程的上下文中没有任何意义。

另一个示例private可能是在图像未在其首选基址加载并且代码中有绝对地址的情况下需要修复的代码部分的一部分。这些页面必须在写入时分配副本,然后执行/读取 - 在此示例中没有。

还需要注意的是,这size意味着进程已保留的虚拟内存。例如HeapCreate为堆保留一些内存,它将有一个 VAD 条目,并将 VAD 条目标记的保留的完整大小添加到size(并且 VAD 中的块的大小通过其他方式计算,例如分析 PTE他们自己)。然后在您调用时提交此内存HeapAlloc即实际分配了 PTE,这意味着 PDE 也被分配了一个物理页面,以便 PTE 可以实际更改。对于特定范围,PTE 的需求为零。这些现在是“承诺的”。当您实际写入地址时,PTE 将被分配一个归零的物理页面,并且 PTE 将成为指向该物理页面的有效硬件 PTE,现在该页面是进程工作集的一部分,直到它从工作集。

private是私有的虚拟提交(在页面中,即 4KiB 粒度),private WS然后显示由 PTE 表示的私有虚拟提交页面的数量,这些 PTE 实际分配了物理页面作为进程工作集的一部分。它是工作集中物理页面的逻辑分类。total WSis ,即它是进程的private WS + shareable WS工作集。

映射图像的大小总是与其提交的大小相同,但映射的文件和部分并不总是——它们可以像堆一样保留块。在映射文件的上下文中保留意味着该区域还没有 PTE。您通常映射整个图像部分的视图,但是对于数据文件,我看到了映射完整文件的情况,但 VAD 条目的保留空间比文件大得多——我不确定如何去做这个。映射的大小在 VAD 中保留,然后分配给它们包含 PTE 的物理页面,以便可以同时填充它们的 PTE 以指向正确的原型 PTE (PPTE),这些原型 PTE (PPTE) 已创建(但未分配物理页面)在图像文件的情况下创建该部分时,. 当文件实际被访问时,给PTE一个物理页来指向,它成为一个有效的硬件PTE。这将只是将指针复制到 PPTE 从 PPTE 指向的物理页面(如果 PPTE 不指向一个,即它是 a MMPTE_SUBSECTION,那么它会被分配并填充 IO 读取到文件,或者它是如果原型 PTE 是页面文件 PTE,则分页)。

保留只是 VAD 中的保留,但提交意味着现在有软件形式的 PTE(即它们是无效的),这意味着有一些额外的提交费用,因为需要在 PDE 中分配物理页面 / PDPT / PML4 即 PTE 页面,这样 PTE 可以实际写入。这个特定的提交费用不会出现在进程的工作集中(工作集提交费用),分页/非分页池提交费用或修改/备用列表提交费用或页面文件提交费用也不显示。

默认情况下,只读部分是共享部分(除了需要由链接器修改的页面,因为它们暂时变得可写),但图像中启用写入的部分默认情况下不共享,因此它是写入时复制,并且每个进程都有不同的数据段副本,在发生缺页时替换它(发生缺页是因为CoW页面无效),并且在需要分页时将其分页到页面文件中。如果您在图像的节标题特性中将其指定为共享节,则它不会在写入页面上分配为副本,只是读/写,并且所有对它的写入都将直接写入映射的图像,并且会被所有已映射它的进程。我相信这最终会写回图像,即它是文件支持的。

CoW 页面虚拟提交被列为私有的事实很有趣。我认为这个页面本身是共享的和文件支持的,只有替换页面是私有的。在这种情况下,CoW 页面也不在工作集上,但在我的情况下,chrome.exe它只是在可共享的工作集上,如您所料,尽管虚拟提交仍被列为私有,但至少它是在可共享工作集上而不是在私有工作集上,这将是优化失败,因为它应该并且能够被共享。这是另一个例子:

在此处输入图像描述

这就引出了如何确定虚拟提交的隐私性的问题。在这种情况下,它似乎将指向 CoW PPTE 的提交 PTE 分类为私有,因为当页面被写入并交换为替代页面副本时,它最终将是私有的。这是误导性的,尽管不是一个有形的问题(表明它是私有工作集的一部分将是一个有形的问题)。至于.rdata,它知道第一页在原始提交中不是私有时是私有的(原始提交从提交 PTE 指向的 PPTE 的保护中确定私有性,这些 PTE 是使用图像部分填写的),但奇怪的是是它不包括图像的私有页面中的 4K 页面(它显示 20K 而不是 24K),但在总数中包含 CoW 私有页面。你会认为它会读取 PPTE/PTE 以确定什么是私有提交,什么不是——当前提交与原始提交不同,因为加载程序已将只读(共享)PTE 更改为 CoW PTE和PPTE,然后它写入它并使 PTE 只读(但 PPTE 仍然是 CoW 并保留保护,即使它指向的物理页面因文件备份和只读而被丢弃(现在可能随着引用计数减少),即它不会从图像文件中重新获得保护)。当页面被写入时,新页面将不再涉及或再次指向PPTE,它现在是pagefile backed,物理页面分配的PFN条目不指向 PPTE。出于这个原因,共享部分是文件支持的,因为它与 PPTE 相关联,就像 CoW 页面一样,图像中的只读页面也是如此,但分配的页面与 PPTE 无关(页面 PFN不指向 PPTE)是页面文件支持的,无论它是设置为读/写还是读(在 IAT 的情况下为读)。

在任务管理器中,缓存 = 待机 + 修改,可用 = 空闲 + 待机。提交费用是使用的物理内存量(RAM + 页面文件)(用于提交本身(在工作集、页面文件、已修改、备用),或用于支持实际提交但不属于提交范围的结构本身,或分页/非分页池等)。

于 2021-03-21T21:15:18.130 回答