Что такое дескрипторы windows

Обновлено: 06.07.2024

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

Процессы могут также получать дескрипторы объектов путем наследования дескрипторов во время создания процесса (когда создатель устанавливает флаг наследования дескрипторов при вызове функции 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

Теперь поставьте 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 предоставляет возможность открывать дескрипторы объекта до тех пор, пока она не сможет больше открыть ни одного дополнительного дескриптора. Этим можно воспользоваться, чтобы посмотреть, сколько дескрипторов может быть создано отдельно взятым процессом на вашей системе. Поскольку под таблицы дескрипторов выделяется место в выгружаемом пуле памяти, до достижения максимального количества дескрипторов, доступного для создания в рамках отдельно взятого процесса, вы можете выйти за рамки выгружаемого пула.

Чтобы увидеть, сколько дескрипторов можно создать на вашей системе, выполните следующие действия:

Чтобы показать максимальный размер пула, Process Explorer должен быть правильно настроен на доступ к символам образа ядра, Ntoskrnl.exe. Оставьте это отображение системной информации работающим, чтобы можно было следить за использованием пула при запуске программы Testlimit.

  1. Откройте окно командной строки.
  2. Запустите программу Testlimit с ключом –h (путем ввода команды testlimit –h). Когда программа Testlimit не сможет открыть новый дескриптор, она покажет общее количество тех дескрипторов, которые ей удалось создать. Если количество будет меньше, чем примерно 16 миллионов, значит, вы, наверное, вышли за пределы выгружаемого пула памяти еще до достижения теоретического лимита дескрипторов на один процесс.
  3. Закройте окно командной строки, благодаря чему будет прекращено выполнение процесса 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.

Системным компонентам и драйверам устройств зачастую нужно открывать дескрипторы объектов, к которым не должны иметь доступ приложения пользовательского режима. Это делается путем создания дескрипторов в таблице дескрипторов ядра (внутренняя ссылка на которую осуществляется по имени ObpKernelHandleTable).

Дескрипторы в этой таблице доступны только из режима ядра и в контексте любого процесса. Это означает, что функция режима ядра может сослаться на дескриптор в контексте любого процесса, не оказывая отрицательного влияния на производительность системы. Диспетчер объектов распознает ссылки на дескрипторы из таблицы дескрипторов ядра, когда установлен старший бит дескриптора, то есть когда ссылки на дескрипторы из таблицы дескрипторов ядра имеют значение, больше чем 0x80000000.

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

Поиск открытых файлов с помощью отладчика ядра.

Хотя для поиска дескрипторов открытых файлов можно воспользоваться такими средствами, как Process Explorer, Handle и OpenFiles.exe, они недоступны при просмотре аварийного дампа или удаленном анализе системы.

Вместо них для поиска дескрипторов, отрытых для файлов на том или ином томе, можно воспользоваться командой !devhandles.

  1. Сначала нужно выбрать букву диска, представляющего интерес, и получить указатель на его объект 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:)

  1. Затем нужно воспользоваться командой !object, чтобы получить объект

Device для нужного имени тома:

1: kd> !object \Device\HarddiskVolume1

Object: fffffa8001bd3cd0 Type: (fffffa8000ca0750) Device

  1. Теперь можно воспользоваться указателем на объект 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

Это уже пятая статья из моей серии публикаций "Преодолевая границы Windows", в которых я рассказываю о максимальном значении и объеме ресурсов, которыми управляет Windows, таких как физическая память, виртуальная память, процессы и потоки:

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

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

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


Когда приложение желает получить управление над одним из этих ресурсов, оно сначала должно вызвать соответствующий API, чтобы создать или открыть ресурс. Например, функция CreateFile открывает или создает файл, функция RegOpenKeyEx открывает ключ реестра, а функция CreateSemaporeEx открывает или создает семафор. Если такая функция успешно выполняется, Windows размещает дескриптор в таблице дескрипторов процесса приложения и возвращает значение этого дескриптора, которое приложение обрабатывает неявно, однако фактически речь идет об индексе возвращенного дескриптора в таблице дескрипторов.

Имея в своем распоряжении дескриптор, приложение делает запросы и управляет объектом, передавая значение дескриптора в такие функции API, как ReadFile , SetEvent , SetThreadPriority и MapViewOfFile . Система может организовать поиск объекта, к которому обращается дескриптор путем индексирования таблицы дескрипторов, чтобы определить местонахождения соответствующей дескриптору записи, которая содержит указатель на объект. Запись дескриптора также хранит информацию об операциях, которые стали доступны процессу, когда он открыл объект, что позволяет системе быть уверенной в том, что процесс не сможет выполнять над объектом операции, на которые у этого процесса нет разрешения. Например, если процесс успешно открыл файл для чтения, то запись дескриптора выглядела примерно так:


Если бы этот процесс попытался бы что-то записать в файл, то функция завершила выполнение с ошибкой, поскольку подобный вид доступа к файлу не был предоставлен, а факт кэширования доступа на чтение означает, что система не должна снова повторять более затратную в плане ресурсов проверку прав доступа.


Максимальное число дескрипторов
Чтобы исследовать первое ограничение вы можете воспользоваться инструментальным средством Testlimit, которое я уже использовал в рамках данной серии статей для того, чтобы опытным путем изучить ограничения на ресурсы системы. Его можно скачать на странице Windows Internals здесь . Для определения количества дескрипторов, которое может создать процесс, Testlimit используется с параметром -h, который указывает ему создать столько дескрипторов, сколько возможно. Это достигается путем создания объекта event с помощью функции CreateEvent и последующего неоднократного дублирования возвращаемого системой дескриптора с помощью функции DuplicateHandle . Используя дублирование, Testlimit избегает необходимости создания новых объектов и все ресурсы, потребляемые этим инструментов, расходуются для записей таблицы дескрипторов. Вот результат работы Testlimit с ключом -h в 64-битной системе:


Этот результат, однако, не отображает общее количество дескрипторов, которые может создать процесс, поскольку системные библиотеки DLL открывают различные объекты во время инициализации процесса. Общее число дескрипторов процесса вы можете увидеть, добавив соответствующую колонку в диспетчере задач или Process Explorer. Общее цифра для Testlimit в данном случае равна 16’771’680:


Когда вы запускаете Testlimit на 32-битной системе, число дескрипторов будет немного отличаться:


Общее число дескрипторов также другое, 16’744’448:


Чем обуславливаются эти различия? Ответ состоит в том, что исполнительная система, ответственная за управление таблицей дескрипторов, устанавливает ограничение на количество дескрипторов для каждого процесса, а также на размер записи в таблице дескрипторов. Здесь мы имеем дело с одним из тех редких случаев, когда Windows устанавливает жесткое ограничение на использование ресурса, так что в данном случае исполнительная система определяет число 16’777’216 (16*1024*1024) как максимальное количество дескрипторов, которое может быть выделено процессу. Любой процесс, которые имеет более десяти тысяч дескрипторов одновременно в какой-либо момент времени, весьма вероятно имеет либо серьезные недочеты, допущенные при его проектировании, или утечку дескрипторов. Так что предел в 16 миллионов дескрипторов практически недостижим и призван помочь предотвратить утечку памяти, вызванную вмешательством в работу процесса со стороны остальной системы. Чтобы понять причину того, почему число, отображаемое в диспетчере задач, отличается от жестко установленного максимума, требуется рассмотреть то, каким образом исполнительная система организовывает таблицу дескрипторов.

Запись таблицы дескрипторов должна иметь достаточный размер для того, чтобы хранить маску прав доступа и указатель на объект. Маска доступа является 32-битной, но размер указателя, очевидно, зависит от того, является система 32-битной или 64-битной. Следовательно, запись дескриптора занимает 8 байт на 32-битной Windows и 12-байт на 64-битной Windows. 64-битная Windows дополняет структуры данных записи дескриптора до 64-битных границ, так что 64-битная запись дескриптора фактически занимает 16 байт. Вот определение записи дескрипторов на 64-битной Windows, отображенное в отладчике ядра с помощью команды dt (dump type):


Здесь мы видим, что данная структура содержит в себе и другую информацию, помимо указателя на объект и маски доступа, выделенных на скриншоте.

Исполнительная система размещает таблицы дескрипторов в блоки, имеющие размер страницы, которые она делит на записи таблицы дескрипторов. Это означает, что страница, которая занимает 4096 байт и на системах x86, и на системах x64, может сохранить 512 записей в 32-битной Windows и 256 записей в 64-битной Windows. Исполнительная система определяет максимальное число страниц, которые она может выделить под записи дескрипторов, путем деления жестко установленного максимума (16’777’216) на число записей дескрипторов на странице; для 32-битной Windows это число равно 32’768, а для 64-битной Windows - 65’536. Поскольку исполнительная система использует первую запись на каждой странице для своей собственной идентификационной информации, действительное количество дескрипторов, доступных для процесса, следует получать, вычитая из 16,777,216 полученные выше числа, что объясняет результаты, полученные при запуске Testlimit: 16’777’216 - 65’536 = 16’711’680 и 16’777’216 - 32’768 = 16’744’488.


Дескрипторы и выгружаемый пул
Вторым ограничением, касающимся дескрипторов, является объем памяти, требуемой для хранения таблицы дескрипторов, который исполнительная система выделяет из выгружаемого пула. Исполнительная система для отслеживания выделяемых ею под таблицы дескрипторов страниц использует трехуровневую схему, подобную той, которую используют модули управления памятью (MMU) процессора для руководства трансляциями виртуальных адресов в физические. Мы уже рассматривали с вами организацию нижнего и среднего уровней, которые фактически хранят в себе записи таблицы дескрипторов. Верхний уровень служит в качестве указателей на таблицы среднего уровня и включает в себя 1024 записи на страницу в 32-битной Windows. Отсюда следует, что общее количество страниц, требуемых для хранения максимального числа дескрипторов для 32-битной Windows можно вычислить как 16’777’216/512*4096, что равно 128 Мб. Это совпадает с показателями использования Testlimit выгружаемого пула, которые показывает диспетчер задач:


В 64-битной версии Windows на верхнем уровне содержится 256 указателей на страницу. Это означает в общем для размещения полной таблицы дескрипторов используется 256 Мб выгружаемого пула (16’777’216/256*4096). Правильность данных вычислений подтверждается показателями использования Testlimit выгружаемого пула на 64-битной Windows:


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


Утечки дескрипторов
Для процесса, допускающего утечку дескрипторов характерно то, что число потерянных дескрипторов постоянно возрастает. Причина этого кроется в том, что утечка дескрипторов очень коварна - в отличие от случая с Testlimit, который создавал дескрипторы для одного и того же объекта, процесс, имеющий утечку дескрипторов, вероятнее всего теряет вместе с ними и объекты. Например, если процесс создает события, но не может закрыть их, он создает утечку как записей дескрипторов, так и объектов событий. Объекты "event" располагаются в невыгружаемом пуле, так что данная утечка затронет в дополнение к выгружаемому пулу и невыгружаемый пул.

Вы можете визуально определить объекты, доступ к которым потерял процесс, используя представление дескрипторов в Process Explorer, поскольку там новые дескрипторы выделены зеленым, а закрытые - красным; если вы увидите много зеленых записей при малом количестве красных, значит вы, скорее всего, столкнулись с утечкой. Чтобы увидеть подобное выделение дескрипторов Process Explorer в действии, вы можете открыть процесс командной строки и, выбрав этот процесс в Process Explorer, перейти к просмотру дескрипторов, после чего следует сменить текущую директорию в командной строке. Дескриптор старой рабочей директории подсветится красным, а новой - зеленым:


По умолчанию Process Explorer показывает только дескрипторы, которые указывают на объекты, имеющие имена, что означает, что вы не увидите всех дескрипторов процесса, если не включите опцию "Show Unnamed Handles and Mappings" в меню View. Вот некоторые безымянные дескрипторы из таблицы дескрипторов командной строки:


К счастью, Windows включает в себя средство отслеживания дескрипторов, которое вы можете использовать для установления факта утечки и определения ответственного за эту утечку программного обеспечения. Оно работает с каждым процессом в отдельности и активируется исполнительной системой для записи активности стека каждый раз, когда какой-либо дескриптор создается или закрывается. Вы можете воспользоваться этим инструментом или при помощи утилиты Application Verifier , которую можно бесплатно скачать с сайта Microsoft, или воспользовавшись отладчиком Windows (Windbg) . Если вы хотите, чтобы система проследила за активностью дескрипторов процесса, начиная с его запуска, то вам нужно использовать Application Verifier. В остальных случаях вам нужно использовать отладчик и команду !htrace , чтобы увидеть информацию об активности процесса.

Чтобы продемонстрировать отслеживание активности в действии, я запустил Windbg и подключился к командной строке, которую я запустил ранее. Чтобы включить отслеживание дескрипторов, я ввел команду !htrace с ключом -enable:


Я позволил процессу продолжать работу и снова сменил директорию. После этого я переключился обратно на Windbg, остановил выполнение процесса и запустил команду htrace без параметров, которая выдает список всех открытых и закрытых операций, которые выполнил процесс, начиная с предыдущего запуска команды !htrace с параметром snapshot или с того момента, когда была включена запись активности дескрипторов. Вот результаты работы этой команды для той же сессии отладчика:


Здесь перечислены события, начиная с самой последней операции, так что, если читать снизу, мы увидим, что командная строка открыла дескриптор 0xb8, затем закрыла его, потом открыла дескриптор 0x22c и в конце закрыла дескриптор 0xec. Process Explorer отметил бы дескриптор 0x22c зеленым и 0xec красным, если бы он был обновлен после смены директории, но, по всей вероятности, не увидел бы 0xb8, если бы обновление не произошло между открытием и закрытием этого дескриптора. Стек для открытия 0x22c показывает, что данная операция стала результатом выполнения командной строкой (cmd.exe) своей функции ChangeDirectory. Если добавить в Process Explorer колонку Handle, она подтвердит, что новый дескриптор - 0x22c:


Если вы ищите только утечки, то вам нужно использовать !htrace с параметром -diff, которая показывает только новые дескрипторы, начиная с последней засечки или с начала запуска отслеживания активности. Как и ожидалось, в результате выполнения этой команды мы видим только дескриптор 0x22c:


Прекрасным источником советов о том, как можно устранить утечки дескрипторов является интервью инженера по технической поддержке Microsoft Джеффа Дэйли (Jeff Dailey) для Channel 9.

В следующий раз я рассмотрю ограничения, установленные для таких основанных на дескрипторах ресурсов, как объекты GDI и USER. Дескрипторы этих ресурсов управляются подсистемой Windows, отличной от исполнительной системы, а потому используют другие ресурсы и имеют другие ограничения.

Дескрипторы создаются вызовами API и определяют ресурсы.

Данные дескриптора

Дескриптор — это относительно небольшой блок данных, который полностью описывает объект для GPU в непрозрачном формате, характерном для GPU. Существует несколько различных типов дескрипторов — отрисовки целевых представлений (RTVs), представлений трафаретов глубины (DSV), представлений ресурсов шейдера (СРВС), неупорядоченных представлений доступа (уавс), представлений постоянного буфера (КБВС) и проб.

Дескрипторы имеют разный размер в зависимости от оборудования GPU. Можно запросить размер SRV, UAV или CBV, вызвав ID3D12Device:: жетдескрипторхандлеинкрементсизе. Дескрипторы показаны в этой документации как неделимые единицы. Ниже приведен пример.

SRV, cbv, UAV и образец

Дескрипторы создаются вызовами API и будут включать такие сведения, как ресурс и MIP-карты, которые должен содержать дескриптор.

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

Дескрипторы объектов не обязательно должны освобождаться или освобождаться. Драйверы не присоединяют выделения для создания дескриптора. Дескриптор, однако, может кодировать ссылки на другие выделения, для которых приложение владеет временем существования. Например, дескриптор для SRV должен содержать виртуальный адрес ресурса D3D (например, текстуру), на который ссылается SRV. Это обязанность приложения, чтобы убедиться, что он не использует дескриптор SRV, когда базовый ресурс D3D, от которого он зависит, был уничтожен или изменен (например, объявлен как нерезидентный).

Основной способ использования дескрипторов заключается в помещении их в кучу дескрипторов, которые являются резервными копиями памяти для дескрипторов.

Дескрипторы дескрипторов

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

Дескрипторы ЦП предназначены для немедленного использования, например для копирования, где должны быть идентифицированы источник и назначение. Сразу после использования (например, вызов ID3D12GraphicsCommandList:: омсетрендертаржетс) они могут быть использованы повторно, или их базовая куча может быть удалена.

Дескрипторы GPU не предназначены для немедленного использования — , они определяют расположения из списка команд для использования во время выполнения GPU. Они должны сохраняться до тех пор, пока все списки команд, ссылающиеся на них, не будут выполнены полностью.

Чтобы создать дескриптор дескриптора для запуска кучи, после создания самой кучи дескрипторов вызовите один из следующих методов.

Эти методы возвращают следующие структуры:

Поскольку размер дескрипторов зависит от оборудования, для получения инкремента между каждым дескриптором в куче используется:

Для копирования дескрипторов и передачи дескрипторов в вызовы API можно легко смещать начальное расположение с числом инкрементов. Разыменование маркера необязательно, как если бы он был допустимым указателем ЦП, а также для анализа битов в пределах маркера.

Некоторые вспомогательные структуры были добавлены с элементами инициализации, чтобы упростить управление дескрипторами.

Дескрипторы null

При создании дескрипторов с помощью вызовов API приложения передают значение NULL для указателя ресурса в определении дескриптора, чтобы при доступе к нему не приходилось обращаться к результату, связанному с шейдером.

Оставшаяся часть дескриптора должна быть заполнена как можно больше. Например, в случае с представлениями ресурсов шейдера (СРВС) дескриптор можно использовать для различения типа представления (Texture1D, Texture2D и т. д.). Числовые параметры в дескрипторе представления, например число MIP-карты, должны быть заданы для значений, допустимых для ресурса.

Во многих случаях существует определенное поведение для доступа к несвязанному ресурсу, например СРВС, возвращающего значения по умолчанию. Они будут учитываться при доступе к дескриптору NULL, если тип доступа шейдера совместим с типом дескриптора. Например, если шейдер ожидает Texture2D SRV и обращается к NULL SRV, определенному как Texture1D, поведение не определено и может привести к сбросу устройства.

В сводке, чтобы создать дескриптор null, передайте null параметр предварительного источника при создании представления с такими методами, как креатешадерресаурцевиев. В параметре описания представления пдеск Задайте конфигурацию, которая будет работать, если ресурс не равен null (в противном случае может произойти сбой на определенном оборудовании).

Однако корневые дескрипторы не должны иметь значение null.

На 1 класса оборудовании (см. раздел уровни оборудования), все дескрипторы, которые привязаны (посредством таблиц дескрипторов), должны быть инициализированы либо как реальные дескрипторы, либо как дескрипторы null, даже если нет доступа к оборудованию, в противном случае поведение не определено.

На партнеров оборудовании это относится к привязанным дескрипторам CBV и UAV, но не к дескрипторам SRV.

На Tier3 оборудовании нет ограничений на это, при условии, что неинициализированные дескрипторы никогда не обращаются.

Дескрипторы по умолчанию

Чтобы создать дескриптор по умолчанию для конкретного представления, передайте в метод создания представления (например, креатешадерресаурцевиев) допустимый параметр с предварительным исходным кодом , но передайте значение NULL для параметра пдеск . Например, если ресурс содержал 14 MIPS, то представление будет содержать 14 MIPS. Вариант по умолчанию охватывает наиболее очевидное сопоставление ресурса с представлением. Это требует, чтобы ресурс выделился полным именем формата (например, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB , а не DXGI_FORMAT_R8G8B8A8_TYPELESS).

Дескрипторы по умолчанию нельзя использовать с представлением структуры ускорения райтраЦинг, так как предоставленный параметр предварительного источника должен иметь значение NULL, а расположение должно передаваться через [D3D12_RAYTRACING_ACCELERATION_STRUCTURE_SRV]/Windows/Win32/API/d3d12/NS-d3d12-d3d12_raytracing_acceleration_structure_srv).

Файловый дескриптор — это неотрицательное число, которое является идентификатором потока ввода-вывода. Дескриптор может быть связан с файлом, каталогом, сокетом.

Например, когда вы открываете или создаете новый файл, операционная система формирует для себя запись для представления этого файла и хранения информации о нем. У каждого файла индивидуальный файловый дескриптор Linux. Открыли 100 файлов — где-то в ядре появились 100 записей, представленных целыми числами.

Как файлы получают дескрипторы

Обычно файловые дескрипторы выделяются последовательно. Есть пул свободных номеров. Когда вы создаете новый файл или открываете существующий, ему присваивается номер. Следующий файл получает очередной номер — например, 101, 102, 103 и так далее.

Как работает файловый дескриптор

Дескриптор для каждого процесса является уникальным. Но есть три жестко закрепленных индекса — это первые три номера (0, 1, 2).

Номер файлового дескриптора и режим

Когда вы завершаете работу с файлом, присвоенный ему дескриптор освобождается и возвращается в пул свободных номеров. Он снова доступен для выделения под новый файл.

В Unix-подобных системах файловые дескрипторы могут относиться к любому типу файлов Unix: обычным файлам, каталогам, блочным и символьным устройствам, сокетам домена, именованным каналам. Дескрипторы также могут относиться к объектам, которые не существуют в файловой системе: анонимным каналам и сетевым сокетам.

Понятием «файловый дескриптор» оперируют и в языках программирования. Например, в Python функция os.open(path, flags, mode=0o777, *, dir_fd=None) открывает путь к файлу path, добавляет флаги и режим, а также возвращает дескриптор для вновь открытого файла. Начиная с версии 3.4 файловые дескрипторы в дочернем процессе Python не наследуются. В Unix они закрываются в дочерних процессах при выполнении новой программы.

Для чего нужны файловые дескрипторы

Чтобы оценить важность файловых дескрипторов, нужно разобраться, как работает файловая система.

  1. В традиционной реализации Unix дескрипторы индексируются в таблицу дескрипторов для каждого процесса, поддерживаемого ядром.
  2. Таблица файловых дескрипторов индексирует общесистемную таблицу файлов, открытых всеми процессами.
  3. В таблице файлов записывается режим, в котором открыт файл или другой ресурс — например, для чтения, записи, чтения и записи.
  4. Режим индексируется в таблицу индексных дескрипторов, описывающих фактические базовые файлы. В каждом индексном дескрипторе хранятся атрибуты и расположение дисковых блоков переданного объекта.

Когда нужно выполнить ввод или вывод, процесс через системный вызов передает ядру дескриптор нужного файла. Ядро обращается к файлу от имени процесса. При этом у самого процесса нет доступа к файлу или таблице индексных дескрипторов.

Что такое плохой файловый дескриптор

В однопоточных приложениях такая проблема обычно не возникает.

Что можно делать с файловыми дескрипторами

Файловые дескрипторы можно использовать для исправления ошибок. Например, если на диске нет свободного места, но вы не видите файлы, которые занимают пространство, то можно посмотреть открытые дескрипторы. Это поможет понять, какое приложение заняло весь доступный объем.

Важно понимать, что если мы один раз открыли файл, и он получил файловый дескриптор, то мы можем взаимодействовать с ним дальше. Не имеет значения, что с этим файлом происходит. Его могут переименовать, удалить, могут изменить его владельца, отобрать права на запись и чтение. Если вы уже начали работать с файлом и знаете его дескриптор, то можете продолжать с ним работать.

Читайте также: