IoRegisterDeviceInterface определяет символьную ссылку, сопоставляемую с экземпляром объекта «устройство». Однако, прежде чем диспетчер ввода-вывода действительно создаст ссылку, драйвер должен вызвать функцию IoSetDeviceInterfaceState, чтобы разрешить использование интерфейса этого устройства. Обычно драйвер делает это, когда диспетчер PnP посылает ему команду start-device для запуска устройства.
Приложение, которому нужно открыть объект «устройство», представленный GUID-идентификатором, может вызывать PnP-функции настройки, например SetupDiEnumDeviceInterfaces для перечисления интерфейсов, доступных по конкретному GUID, и получения имен символьных ссылок, с помощью которых может быть открыт объект «устройство». Чтобы получить дополнительную информацию (например, автоматически сгенерированное имя устройства), приложение вызывает функцию SetupDiGetDeviceInterface-Detail для всех устройств, перечисленных SetupDiEnumDeviceInterfaces. Получив от SetupDiGetDeviceInterfaceDetail имя устройства, приложение обращается к Windows-функции CreateFile, чтобы открыть устройство и получить его описатель.
ЭКСПЕРИМЕНТ: просмотр каталога Device
Для просмотра имен устройств в каталоге Device пространства имен диспетчера объектов можно использовать утилиту Winobj (wwwsysin ternals.com) или команду !object отладчика ядра. Ниже показан пример символьной ссыпки, созданной диспетчером ввода-вывода и указывающей на объект «устройство» с автоматически сгенерированным именем.
Команда !object отладчика ядра для каталога Device выводит следующую информацию.
При выполнении команды !object с указанием объекта-каталога диспетчера объектов отладчик ядра записывает дамп содержимого каталога в соответствии с его внутренней организацией в диспетчере объектов. Для ускорения поиска каталог хранит объекты в хэш-таблице, основанной на хэше имен объектов, поэтому команда !object перечисляет объекты так, как они хранятся в каждой корзине (bucket) хэш-таблицы каталога.
Как видно на рис. 9–6, объект «устройство» ссыпается на свой объект «драйвер», благодаря чему диспетчер ввода-вывода знает, из какого драйвера нужно вызвать процедуру при получении запроса ввода-вывода. C помощью объекта «устройство» он находит объект «драйвер», который представляет драйвер, обслуживающий устройство. После этого он обращается к объекту «драйвер», используя номер функции из исходного запроса; каждый номер функции соответствует точке входа драйвера (номера функций на рис. 9–6 подробнее описываются в разделе «Блок стека IRP» далее в этой главе).
C объектом «драйвер» нередко сопоставляется несколько объектов «устройство». Список объектов «устройство» представляет физические и логические устройства, управляемые драйвером. Так, для каждого раздела жесткого диска имеется отдельный объект «устройство» с информацией, специфичной для данного раздела. Ho для обращения ко всем разделам используется один и тот же драйвер жесткого диска. При выгрузке драйвера из системы диспетчер ввода-вывода с помощью очереди объектов «устройство» определяет устройства, на которые повлияет удаление драйвера.
ЭКСПЕРИМЕНТ: исследуем объекты «драйвер» и «устройство»
Эти объекты можно исследовать с помощью команд !drvobj и !devobj отладчика ядра. Следующий пример относится к объекту «драйвер» для драйверов класса «клавиатура». У этого объекта имеется единственный объект «устройство».
Заметьте, что команда !devobj заодно сообщает адреса и имена любых объектов «устройство», поверх которых размещен просматриваемый вами объект (строка AttachedTo), а также объектов «устройство», размещенных над указанным объектом (строка AttachedDevice).
Использование объектов для регистрации информации о драйверах означает, что диспетчеру ввода-вывода не нужно знать никаких деталей реализации драйверов. Он просто находит драйвер по указателю, тем самым позволяя легко загружать новые драйверы и обеспечивая их переносимость. Кроме того, представление устройств и драйверов разными объектами упрощает подсистеме ввода-вывода закрепление драйверов за дополнительными устройствами, которые появляются при изменении конфигурации системы.
Открытие устройств
Объекты «файл» являются структурами режима ядра, которые точно соответствуют определению объектов в Windows: это системные ресурсы, доступные для совместного использования двум или нескольким процессам, у них могут быть имена, их безопасность обеспечивается моделью защиты объектов, и они поддерживают синхронизацию. Хотя большинство разделяемых ресурсов в Windows базируется в памяти, основная часть ресурсов, с которыми имеет дело подсистема ввода-вывода, размещается на физических устройствах или представляет их. Несмотря на эту разницу, операции над совместно используемыми ресурсами подсистемы ввода-вывода осуществляются как над объектами.
Объекты «файл» — представление ресурсов в памяти, которое обеспечивает чтение и запись данных в эти ресурсы. B таблице 9–1 перечислены некоторые атрибуты объектов «файл». Описание и размеры полей см. в определении структуры FILE_OBJECT в Ntddk.h.
ЭКСПЕРИМЕНТ: просмотр структуры данных объекта «файл»
Вы можете просмотреть содержимое этой структуры с помощью команды dt отладчика ядра:
Когда поток открывает файл или простое устройство, диспетчер ввода-вывода возвращает описатель объекта «файл». Рис. 9–7 иллюстрирует, что происходит при открытии файла.
B этом примере С-программа (1) вызывает из стандартной библиотеки функцию fopen, которая в свою очередь вызывает Windows-функцию CreateFile (2). Далее DLL подсистемы Windows (в данном случае — Kernel32.dll) вызывает функцию NtCreateFile из Ntdll.dll (3). Эта функция в Ntdll.dll содержит соответствующие команды, вызывающие переход в режим ядра в диспетчер системных сервисов, который и обращается к настоящей функции NtCreateFile (4) из Ntoskrnl.exe (о диспетчеризации системных сервисов см. главу 3).
ПРИМЕЧАНИЕ Объекты «файл» представляют открытые экземпляры файлов, а не сами файлы. B отличие от UNIX-систем, где используются vnode, в Windows представление файла не определено — системные драйверы Windows определяют свои представления.
Как и другие объекты исполнительной системы, файлы защищаются дескрипторами защиты, которые содержат список управления доступом (ACL). Чтобы выяснить, позволяет ли ACL файла получить процессу доступ того типа, который был запрошен его потоком, диспетчер ввода-вывода обращается к системе защиты. Если да, диспетчер объектов (5,6) разрешает доступ и сопоставляет предоставленные права доступа с описателем файла, возвращаемым потоку. Если этому или другому потоку того же процесса понадобятся дополнительные операции с файлом, не указанные в исходном запросе, ему придется открыть новый описатель, по которому тоже будет проведена проверка прав доступа (подробнее о защите объектов см. главу 8).
ЭКСПЕРИМЕНТ: просмотр описателей устройств
У любого процесса, открывшего описатель какого-либо устройства, в таблице описателей появляется объект «файл», соответствующий открытому экземпляру. Для просмотра таких описателей вполне годится Process Explorer: выберите процесс и пометьте Show Lower Pane в меню View и Handles в подменю Lower Pane View меню View. Отсортируйте по столбцу Туре и прокрутите содержимое до того места, где показываются описатели, представляющие объекты «файл»; они помечаются как «File».
B данном примере у процесса Csrss имеются открытые описатели объектов «файл», которые представляют открытые экземпляры устройств и получают автоматически генерируемые имена, а также описатели объектов «файл», принадлежащих драйверу службы терминалов. Чтобы просмотреть конкретный объект «файл» в отладчике ядра, сначала определите адрес этого объекта. Следующая команда выводит сведения о выделенном на иллюстрации описателе (0xB8), который принадлежит процессу Csrss.exe с идентификатором 2332 (0x91c):
Поскольку это объект «файл», вы можете получить информацию о нем командой !fileobj:
Поскольку объект «файл» — представление разделяемого ресурса в памяти, а не сам ресурс, он отличается от остальных объектов исполнительной системы. Объект «файл» содержит лишь данные, уникальные для описателя объекта, тогда как собственно файл — совместно используемые данные или текст. Всякий раз, когда поток открывает описатель файла, создается новый объект «файл» с новым набором атрибутов, специфичным для этого описателя. Например, атрибут «текущее смещение в байтах» определяет место в файле, где будут записаны или считаны следующие данные по текущему описателю. У каждого описателя одного и того же файла свое значение атрибута «текущее смещение в байтах». Кроме того, объект «файл» уникален для процесса; исключение составляют случаи, когда процесс дублирует описатель файла для передачи другому процессу (через Windows-функцию DuplicateHandle) или когда дочерний процесс наследует описатель файла от родительского. Только в этих двух случаях процессы располагают отдельными описателями, которые ссылаются на один и тот же объект «файл».