Что определяет "Частные данные" в VMMAP?

Я использую VMMap для анализа использования виртуального / адресного пространства процесса в моем приложении смешанного режима (управляемого и неуправляемого). Я понимаю, как работает Windows VMM и API виртуальной памяти, я понимаю, как работает API Heap Memory. Я посмотрел на реализацию CRT, которую я использую (не очень подробно), и (думаю, я - это может быть моим упадком), чтобы понять, как она использует вышеупомянутые Win32 API.

Я пытаюсь понять, что показывает эта статистика "Личные данные". Мое приложение не обращается напрямую ни к одной из функций API памяти Win32, оно всегда использует "malloc/new" в нативном C++ и "new" в C# (в глубине души будет использоваться Win32 API управления памятью).

Определение "Частные данные", данное VMMap:

Частная память - это память, выделенная VirtualAlloc и не распределенная ни диспетчером кучи, ни средой выполнения.NET. Он не может использоваться совместно с другими процессами, взимается с системного лимита и обычно содержит данные приложения.

Итак, я думаю, это определение заставляет меня спросить, хорошо, так кто же звонит в VirtualAlloc? Это менеджер кучи или.Net время выполнения?

Я мог бы получить адрес некоторых конфиденциальных данных и использовать WinDbg, чтобы выяснить.... Ну... получается, что Microsoft в своей мудрости добавила публичные символы ntdll, так что WinDbg не работает так хорошо - я может предоставить более подробную информацию об этом, если требуется, но в основном такие команды, как! address -summary, больше не работают из-за пропущенных символов.

Другой способ сформулировать этот вопрос: какой код C++ или C# можно написать, что приведет к увеличению или уменьшению статистики личных данных? Или все это управляется ОС, средой выполнения C++ или средой.Net и, следовательно, зависит от ее прихотей?

Я могу сделать вывод из природы VMMap (другие типы памяти являются ИСКЛЮЧИТЕЛЬНЫМИ КАЖДОГО ДРУГОГО), что эти "личные данные", следовательно, не могут быть следующими типами адресного пространства:

  • Куча (обратите внимание, что это включает выделенное и зарезервированное пространство кучи - зарезервированное посредством вызова VirtualAlloc, как описано в описании личных данных выше).
  • Управляемая куча
  • стек
  • Разделяется
  • Сопоставленный файл
  • Образ
  • Таблица страниц
  • непригодный
  • Свободно

(Я не смог найти файл интерактивной справки, который определяет, что VMMap считает всеми перечисленными выше типами, но вот ссылка для загрузки файла справки: https://technet.microsoft.com/en-us/library/dd535533.aspx)

Я заметил, что в моем приложении ОБЩИЙ (зарезервированный и фиксированный) размер личных данных остается довольно постоянным на протяжении всего срока службы моих приложений, несмотря на то, что размеры кучи / управляемой кучи / стека изменяются, как и ожидалось. Я также заметил, что из общего объема ~250 МБ, используемого частными данными, фактически выделяется только ~33 МБ. Обратите внимание, что мой метод измерения это довольно элементарный, поэтому значение может меняться между каждым из моих измерений, и я просто не вижу его (если бы я знал, что это измеряется, я мог бы использовать DebugDiag, чтобы получить дамп процесса, когда соответствующий счетчик достиг определенного порога (курица и яйцо).

Моя текущая спекулятивная теория состоит в том, что это пространство, которое резервируется для роста собственных (или, я полагаю, управляемых) кучи, когда они достигают своих возможностей, но у меня нет ничего, чтобы доказать это. Так что он твердо остается в спекулятивной куче.

Поиск в Интернете подробностей об этом может быть болезненным, есть много публикаций / статей / блогов, которые путают вещи, используют самостоятельные ссылочные определения (первое предложение определения Performance Monitor для рабочего набора является отличным примером этого), неполные или просто неправильно. Во многих местах размыты определения или используется противоречивая терминология (обратите внимание, что определение VMMaps поля приватных данных продолжает называть его приватной памятью, возможно, немного анальной жалобой, но неоднозначностью).

Теперь, когда я раскритиковал остальную часть интернета за то, что все запутано и некорректно... если в вышеприведенном есть что-то, что не имеет смысла, или вы можете показать мне документацию об обратном, или вам нужно более четкое определение, дайте мне знать, и я тоже внесу себя в список правонарушителей! Я думаю, что первая половина попытки объяснить кому-то проблему с памятью в Интернете - убедиться, что мы все говорим об одном и том же.

И наконец, вопрос: как VMMap узнает, что конкретная область памяти является стеком, особенно? предполагает, что я никогда не узнаю ответ: /

ОБНОВЛЕНИЕ / РЕДАКТИРОВАНИЕ: я обнаружил, что, включив трассировку стека пользователей gflags (gflags -i myapp.exe +ust), вы можете увеличить размер личных данных, я бы предположил, что это база данных с обратным следом, но там даже без gflags Есть еще частные данные, которые я изо всех сил пытаюсь объяснить.

2 ответа

Я знаю, что это довольно старый вопрос. Но это по-прежнему без ответа по какой-то причине. Этот вопрос упоминал Саша Гольдштейн в своем выступлении о WinDbg на конференции DotNext - 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 для выделения памяти для своей кучи.

В свернутом представлении VMMap для процесса отображаются все записи VAD. VAD можно просмотреть с помощью kd / windbg / 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 подраздела показаны все диапазоны защиты в этом разделе. Первоначально это было 5 копий на страницах записи, теперь 3 были заменены PTE для чтения / записи, а 2 остались нетронутыми.

Есть личная страница, а также страницы для чтения / записи и CoW. .dataнаходятся . Приватной частью будет страница, содержащая таблицу адресов импорта (IAT), потому что она изменяется загрузчиком и различается для каждого процесса. Следовательно, эта копия была сделана и на странице записи, затем она была записана, и теперь она сделана загрузчиком только для чтения. Он не обязательно должен быть исполняемым, потому что доступ к нему осуществляется с помощью косвенного перехода или вызова в относительной памяти .

Все остальные разделы имеют файловую поддержку и используются совместно между процессами. Совместно используемый рабочий набор - это рабочий набор, который может быть отображен другими процессами, а общий рабочий набор - это рабочий набор, который фактически отображается хотя бы одним другим процессом и является подмножеством совместно используемого WS.

Privateозначает, что данные изменяются процессом, и они актуальны только для этого процесса, а не для других, и поэтому не записываются обратно в совместно используемый сопоставленный файл для просмотра другими процессами. Следовательно, он должен создать страницу, которая будет храниться в файле подкачки, а не записываться обратно в файл, когда ее нужно выгрузить. Одним из примеров этого является IAT, где каждое изображение будет иметь разные адреса импорта в зависимости от того, где загруженные модули находятся в адресном пространстве процесса, которое варьируется от процесса к процессу и ничего не значит в контексте другого процесса.

Другим примером a могут быть части раздела кода, требующие исправлений в случае, если изображение не загружено на его предпочтительную базу и в коде есть абсолютные адреса. Этим страницам нужно будет выделить копию при записи, а затем выполнить выполнение / чтение - в этом примере их нет.

Также важно отметить, что это означает виртуальную память, зарезервированную процессом. Например HeapCreate резервирует некоторую память для кучи, в которой будет запись VAD, и полный размер резервирования, отмеченный записью VAD, добавляется к size(а размер блоков в VAD рассчитывается с помощью других средств, таких как анализ самих PTE). Эта память затем фиксируется, когда вы вызываете HeapAllocт.е. элементы PTE фактически назначаются, то есть элементам PDE также выделяется физическая страница, так что элементы PTE могут быть фактически изменены. PTE устанавливаются без спроса для определенного диапазона. Теперь они «совершены». Когда вы действительно пишете по адресу, PTE будет выделена обнуленная физическая страница, и PTE будет преобразован в допустимый аппаратный PTE, который указывает на эту физическую страницу, и теперь страница является частью рабочего набора процесса до тех пор, пока она не будет обрезана с рабочий набор.

private - это виртуальная фиксация, которая является частной (на странице, т.е. с детализацией по 4 КБ), а private WSзатем показывает количество частных виртуальных страниц фиксации, представленных PTE, для которых фактически выделена физическая страница как часть рабочего набора процесса. Это логическая категоризация физических страниц в рабочем наборе. В total WS является private WS + shareable WS, т.е. это рабочий набор процесса.

Сопоставленные изображения всегда имеют тот же размер, что и их фиксация, но сопоставленные файлы и разделы не всегда - они могут иметь зарезервированные блоки, такие как куча. Зарезервировано в контексте сопоставленных файлов означает, что для этого региона еще нет PTE. Обычно вы сопоставляете представление всего раздела изображения, но с файлом данных я видел случаи, когда был отображен полный файл, но запись VAD имела зарезервированное пространство, намного превышающее размер файла - я не уверен, как сделать это. Размер сопоставления зарезервирован в VAD, а затем им выделяются физические страницы, содержащие PTE, так что их PTE могут быть одновременно заполнены, чтобы указывать на правильные прототипы PTE (PPTE), которые были созданы (но не выделены физические страниц) при создании раздела в случае файлов изображений,и когда представление было отображено в случае файлов данных. При фактическом доступе к файлу PTE предоставляется физическая страница, на которую он должен указывать, и он становится действительным аппаратным PTE. Это будет просто копирование указателя на физическую страницу, на которую указывает PPTE, из PPTE (и если PPTE не указывает на одну, т. Е. MMPTE_SUBSECTION, затем он выделяется и заполняется вводом-выводом, прочитанным в файл, или выгружается, если прототипом PTE является PTE файла подкачки).

Резервирование - это просто резервирование в VAD, но фиксация означает, что теперь для него есть PTE, которые являются программными формами (т.е. они недействительны), что означает, что существует некоторая дополнительная плата за фиксацию, потому что физические страницы должны быть выделены в PDE / PDPT / PML4, т. Е. Страницы PTE, на которые можно действительно записывать PTE. Эта конкретная плата за фиксацию не отображается в рабочем наборе процесса (плата за фиксацию рабочего набора), также как плата за фиксацию выгружаемого / невыгружаемого пула, плата за фиксацию измененного / резервного списка или плата за фиксацию файла подкачки.

Раздел только для чтения по умолчанию является общим разделом (кроме страниц, которые должны быть изменены компоновщиком, поскольку они на мгновение становятся доступными для записи), но раздел с возможностью записи в изображении по умолчанию не является общим, поэтому он копируется при записи , и каждый процесс имеет другую копию раздела данных, которая заменяет его при возникновении ошибки страницы (ошибка страницы возникает из-за того, что страницы CoW недействительны), и она выгружается в файл страницы, когда ее необходимо выгрузить. Если вы укажете его как общий раздел в характеристиках заголовка раздела в изображении, тогда он не будет выделен как копия на странице записи, а будет только чтение / запись, и все записи в него будут записываться непосредственно в сопоставленное изображение и будут видны все процессы, которые его сопоставляли. Я считаю, что это заканчивается обратной записью в изображение, т. е. с файловой поддержкой.

Интересен тот факт, что виртуальная фиксация страницы CoW указана как закрытая. Я бы подумал, что эта страница является общей и файловой, только замещающая страница является частной. Страница CoW в данном случае тоже находится не в рабочем наборе, а в моем chrome.exeэто так, и это все и только в совместно используемом рабочем наборе, открытом для чтения, как и следовало ожидать, хотя виртуальная фиксация по-прежнему указана как частная, но, по крайней мере, она находится в совместно используемом рабочем наборе, а не в частном рабочем наборе , что было бы ошибкой оптимизации, потому что это должно быть и может быть опубликовано. Вот еще один пример:

Возникает вопрос, как определяется конфиденциальность виртуального коммита. В этом случае он, по-видимому, классифицирует PTE фиксации, которые указывают на PPTE CoW, как частные, потому что в конечном итоге он станет частным, когда страница будет записана и заменена на заменяющую копию страницы. Это вводит в заблуждение, хотя и не является ощутимой проблемой (показать, что он является частью частного рабочего набора, было бы ощутимой проблемой). Для .rdata, он знает, что первая страница является частной, когда она не является частной в исходной фиксации (исходная фиксация определяет конфиденциальность по защите PPTE, на которые указывают PTE фиксации, которые были заполнены с использованием разделов изображений), но что странно заключается в том, что он не включает эту страницу 4K в частную общую сумму для изображения (она показывает 20K, а не 24K), но включает частные страницы CoW в общую сумму. Вы могли подумать, что он прочитает PPTE/PTE, чтобы определить, что является частным фиксацией, а что нет --- текущая фиксация отличается от исходной фиксации, потому что загрузчик изменил PTE только для чтения (общий) на CoW PTE и PPTE,который затем записывает в PTE и делает его доступным только для чтения (но PPTE остается CoW и сохраняет защиту, даже когда физическая страница, на которую он указывает, отбрасывается из-за резервной копии файла и только для чтения (и теперь это может быть, поскольку счетчик ссылок уменьшился), т.е. он не восстанавливает защиту от файла изображения). Когда страница записана, новая страница больше не будет задействовать PPTE или указывать на него снова, и теперь она поддерживается файлом подкачки, а выделенная физическая страница Запись PFN не указывает на PPTE. По этой причине общий раздел поддерживается файлом, потому что он связан с PPTE, как страница CoW и как страница только для чтения в изображении, но выделенная страница не связана с PPTE (страница PFN не указывает на PPTE) поддерживается файлом подкачки, независимо от того, настроено ли это на чтение / запись или чтение (чтение в случае IAT).

В диспетчере задач кэширование = ожидание + модифицированное, а доступное = свободное + ожидание. Плата за фиксацию - это объем физической памяти (ОЗУ + файл подкачки), который используется (либо для самой фиксации (в рабочем наборе, файле подкачки, измененный, резервный), либо для структур, поддерживающих фактическую фиксацию, но не являющихся частью диапазона фиксации. сам или выгружаемый / невыгружаемый пул и т. д.).

Другие вопросы по тегам