Дескрипторы объекта и таблица дескрипторов процесса Windows
Когда процесс создает или открывает объект по его имени, он получает дескриптор, дающий ему доступ к объекту. Ссылаться на объект по его дескриптору быстрее, чем использовать его имя, поскольку диспетчер объектов может не заниматься поиском по имени и находить объект напрямую.
Процессы могут также получать дескрипторы объектов путем наследования дескрипторов во время создания процесса (когда создатель устанавливает флаг наследования дескрипторов при вызове функции CreateProcess, и дескриптор помечен как наследуемый либо в процессе своего создания, либо после создания путем использования Windows-функции SetHandleInformation) или путем получения продублированного дескриптора от другого процесса (см. Windows-функцию DuplicateHandle).
Все процессы пользовательского режима должны иметь дескриптор объекта, прежде чем их потоки смогут использовать объект. Использование дескрипторов для управления системными ресурсами — идея не новая.
Например, библиотеки времени выполнения C и Pascal (более старого языка программирования, аналогичного Delphi) возвращают дескрипторы открытых файлов. Дескрипторы служат в качестве косвенных указателей на ресурсы системы; эта косвенность не дает прикладным программам напрямую манипулировать структурами системных данных.
ПРИМЕЧАНИЕ. Компоненты исполняющей системы и драйверы устройств могут обращаться к объектам непосредственно, поскольку они запущены в режиме ядра и поэтому имеют доступ к структурам объекта в системной памяти. Но они должны объявить о своем использовании объекта, увеличив значение счетчика ссылок, чтобы объект не мог быть удален из памяти, пока он все еще используется.
Однако для успешного использования объекта драйверы устройств должны знать определение внутренней структуры объекта, а для многих объектов она не предоставляется. Взамен драйверам устройств рекомендуется использовать соответствующие API-функции ядра для изменения или чтения информации из объекта. Например, хотя драйверы устройств могут получить указатель на объект типа «процесс» (EPROCESS), его структура им не известна, и должны быть использованы API-функции вида Ps*.
Для других объектов непрозрачен сам тип (это касается большинства объектов исполняющей системы, в которые заключен диспетчер объектов, в качестве примеров можно привести события или мьютексы). Для таких объектов драйверы должны использовать такие же системные вызовы, которые в конечном итоге используются приложениями пользовательского режима (такие как ZwCreateEvent), и использовать дескрипторы, а не указатели на объекты.
Дескрипторы объектов дают дополнительные преимущества. Во-первых, за исключением того, к чему они относятся, нет никакой разницы между дескрипторами файла, события и процесса. Эта схожесть обеспечивает единый интерфейс для ссылки на объекты, независимо от их типа. Во-вторых, диспетчер объектов имеет исключительное право на создание дескрипторов и на определение местоположения объекта, который относится к дескриптору. Это означает, что диспетчер объектов может проверять каждое действие в пользовательском режиме, влияющее на объект, чтобы убедиться в том, что профиль безопасности вызывающей программы разрешает проводить запрашиваемую операцию над данным объектом.
Просмотр открытых дескрипторов.
Запустите Process Explorer и убедитесь, что нижняя панель включена и настроена на показ открытых дескрипторов. (View (Вид) — Lower Pane View (Просмотр нижней панели) — Handles (Дескрипторы)). После этого откройте окно командной строки и просмотрите таблицу дескрипторов для нового процесса Cmd.exe. Вы должны увидеть дескриптор открытого файла для текущего каталога. Например, предположим, что текущим является каталог C:\Users\Administrator, тогда Process Explorer покажет следующее.

Теперь поставьте Process Explorer на паузу, нажав клавишу Пробел или щелкнув на пунктах View (Вид) — Update Speed (Изменить скорость) — Pause (Пауза).
Затем измените текущий каталог с помощью команды cd и нажмите клавишу F5, чтобы обновить отображаемую информацию. Вы увидите в Process Explorer, что дескриптор предыдущего текущего каталога закрыт и открыт новый дескриптор для нового текущего каталога. Предыдущий дескриптор выделен красным цветом, а новый дескриптор выделен зеленым цветом.
Свойство выделения разным цветом, имеющееся в Process Explorer, делает заметнее изменения в таблице дескрипторов. Например, если процесс допускает утечку дескрипторов, просмотр таблицы дескрипторов с помощью Process Explorer может быстро показать, какой дескриптор или какие дескрипторы были открыты, но не были закрыты. (Обычно виден длинный список дескрипторов для одного и того же объекта.) Эта информация поможет программисту обнаружить утечку дескрипторов.
Монитор ресурсов также показывает открытые (именованные) дескрипторы для процессов, выбранных путем установки флажков напротив их имен. Вот как выглядят дескрипторы открытого окна командной строки.

Таблицу открытых дескрипторов можно также вывести, используя средство командной строки Handle из серии программных продуктов Sysinternals.
Посмотрите, к примеру, на следующий, частично показанный вывод, полученный с помощью средства Handle при изучении дескрипторов файловых объектов, находящихся в таблице дескрипторов для процесса Cmd.exe до и после изменения каталога. По умолчанию Handle отфильтровывает нефайловые дескрипторы, пока не будет использован ключ –a, который приводит к выводу всех дескрипторов в процессе, аналогично Process Explorer.
C:\>handle -p cmd.exe
Copyright (C) 1997-2011 Mark Russinovich
cmd.exe pid: 5124 Alex-Laptop\Alex Ionescu
3C: File (R-D) C:\Windows\System32\en-US\KernelBase.dll.mui
C:\Windows>handle -p cmd.exe
Copyright (C) 1997-2011 Mark Russinovich
cmd.exe pid: 5124 Alex-Laptop\Alex Ionescu
3C: File (R-D) C:\Windows\System32\en-US\KernelBase.dll.mui
40: File (RW-) C:\Windows
Дескриптор объекта является индексом в таблице дескрипторов, относящейся к конкретному процессу. Этот индекс указывается исполнительным блоком процесса (EPROCESS). Первый индекс дескриптора имеет значение 4, второй — 8 и т. д. Таблица дескрипторов процесса содержит указатели на все объекты, которые процесс открыл для своей работы.
Таблицы дескрипторов реализованы по древовидной схеме, подобной той, которую реализует блок управления памятью x86 для перевода виртуальных адресов в физические, которая дает максимальное значение, превышающее 16 000 000 дескрипторов на процесс.
ПРИМЕЧАНИЕ. В древовидной схеме таблиц таблица верхнего уровня может содержать страницу, заполненную указателями на таблицы среднего уровня, что позволяет иметь более половины миллиарда дескрипторов. Но чтобы поддержать совместимость со схемой дескрипторов, имевшейся в Windows 2000, и унаследовать ограничение в 16 777 216 дескрипторов, таблица верхнего уровня содержит не более 32 указателей на таблицы среднего уровня, устанавливая для более новых версий Windows тот же предел.
При создании процесса выделяется только таблица дескрипторов самого низкого уровня, другие уровни создаются по мере необходимости. Таблица дескрипторов нижнего уровня состоит из такого количества записей, которое может поместиться на странице минус одна запись, которая используется для контроля дескрипторов.
Например, для систем x86 страница составляет 4096 байт и поделена на записи таблицы дескрипторов размером 8 байт, которых получается 512 минус 1, то есть всего 511 записей в таблице дескрипторов самого низкого уровня. Таблица дескрипторов среднего уровня содержит полную страницу указателей на таблицы нижнего уровня, поэтому количество таблиц дескрипторов нижнего уровня зависит от размера страницы и размера указателя для платформы. Схема таблицы дескрипторов в системе Windows показана на следующем рисунке.

Создание максимального количества дескрипторов.
Тестовая программа Testlimit из коллекции Sysinternals предоставляет возможность открывать дескрипторы объекта до тех пор, пока она не сможет больше открыть ни одного дополнительного дескриптора. Этим можно воспользоваться, чтобы посмотреть, сколько дескрипторов может быть создано отдельно взятым процессом на вашей системе. Поскольку под таблицы дескрипторов выделяется место в выгружаемом пуле памяти, до достижения максимального количества дескрипторов, доступного для создания в рамках отдельно взятого процесса, вы можете выйти за рамки выгружаемого пула.
Чтобы увидеть, сколько дескрипторов можно создать на вашей системе, выполните следующие действия:
- Загрузите с адреса http://live.sysinternals.com/WindowsInternals исполняемый файл Testlimit, необходимой вам версии для 32- или 64-разрядной версии Windows.
- Запустите программу Process Explorer, щелкните на пункте View (Вид) — System Information (Информация о системе) — Memory (Память). Обратите внимание на текущий и максимальный размер выгружаемого пула.
Чтобы показать максимальный размер пула, Process Explorer должен быть правильно настроен на доступ к символам образа ядра, Ntoskrnl.exe. Оставьте это отображение системной информации работающим, чтобы можно было следить за использованием пула при запуске программы Testlimit.
- Откройте окно командной строки.
- Запустите программу Testlimit с ключом –h (путем ввода команды testlimit –h). Когда программа Testlimit не сможет открыть новый дескриптор, она покажет общее количество тех дескрипторов, которые ей удалось создать. Если количество будет меньше, чем примерно 16 миллионов, значит, вы, наверное, вышли за пределы выгружаемого пула памяти еще до достижения теоретического лимита дескрипторов на один процесс.
- Закройте окно командной строки, благодаря чему будет прекращено выполнение процесса Testlimit и будут закрыты все открытые им дескрипторы.
Как показано на следующем рисунке, на системах x86 каждая запись дескриптора состоит из структуры с двумя 32-разрядными элементами: указателем на объект (с флагами) и маской предоставленных прав доступа. На 64-разрядных системах запись таблицы дескрипторов имеет длину 12 байт: 64-разрядный указатель на заголовок объекта и 32-разрядная маска доступа.

Просмотр таблицы дескрипторов с помощью отладчика ядра.
В команде !handle отладчика ядра используются три аргумента: !handle
Индекс дескриптора идентифицирует запись дескриптора в таблице дескрипторов. (Нуль означает «показать все дескрипторы».) Индекс первого дексритора имеет значение 4, второго — 8 и так далее. Например, после ввода команды !handle 4 будет показан первый дескриптор для текущего процесса.
Флаги можно указать в виде поразрядной маски, где разряд 0 означает «показать только информацию в записи дескриптора», разряд 1 означает «показать свободные (то есть неиспользуемые) дескрипторы, а разряд 2 означает «показать информацию об объекте, на который ссылается дескриптор». Следующая команда приводит к показу всех подробностей о таблице дескрипторов для процесса с идентификатором 0x62C:
lkd> !handle 0 7 62c
processor number 0, process 000000000000062c
Searching for Process with Cid == 62c
SessionId: 1 Cid: 062c Peb: 7fffffdb000 ParentCid: 0558
DirBase: 7e401000 ObjectTable: fffff8a00381fc80 HandleCount: 111.
Handle table at fffff8a0038fa000 with 113 Entries in use
0000: free handle, Entry address fffff8a0038fa000, Next Entry 00000000fffffffe
0004: Object: fffff8a005022b70 GrantedAccess: 00000003 Entry: fffff8a0038fa010
Object: fffff8a005022b70 Type: (fffffa8002778f30) Directory
ObjectHeader: fffff8a005022b40fffff8a005022b40 (new version)
HandleCount: 25 PointerCount: 63
Directory Object: fffff8a000004980 Name: KnownDlls
0008: Object: fffffa8005226070 GrantedAccess: 00100020 Entry: fffff8a0038fa020
Object: fffffa8005226070 Type: (fffffa80027b3080) File
ObjectHeader: fffffa8005226040fffffa8005226040 (new version)
HandleCount: 1 PointerCount: 1
Directory Object: 00000000 Name: \Program Files\Debugging Tools for Windows (x64)
Первым флагом является бит блокировки, показывающий, что запись в настоящее время используется. Вторым флагом является указатель на возможность наследования, то есть он показывает, получат ли процессы, созданный данным процессом, копию этого дескриптора в свои таблицы дескрипторов. Как уже отмечалось, наследование дескрипторов может быть указано при создании дескриптора или после него с помощью функции SetHandleInformation.
Третий флаг показывает, должно ли закрытие объекта генерировать контрольное сообщение (флаг не показывается в Windows, диспетчер объектов использует его для внутренних нужд). Бит защиты от закрытия хранится в неиспользованной части маски доступа и показывает, разрешено ли вызывающей программе закрыть этот дескриптор (флаг может быть установлен с помощью системного вызова NtSetInformationObject).
Системным компонентам и драйверам устройств зачастую нужно открывать дескрипторы объектов, к которым не должны иметь доступ приложения пользовательского режима. Это делается путем создания дескрипторов в таблице дескрипторов ядра (внутренняя ссылка на которую осуществляется по имени ObpKernelHandleTable).
Дескрипторы в этой таблице доступны только из режима ядра и в контексте любого процесса. Это означает, что функция режима ядра может сослаться на дескриптор в контексте любого процесса, не оказывая отрицательного влияния на производительность системы. Диспетчер объектов распознает ссылки на дескрипторы из таблицы дескрипторов ядра, когда установлен старший бит дескриптора, то есть когда ссылки на дескрипторы из таблицы дескрипторов ядра имеют значение, больше чем 0x80000000.
Таблица дескрипторов ядра также служит в качестве таблицы дескрипторов для процесса System, и все дескрипторы, созданные процессом System (например, кодом, запущенным в системных потоках), автоматически помечаются как дескрипторы ядра, поскольку они размещаются в таблице дескрипторов ядра по определению.
Поиск открытых файлов с помощью отладчика ядра.
Хотя для поиска дескрипторов открытых файлов можно воспользоваться такими средствами, как Process Explorer, Handle и OpenFiles.exe, они недоступны при просмотре аварийного дампа или удаленном анализе системы.
Вместо них для поиска дескрипторов, отрытых для файлов на том или ином томе, можно воспользоваться командой !devhandles.
- Сначала нужно выбрать букву диска, представляющего интерес, и получить указатель на его объект Device. Для этого, как показано ниже, можно воспользоваться командой !object:
1: kd> !object \Global??\C:
Object: fffff8a00016ea40 Type: (fffffa8000c38bb0) SymbolicLink
ObjectHeader: fffff8a00016ea10 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: fffff8a000008060 Name: C:
Target String is ‘\Device\HarddiskVolume1’
Drive Letter Index is 3 (C:)
- Затем нужно воспользоваться командой !object, чтобы получить объект
Device для нужного имени тома:
1: kd> !object \Device\HarddiskVolume1
Object: fffffa8001bd3cd0 Type: (fffffa8000ca0750) Device
- Теперь можно воспользоваться указателем на объект Device, вставив его в команду !devhandles. Каждый показанный объект указывает на файл:
Checking handle table for process 0xfffffa8000c819e0
Kernel handle table at fffff8a000001830 with 434 entries in use
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00187000 ObjectTable: fffff8a000001830 HandleCount: 434.
0048: Object: fffffa8001d4f2a0 GrantedAccess: 0013008b Entry: fffff8a000003120
Object: fffffa8001d4f2a0 Type: (fffffa8000ca0360) File
ObjectHeader: fffffa8001d4f270 (new version)
HandleCount: 1 PointerCount: 19
Directory Object: 00000000 Name: \Windows\System32\LogFiles\WMI\
Related posts:
- Диспетчер объектов
- Структура объекта Windows
- Методы объекта Windows
- Объекты и дескрипторы
Как получить дескриптор кнопки «Подробнее» в неразвёрнутом диспетчере задач?
Не могу найти способ отыскать дескриптор кнопки «Подробнее» в неразвёрнутом диспетчере задач.
С помощью ControlGetHandle через AutoIT могу получить дескриптор какого-либо элемента окна, но как в этом фрейме произвести поиск кнопки с возратом дескриптора этой кнопки, — это вопрос. Поиск вкладки воспроизводится через _GUICtrlTab_FindTab с указанием имени вкладки, а с этой штукой, естественно, так не работает.

- Вопрос задан более трёх лет назад
- 551 просмотр
1 комментарий
Простой 1 комментарий
ну WinSpy посмотри. может это и не окно.
Решения вопроса 2

# @mindtester Куратор тега Windows
http://iczin.su/hexagram_48
как уже показал в каментах — это не окно. кроме того что уже перечислил, еще к примеру Qt приложения, тоже не используют хендлы. а именно Qt мне однажды надо было автоматизировать извне.
варианты:
— по координатам
— грабить текущее изображение и распознавать надписи, для принятия дальнейшего решения
ps мне пришлось использовать и то и другое )). трудно, криво, не все цели были достигнуты ))
pps поправка — для простейших случаев, на самом деле просто, даже очень просто. но у меня был очень разнообразный текст в одних тех же контролах. там были объективные трудности ))
Ответ написан более трёх лет назад
Комментировать
Нравится Комментировать

Александр Жарченко @exxxcitation1 Автор вопроса
Ваше Эникейшество
Если интересно, то разобрался с данной проблемой с помощью данного кода. Помогла библиотека для нестандартных элементов GUI, — UIAutomate.au3
#RequireAdmin #include Run("taskmgr.exe") $hWd = WinWaitActive("[CLASS:TaskManagerWindow]") $oParent = _UIA_GetElementFromHandle($hWd) #создаёт элемент на основе дескриптора окна $oButton = _UIA_GetControlTypeElement($oParent, "UIA_ButtonControlTypeId", "CBExpandoButtonImage", "AutomationId") #получает элемент с указанием его параметров _UIA_ElementDoDefaultAction($oButton) #выполняет стандартное действие для найденного элемента
Ответ написан более трёх лет назад

# @mindtester Куратор тега Windows
весьма любопытно, обследуем! ))

Александр Жарченко @exxxcitation1 Автор вопроса
#, Тема обсуждения данного случая )

# @mindtester Куратор тега Windows
Александр Жарченко, бррр. это всего лишь отработка по координатам. фи

# @mindtester Куратор тега Windows
Александр Жарченко, ни чего нового к моему ответу

Александр Жарченко @exxxcitation1 Автор вопроса
#, Скрипт ищет элемент с заданными параметры и активирует стандартное действие. Почему координаты? Или я что-то недоглядел?

# @mindtester Куратор тега Windows
Или я что-то недоглядел?
по моему так

Александр Жарченко @exxxcitation1 Автор вопроса
#,
Не вижу ничего, связанного с координатами )


# @mindtester Куратор тега Windows
Александр Жарченко, .. смотрел по ссылке.
ок. беру таймаут для ознакомления

# @mindtester Куратор тега Windows
Александр Жарченко, вы точно решили свою задачу?

Александр Жарченко @exxxcitation1 Автор вопроса
#, По ссылке в конце просто кульминация )
Да, решил, всё окей.

# @mindtester Куратор тега Windows
Александр Жарченко, возвращаю отметку, ибо возможно я не прав.. заранее приношу извинения..

# @mindtester Куратор тега Windows
Александр Жарченко, если еще отработает на Qt.. будет вам спасибо за поиски ))
Ответы на вопрос 1
Насколько я помню, диспетчер задач — это UWP приложение, элементы внутри него не являются win32 объектами и, соответственно, не имеют handle. Могу ошибаться.
WinSpy++, как было замечено, должен показать, что я прав (или нет).
Ответ написан более трёх лет назад
Нравится 1 2 комментария

Александр Жарченко @exxxcitation1 Автор вопроса
То есть обратиться по какому-то индетификатору у меня к нему не получится?

# @mindtester Куратор тега Windows
Александр Ананьев, сомневаюсь что это UWP. Spy++ показывает ApplicationFrameInputSinkWindow для типичных UWP, на всей территории окна. для Параметры Windows на заголовке окна Windows.UI.Core.CoreWindow , на остальной части тоже ApplicationFrameInputSinkWindow . но для Диспетчер задач показывает хендлы для окна вообще и основных элементов. другой вопрос что неопределяемые элементы в контейнере DirectUIHWND
для сравнения:
Visual Studio Installer это полностью Intermediate D3D Window upd по ходу Электрон ))) .. upd2 не электрон, у него вообще Chrome_RenderWidgetHostHWND ))) /upd
Visual Studio 2019 это полностью
HwndWrapper[DefaultDomain;;cb5e328f-c107-4fbd-8771-6d7604e29c59]
Firefox это полностью MozillaWindowClass
Александр Жарченко, по хендлам winapi вам точно не получится добраться до элементов подобных окон
Ваш ответ на вопрос
Войдите, чтобы написать ответ

- Windows
- +3 ещё
Как сделать доступ в Интернет только из виртуальной машины в ноутбуке с Windows 11 через встроенный модем LTE/4G?
- 1 подписчик
- 5 часов назад
- 19 просмотров
Зомби, которые съедают вашу память
Что бы вы там себе не думали, а зомби существуют. И они действительно едят мозги. Не человеческие, правда, а компьютерные. Я говорю сейчас о зомби-процессах и потребляемых ими ресурсах. Это будет душераздирающая история о потерянных и снова найденных 32 ГБ оперативной памяти. Возможно, лишь некоторые из вас столкнутся с точно такой же проблемой, но если вдруг это произойдёт — у вас хотя бы будет шанс понять, что происходит.
Начнём с того, что компьютеры под управлением ОС Windows склонны со временем терять память. Ну, по крайней мере, у меня, при моём способе ими пользоваться. После пары недель без перезагрузок (или, например, всего одного уикэнда за который я 300 раз пересобрал Хром) я стал замечать, что диспетчер задач начинает показывать мне очень маленькое количество свободной оперативной памяти, но в то же время в системе нет никаких процессов, которые эту самую память активно используют. В том примере выше (с 300 сборками Хрома) диспетчер задач сказал мне, что в системе занято 49.8 ГБ плюс ещё 4.4 ГБ памяти сжато — но при этом запущено всего несколько процессов, и все они в сумме даже и близко не используют столько памяти:

В моём компьютере 96 ГБ оперативной памяти (да, я счастливчик) и когда у меня нет вообще никаких запущенных процессов — я, знаете ли, хотел бы видеть ну хотя бы половину этой памяти свободной. Я правда рассчитываю на это. Но иногда этого достичь не удаётся и мне приходится перезагружать ОС. Ядро Windows написано качественно и надёжно (без шуток), так что память не должна бы пропадать бесследно. Но всё же она пропадает.
Первой же моей догадкой стало воспоминание о том, что один из моих коллег как-то жаловался на зомби-процессы, которые иногда остаются в ОС уже не активными, но всё же ещё не до конца удалёнными ядром. Он даже написал специальную утилиту, которая выводит список таких процессов — их имена и количество. Когда он запускал эту утилиту в своих тестах, то получал до нескольких сотен зомби-процессов на обычной Windows-машине. Я нашел его инструмент, запустил на своём компьютере и получил… 506 000 зомби-процессов. Да, 506 тысяч!
Я вспомнил, что одной из возможных причин перехода процесса в состояние «зомби» может быть то, что какой-то другой процесс держит открытым его дескриптор (handle). В моём случае большое количество зомби-процессов играло мне на руку — им было сложнее скрыться. Я просто открыл диспетчер задач и добавил на вкладку Details столбец с количеством открытых дескрипторов для каждого процесса. Затем отсортировал список по убыванию значений в этом столбце. Я сразу нашел героя данной истории — процесс CcmExec.exe (часть Microsoft System Management Server) имел 508 000 открытых дескрипторов. Это было во-первых, очень много, а во-вторых, подозрительно близко к найдненному мною выше числу в 506 000 зомби-процессов.

Я убил процесс CcmExec.exe и получил следующий результат:

Всё получилось ровно так, как я того и ожидал. Как я без иронии писал выше — ядро Windows написано очень хорошо и когда процесс уничтожается, то и все занятые им ресурсы освобождаются. Закрытие CcmExec.exe освободило 508 000 дескрипторов, что дало возможность окончательно закрыть 506 000 зомби-процессов. Количество свободной оперативной памяти мгновенно выросло на 32 ГБ. Тайна раскрыта!
Что такое зомби-процесс?
До этого момента мы ещё не выяснили, что же заставило все эти процессы зависнуть в неопределённости, а не быть удалёнными. Похоже на то, что мы имеем дело с тривиальным багом в приложении (а не в ядре ОС). Общее правило гласит, что когда вы создаёте процесс, то получаете его дескриптор и дескриптор его главного потока. Вы ОБЯЗАНЫ закрыть эти дескрипторы. Если вашей задачей было просто запустить процесс — их можно закрыть сразу же (это не убъёт запущенный процесс, а просто разорвёт связь вашего процесса с ним). Если новый процесс вам для чего-то нужен (например, вы ждёте окончания его работы или вам нужен код, который он вернёт) — то нужно воспользоваться соответствующими функциями (например, WaitForSingleObject(hProcess, INFINITE) для ожидания выхода или GetExitCodeProcess(hProcess, &exitCode) для получения кода возврата) и всё-равно закрыть дескрипторы после того, как вы получили от дочернего процесса всё, чего хотели. Аналогично следует и поступать и с дескрипторами процессов, которые вы для чего-нибудь открываете с помощью функции OpenProcess().
Если процесс, который забывает так поступать, относится к системным, то вам даже может не помочь выйти из своего аккаунта и снова залогиниться, только полная перезагрузка.
Куда же девается память?
Ещё одним инструментом, который я использовал в своём исследовании, была утилита RamMap. Она показывает использование каждой страницы памяти. На вкладке Process Memory мы видим сотни тысяч процессов, каждый из которых занимает 32 КБ оперативной памяти — очевидно, это и есть наши зомби. Но ~500 000 раз по 32 КБ будет равно примерно 16 ГБ — куда же делась остальная память? Сравнение состояния памяти до и после закрытия зомби-процессов даёт ответ на этот вопрос:

Мы можем чётко увидеть, что ~16 ГБ уходит на Process Private Memory. Также мы видит, что ещё 16 ГБ приходится на Page Table Memory. Очевидно, что каждый зомби-процесс занимает 32 КБ в таблице страниц памяти и еще 32 КБ использует для своей личной памяти. Я не знаю для чего зомби-процессу так много памяти, но, наверное, никто никогда не думал, что число таких процессов может измеряться сотнями тысяч.
Некоторые типы занятой памяти увеличились после закрытия процесса CcmExec.exe, в основном это касается Mapped File и Metafile. Я не знаю точно, почему так получилось. Одной из моих догадок является то, что ОС решила, что свободной памяти теперь достаточно и что-то себе закешировала. Это, в общем, не плохо. Мне не жаль памяти для нужд ОС, я просто не хочу, чтобы она пропадала совсем уж бесцельно.
Важное замечание: RamMap тоже открывает дескрипторы всех процессов, так что эту утилиту следует закрыть, если вы хотите добиться закрытия зомби-процессов.
Я написал твит о моей находке и исследование продолжил другой программист, который сумел воспроизвести данный баг и передать информацию о нём разработчику из Microsoft, который сказал, что это «известная проблема, которая иногда случается, когда очень много процессов запускаются и закрываются очень быстро».
Я надеюсь, что данная проблема будет скоро исправлена.
Почему у меня на компьютере возникают такие странные проблемы?
Я работаю над кодом Windows-версии Хрома и одной из моих задач является оптимизация его сборки на этой ОС, а это требует многократных запусков этой самой сборки. Каждая сборка Хрома запускает огромное множество процессов — от 28 000 до 37 000 в зависимости от выбранных настроек. При использовании нашей распределённой системы сборки (goma) эти процессы создаются и закрываются очень быстро. Мой лучший результат сборки Хрома — 200 секунд. Но столь агрессивная политика запуска процессов выявляет и проблемы в ядре Windows и её компонентах:
- Быстрое удаление процессов ведёт к зависаниям пользовательского ввода
- Драйвер тачпада выделяет, но не освобождает память при каждом создании процесса
- App Verifier создаёт O(n^2) лог-файлов (и об этом стоит написать отдельный пост!)
- Есть баг в ядре Windows, который касается буферизации файлов, и этот баг воспроизводится на всех Windows от Server 2008 R2 до Windows 10
- Windows Defender задерживает запуск каждого процесса goma на 250 мс
Что дальше?
Если вы работаете не на компьютере, управляемом политиками компании, то процесс CmmExec.exe у вас не запущен и с конкретно данным багом вы не столкнётесь. Также он коснётся вас только если вы собираете Хром или делаете ещё что-то похожее, создавая и закрывая при этом десятки тысяч процессов в короткие промежутки времени.
CcmExec — не единственная в мире программа с багами. Я нашел много других, содержащих в себе конкретно этот же тип ошибок, приводящих к созданию зомби-процессов. И есть ещё огромное множество тех, которые я не нашел.
Как знают все опытные программисты, любая ошибка, которая не была явно исправлена или предупреждена — точно когда-то произойдёт. Просто написать в документации «Пожалуйста, закройте этот дескриптор» — не достаточно. Так что вот мой вклад в то, чтобы сделать нахождение подобного типа ошибок проще, а их исправление — реальнее. FindZombieHandles — это инструмент, основанный на NtApiDotNet и коде от @tiraniddo, который выводит список зомби-процессов и информацию о том, кто сделал их зомби. Вот пример вывода данной утилиты, запущенной на моём компьютере:
274 total zombie processes. 249 zombies held by IntelCpHeciSvc.exe(9428) 249 zombies of Video.UI.exe 14 zombies held by RuntimeBroker.exe(10784) 11 zombies of MicrosoftEdgeCP.exe 3 zombies of MicrosoftEdge.exe 8 zombies held by svchost.exe(8012) 4 zombies of ServiceHub.IdentityHost.exe 2 zombies of cmd.exe 2 zombies of vs_installerservice.exe 3 zombies held by explorer.exe(7908) 3 zombies of MicrosoftEdge.exe 1 zombie held by devenv.exe(24284) 1 zombie of MSBuild.exe 1 zombie held by SynTPEnh.exe(10220) 1 zombie of SynTPEnh.exe 1 zombie held by tphkload.exe(5068) 1 zombie of tpnumlkd.exe 1 zombie held by svchost.exe(1872) 1 zombie of userinit.exe
274 зомби — это ещё не так плохо. Но уже и это указывает на определённые проблемы, которые могут быть найденны и исправлены. Процесс IntelCpHeciSvc.exe в этом списке имеет самые большие проблемы — похоже на то, что он открывает (и забывает закрыть) дескриптор процесса каждый раз, когда я открываю видео в Windows Explorer.
Visual Studio забывает закрыть дескрипторы как минимум двух процессов, в одном случае это воспроизводится всегда. Просто запустите сборку проекта и подождите ~15 минут пока процесс MSBuild.exe закроется. Можно также выставить опцию “set MSBUILDDISABLENODEREUSE=1” и тогда MSBuild.exe закроется сразу по окончанию сборки и потерянный дескриптор будет виден сразу. К сожалению, какой-то негодяй в Microsoft исправил эту проблему и фикс должен выйти в обновлении VS 15.6, так что поторопитесь воспроизвести её, пока это ещё работает (надеюсь, не нужно объяснять, что это была шутка и никакой он на самом деле не негодяй).
Также вы можете использовать для просмотра забытых процессов программу Process Explorer, сконфигурировав её нижнюю панель так, как это показано ниже (заметьте, что в этом случае будут показаны забытые дескрипторы как для процессов, так и для потоков):

Вот пару примеров найденных багов (о некоторых сообщено разработчикам, но не о всех):
- Утечка в CcmExec.exe (описанный выше случай с 500 000 зомби) — разработчики работают над исправлением
- Утечка в Program Compatibility Assistant Service — проблема исследуется
- Утечка в devenv.exe + MSBuild.exe (проблема уже исправлена)
- Утечка в devenv.exe + ServiceHub.Host.Node.x86.exe (багрепорт отправлен)
- Утечка в IntelCpHeciSvc.exe + Video.UI.exe для каждого открытого видеофайла (Intel приняла багрепорт и переслала его в Lenovo)
- Утечка в RuntimeBroker.exe + MicrosoftEdge и Video.UI.exe (возможно, имеет отношение к некоторым другим багам в RuntimeBroker.exe)
- Утечка в AudioSrv + Video.UI.exe
- Утечка в одном внутреннем инструменте Google из-за использования старой версии psutil
- Утечка в утилитах от Lenovo: tphkload.exe теряет один дескриптор, SUService.exe теряет три
- Утечка в Synaptic’s SynTPEnh.exe

Используя Process Explorer, я заметил, что NVDisplay.Container.exe открывает ~5000 дескрипторов на событие \BaseNamedObjects\NvXDSyncStop-61F8EBFF-D414-46A7-90AE-98DD58E4BC99, создавая новый дескриптор каждые две минуты. Я так понимаю, они хотят быть супер-уверены в том, что могут остановить NvXDSync? Багрепорт Nvidia отправлен.

Corsair Link Service создаёт ~15 дескрипторов в секунду, не освобождает их совсем. Багрепорт отправлен.
Adobe’s Creative Cloud теряет тысячи дескрипторов (около 6500 в день, по моим подсчётам). Багрепорт отправлен.
Удивительно, что никто до этого особо не обращал внимание на подобные баги. Эй, Microsoft, возможно, стоит собирать статистику по таким случаям и что-то предпринимать по этому поводу? Эй, Intel и Nvidia, почистите немного ваш код. Помните, я наблюдаю за вами.
А теперь вы можете взять утилиту FindZombieHandles, запустить её на вашей машине и рассказать о своих находках. Также вы можете использовать в экспериментах диспетчер задач и Process Explorer.
- Блог компании Инфопульс Украина
- Системное программирование
- Google Chrome
- Отладка
- Системы сборки
Что обозначают в диспетчере задач, счётчик потоков и дескрипторы?
это пищеварительный тракт винды. Потоки-это процедуры программ, дескрипторы-это числа или структуры для взаимодействия процессов.
Супер_ЭнималМастер (2462) 10 лет назад
потоки это файлы открытые для передачи, а дескрипторы это управляющие команды потоками?
GrayМыслитель (5748) 10 лет назад
поток-это исполняемый код в отдельной области памяти, они служат для того, чтобы процесс не ждал выполнения долгоиграющих функций и мог заниматься другими делами. А дескриптор-это грубо говоря случайное число, присвоенное устройствам ввода-вывода.
Остальные ответы
Windows одновременно выполняет много программ, в основном служебных самой Windows. Точнее, квазиодновременно, процессор поочередно, очень быстро для нас, переключается на решение каждой из них. Каждая программа в терминах операционной системы считается отдельным процессом. Отдельные части многих задач могут выполняться какое-то время независимо, поэтому процессы часто делятся на несколько потоков, именно между их выполнением переключается процессор, точнее, каждое процессорное ядро (современные процессоры многоядерные) . Операционная система создает каждому процессу и каждому потоку специальную структуру данных, в которой указывается область памяти, в которой лежат данные потока, его приоритет и так далее. Эта структура данных называется дескриптором потока или процесса, дескрипторы имеют числовые имена — идентификаторы.