39772

Простой драйвер, посылающий в приложение адреса своих ха

Реферат

Информатика, кибернетика и программирование

Для того чтобы приложение могло запросить у драйвера выполнение конкретного действия из числа предусмотренных в драйвере в качестве одного из параметров этой функции выступает код действия в данном случае IOCTL__DDR. Процедура драйвера вызываемая функцией Windows DeviceIoControl должна проанализировать поступивший в драйвер код действия и передать управление на соответствующий фрагмент драйвера. В программе драйвера для формирования кода действия использован макрос CTL_CODE который определен в файле NTDDK.

Русский

2013-10-08

62.5 KB

0 чел.

Простой драйвер, посылающий в приложение адреса своих характерных процедур

Текст драйвера начинается с оператора препроцессора #include, с помощью которого к программе подсоединяется файл NTDDK.H, содержащийся в пакете DDK NT. Этот файл включает значительную часть определений констант, типов переменных, прототипов функций и макросов, используемых в исходных текстах программ драйверов. Некоторая доля этих определений входит в другие заголовочные файлы, на которые имеются ссылки в файле NTDDK.H. Два следующих предложения программы служат для задания символических имен (в данном случае NT_DEVICE_NAME и WIN32_DEVICE_NAME) текстовым строкам с именами объекта устройства, который будет создан нашим драйвером.

Предложения вида

#define IOCTL_ADDR CTL_CODE \

(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)

позволяют определить коды действий, выполняемых драйвером по запросам приложения. Обращение приложения к драйверу, независимо от цели этого обращения, осуществляется с помощью единой функции DeviceIoControl(). Для того чтобы приложение могло запросить у драйвера выполнение конкретного действия (из числа предусмотренных в драйвере), в качестве одного из параметров этой функции выступает код действия (в данном случае IOCTL__ADDR). Процедура драйвера, вызываемая функцией Windows DeviceIoControl(), должна проанализировать поступивший в драйвер код действия и передать управление на соответствующий фрагмент драйвера.

Коды действия, называемые в документации DDK NT управляющими кодами ввода-вывода (I/O control codes), строятся по определенным правилам. Каждый код представляет собой слово длиной 32 бита, в отдельных полях которого размещаются компоненты кода (рис. 1).

Рис. 1. Поля кода действия

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

В поле "Тип устройства" помещается предопределенная константа, характеризующая устройство (FILE_DEVICE_CD_ROM, FILE_DEVICE_MOUSE и др.). В нашем случае можно использовать константу FILE_DEVICE_UNKNOWN, равную 0x22.

Поле доступа определяет запрашиваемые пользователем права доступа к устройству (чтение, запись, чтение и запись). Мы будем использовать константу FILE_ANY _ ACCESS, равную нулю.

Функциональный код может принимать произвольное значение в диапазоне 0x800...0xFFF (значения 0x000...0x7FF зарезервированы для кодов Microsoft). В рассматриваемом примере используется единственный код действия и для него выбран функциональный код, равный 0x800. В последующих примерах драйверов кодов действий будет больше и им будут присваиваться функциональные коды 0x801,0x802 и т. д.

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

Код действия можно сформировать "вручную", как это сделано в нашем приложении Windows:

#define IOCTL_ADDR (0x800«2) | (0x22«16)

В этом случае предполагаются константы FILE_DEVICE_UNKNOWN=0x22, METHOD_BUFFERED=0 и FILE_ANY_ACCESS=0 при значении функционального кода 0x800. В программе драйвера для формирования кода действия использован макрос CTL_CODE, который определен в файле NTDDK.H. Этот макрос позволяет обойтись без детального знания формата кода действия и значений конкретных констант.

Вслед за определением кода действия в тексте драйвера приведены прототипы используемых в нем функций. Этим функциям можно дать произвольные имена, однако их, как говорят, сигнатура, т. е. состав параметров вместе с типом возвращаемого значения, жестко заданы системой. Ключевое слово IN, с которого начинается описание каждого параметра, говорит о том, что этот параметр является для функции входным, т. е. передается в функцию при ее вызове. В других случаях может использоваться ключевое слово OUT, а также и комбинация IN OUT, если через данный параметр осуществляется как передача данного в функцию, так и возврат результата ее работы. По правилам языка Си, функция может изменять значения передаваемых в нее через параметры данных, если параметром является не само данное, а его адрес (указатель).

Программная часть драйвера начинается с обязательной функции с именем DriverEntry(), которая автоматически вызывается системой на этапе загрузки драйвера и должна содержать все действия по его инициализации. В первых строках функции определяются используемые в ней данные - указатель на объект устройства типа PDEVICE_OBJECT и две символьные строки типа UNICODE_STRING с именами устройства. В качестве первого параметра функция получает указатель еще на один объект, именно на объект драйвера типа PDRIVER_OBJECT.

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

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

При загрузке драйвера система создает объект драйвера (driver object), олицетворяющий для системы образ драйвера в памяти. С другой стороны, объект драйвера представляет собой структуру, содержащую необходимые для функционирования драйвера данные и адреса (указатели) функций.

В процессе инициализации драйвера (процедуру инициализации пишет программист-разработчик драйвера) создаются один или несколько объектов устройств (device object), олицетворяющих те устройства, с которыми будет работать данный драйвер. Объекты устройств существуют все время, пока драйвер находится в памяти; если мы не предусматриваем специальных средств динамической выгрузки драйвера, то объекты устройств будут уничтожены лишь при завершении работы системы Windows. Объект устройства (по крайней мере один) необходим для правильного функционирования драйвера и создается даже в том случае, если, как это имеет место в нашем примере, драйвер не имеет отношения к каким-либо реальным физическим устройствам.

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

Дело усугубляется тем, что объект устройства должен иметь два имени, одно -в пространстве имен NT, другое - в пространстве имен Win32. Оба эти имени должны, во-первых, быть определены с помощью кодировки Unicode, в которой под каждый символ выделяется не 1, а 2 байта, и, во-вторых, представлять собой не просто символьные строки, а специальные структуры типа UNICODE_STRING, в которые входят помимо самих строк еще и их длины ("структуры со счетчиками"). Кодировка Unicode задается с помощью символа L, помещаемого перед символьной строкой в кавычках, а преобразование строк символов в структуры типа UNICODE_STRING осуществляется вызовами функции RtlInitUnicodeString(), которые можно найти далее по тексту программы драйвера.

Имена объектов устройств составляются по определенным правилам. NT-имя предваряется префиксом \Device\, a Win32-имя - префиксом \??\ (или \DosDevice\). При указании имен в Си-программе знак обратной косой черты удваивается.

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

Следующая обязательная операция - создание объекта устройства - осуществляется вызовом функции IoCreateDevice(), принимающей ряд параметров. Первый параметр, указатель на объект драйвера, поступает в функцию DriverEntry() при ее вызове из Windows (см. заголовок функции DriverEntry). Второй параметр определяет размер так называемого расширения устройства - области, служащей для передачи данных между функциями драйвера. В рассматриваемом драйвере расширение устройства не используется и на месте этого параметра указан 0. В качестве третьего параметра указывается созданное нами ранее NT-имя устройства. Наконец, последний параметр этой функции является выходным - через него функция возвращает указатель (типа DEVICE_OBJECT) на созданный объект устройства.

Последнее, что надо сделать на этапе инициализации драйвера, - это занести в объект драйвера адреса основных функций, включенных программистом в текст драйвера. Под основными функциями мы будем понимать те фрагменты драйвера, которые вызываются системой автоматически в ответ на определенные действия, выполняемые приложением или устройством. В наших примерах драйверов таких действий будет три: получение дескриптора драйвера функцией CreateFile(), запрос к драйверу на выполнение требуемого действия функцией DeviceIoControl() и закрытие драйвера функцией CloseHandle(). В более сложных драйверах основных функций может быть больше (вплоть до приблизительно трех десятков). Для хранения адресов основных функций в объекте драйвера предусмотрен массив (с именем MajorFunction) указателей на функции типа PDRIVER_DISPATCH. В файле NTDDK.H определены символические смещения элементов этого массива. Так, в первом элементе массива (смещение IRP_MJ_CREATE=0) должен размещаться указатель на функцию, которая вызывается автоматически при выполнении в приложении функции CreateFile(). В элементе со смещением IRP_MJ_CLOSE=2 размещается указатель на функцию, вызываемую при закрытии устройства (функцией CloseHandle()). Наконец, в элементе со смещением IRP_MJ_DEVICE_CONTROL=OxOE должен находиться адрес функции диспетчеризации, которой система передает управление в ответ на вызов в выполняемом приложении Windows функции DeviceIoControl() с указанием кода требуемого действия. Назначение функции диспетчеризации - анализ кодов действий, направляемых в драйвер приложением, и осуществление переходов на соответствующие фрагменты драйвера. В рассматриваемом примере три упомянутые функции имеют (произвольные) имена CtlCreate, CtlClose и CtlDispatch; структура нашего драйвера с указанием его функций.

Рис. 2. Структура простейшего драйвера

Массив MajorFunction является одним из элементов структурной переменной. Если бы эта структура была объявлена в программе с указанием ее имени (пусть это имя будет Driver-Object), то для обращения к элементу структуры с индексом 0 следовало бы использовать конструкцию с символом точки:

DriverObjеct.MajorFunction[0]=CtlCreate;

Однако у нас имеется не имя структурной переменной, а ее адрес pDriverObject, полученный в качестве первого параметра при активизации функции DriverEntry. В этом случае для обращения к элементу структуры следует вместо точки использовать обозначение->:

pDriverObjеct->MajorFunction[IRP_MJ_CREATE] =CtlCreate;

Разумеется, вместо численного значения индекса массива надежнее воспользоваться символическим.

Функция DriverEntry(), как, впрочем, и все остальные функции, входящие в состав драйвера, завершается оператором return с указанием кода успешного завершения STATUS_SUCCESS (равного нулю).

Как видно из прототипов функций CtlCreate(), CtlClose() и CtlDispatch(), все они принимают (из системы Windows) в качестве первого параметра указатель на объект драйвера, а в качестве второго - указатель на структуру типа IRP. Эта структура, так называемый пакет запроса ввода-вывода (in/out request packet, IRP), играет чрезвычайно важную роль в функционировании драйвера наряду с уже упоминавшимися объектами драйвера и устройства. Рассмотрим более детально создание и взаимодействие всех этих структур (рис. 3).

Объект драйвера, олицетворяющий собой образ выполнимой программы драйвера в памяти, создается при загрузке драйвера на этапе запуска системы Windows. В этом объекте еще не заполнен массив MajorFunction, а также DeviceObject — указатель на объект устройства, поскольку сам объект устройства пока еще не существует.

Загрузив драйвер, Windows активизирует его функцию инициализации DriverEntry(). Эта функция должна содержать вызов IoCreateDevice(), создающий объект устройства. В объекте устройства есть ссылка на объект драйвера, которому это устройство принадлежит, и, кроме того, адрес так называемого расширения устройства (device extension), поля произвольного размера, служащего для обеспечения передачи данных между запросами ввода-вывода. В настоящем примере драйвера расширение устройства не используется (и соответственно, не создается). Функция IoCreateDevice(), создав объект устройства, заносит его адрес в объект драйвера. Таким образом, обе эти структуры оказываются взаимосвязаны.

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

Пакет запроса ввода-вывода создается заново при каждом обращении приложения к драйверу, т. е. при каждом вызове функции DeviceIoControl(). В терминологии драйверов Windows NT этот вызов носит название запроса ввода-вывода (I/O request). Выполнение функции IoCompleteRequest(), которой завершается любая активизируемая из приложения функция драйвера, приводит к уничтожению этого пакета, который, таким образом, существует лишь в течение времени выполнения активизированной функции драйвера. Обычно приложение за время своей жизни обращается к драйверу неоднократно; следующий запрос ввода-вывода снова создаст пакет IRP, который, разумеется, ничего не будет знать о предыдущем.


 

А также другие работы, которые могут Вас заинтересовать

39849. Назначение и принцип действия изделия, сборочной единицы, в которую входит деталь 758 KB
  Форма детали позволяет получать заготовку простой формы с минимальными припусками.25 Диаметр отверстия шпинделя мм 55 Внутренний конус шпинделя Морзе 6 Частота вращения шпинделя мин –1 12.1000 Частота вращения шпинделя мин.9 Скорость быстрых перемещений суппорта мм мин.
39850. Проектирование участка механической обработки для изготовления детали узла 53-320-ГОСТ 387 KB
  Проектируемые и реализуемые производственные процессы должны обеспечивать решение следующих задач: выпуск продукции необходимого качества, без которого затраченные на неё труд и материальные ресурсы будут израсходованы бесполезно; выпуск требуемого количества изделий в заданный срок при минимальных затратах живого труда и вложенных капитальных затратах.
39851. Проектирование участка механической обработки деталей узла Мотоблока 1.61 MB
  Развитие и повышение эффективности машиностроения возможно при существенном росте уровня автоматизации производственного процесса. В последние годы широкое распространение получили работы по созданию новых высокоэффективных автоматизированных механосборочных производств и реконструкции действующих производств
39852. Проектирование участка механической обработки детали узла 58-308-00СБ Деталь: Вал-шестерня 58-308-01 N=400 шт 2 MB
  В связи с изменением методов проектирования и структуры технологической оснастке и широкое применение получит оснастка многократного использования. Опыт работы заводов показывает, что внедрение переналаживаемых станочных приспособлений в 2-3 раза сокращает трудоемкость проектирование и в 3-4 раза цикл изготовления станочных приспособлений.
39854. Разработка технологического процесса механической обработки деталей узла Редуктор - 338 – Б – 0002 1.34 MB
  Проектируемые и реализуемые производственные процессы должны обеспечивать решение следующих задач: выпуск продукции необходимого качества, без которого затраченные на неё труд и материальные ресурсы будут израсходованы бесполезно; выпуск требуемого количества изделий в заданный срок при минимальных затратах живого труда и вложенных капитальных затратах.
39855. Проектирование участка механической обработки для изготовления детали узла МБ – 901 «Барабан сцепления ведомый» 236.5 KB
  Проектируемые и реализуемые производственные процессы должны обеспечивать решение следующих задач: выпуск продукции необходимого качества без которого затраченные на нее труд и материальные ресурсы будут израсходованы бесполезно; выпуск требуемого количества изделий в заданный срок при минимальных затратах живого труда и вложенных капитальных затрат. В дальнейшем это позволит создавать интегрированные производства обеспечивающие автоматизацию основных и вспомогательных процессов и при минимальном участии человека в производственном...
39857. Проектирование участка механической обработки деталей узла Стакан 1.79 MB
  Очевидно, что круг задач эффективной эксплуатации производственных систем весьма широк, эти задачи сложны и многообразны, особенно если учесть масштабы современного производства и уровень техники, и решение их требует от технолога широкого кругозора и глубоких знаний различных дисциплин.