§ 21. Тестируем драйвер на практике
|
Дмитрий Иванов, 18 Ноября 2008 года
|
Статья доработана и обновлена 16 Мая 2014
|
Итак, продолжим. Теперь у нас есть готовый драйвер, который мы сделали в прошлой статье. Теперь его надо как-то поиспользовать. Как мы будем это делать? Решим обычную задачу:
|
С помощью драйвера через пользовательскую программу хотим записывать данные в нужный порт и читать данные из порта под ОС Windows NT, 2000, XP.
|
Вот этим мы сейчас именно и займемся, но сначала, надо зарегестрировать наш драйвер, поскольку ОС про него пока ничего не знает. Для того чтобы зарегистровать драйвер в системе, надо выполнить следующие шаги:
1. Скопируйте файл драйвера Port.sys в папку C:\Windows\system32\drivers\ если, конечно, система у Вас установлена на диск С: и Вы не меняли пути установки Windows.
2. Среди файлов к этой статье, найдите файл install.reg и запустите его. При этом Вы увидете сообщение следующего вида:

Нажимайте Да. При этом Вы получите сообщение об успешном внесении информации в реестр.

Можно лично убедиться в этом. В программе редактора реестра regedit.exe в ветви HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Services \ Port можно увидеть информацию о нашем драйвере:

Занесение информации о драйвере в рееестр - очень важный момент. Именно от туда ОС при загрузке драйверов будет черпать информацию, какие драйвера загружать и как ей надо это надо делать.
3. Ну и наконец, самый важный элемент - ПЕРЕЗАГРУЗКА КОМПЬЮТЕРА, чтобы внесенные нами изменения вступили в силу. Теперь при загрузке, Windows увидит наш драйвер и загрузит его в память.
Теперь создадим пользовательскую программу, которая сама прав доступа к портам ввода-вывода в Windows NT не имеет, но используя наш драйвер Port.sys сможет легко работать с портами компьютера.
Итак, создаем пустой проект консольного приложения в VS++, создаем пустой файл *.cpp и копируем туда ниже следующий код:
#include <windows.h>
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
//определяем коды дейсвий для драйвера
#define IOCTL_READ (0x800<<2)|(0x22<<16)
#define IOCTL_WRITE (0x801<<2)|(0x22<<16)
HANDLE hDrv;
DWORD cbRet;
int main()
{
//открываем драйвер
hDrv = CreateFile ( "\\\\.\\MYDRIVER", GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
// если не удалось, выходим из программы
if ( hDrv == INVALID_HANDLE_VALUE )
{
cout<<"Error! Can`t open driver. Press any key to exit..."<<endl;
getch();
return false;
}
else
cout<<"Driver is open!"<<endl;
//Тестируем запись данных в порт через драйвер
USHORT DataToDriver[2];
DataToDriver[0]=888; //адрес порта куда писать
DataToDriver[1]=224; //что писать в порт
DeviceIoControl(hDrv,IOCTL_WRITE,DataToDriver,4,NULL,0,&cbRet,NULL);
//Тестируем чтение данных из порта через драйвер
USHORT DataToDriver_[1];
DataToDriver_[0]=888; //адрес порта откуда читать
USHORT DataFromDriver; //куда записать результат чтения
DeviceIoControl(hDrv,IOCTL_READ,DataToDriver_,2,&DataFromDriver,2,&cbRet,NULL);
cout<<DataFromDriver<<endl;
return true;
}
Компилируем, запускаем. Если все в порядке: программа откомпилировалась, драйвер установился, то Вы должны увидеть следующее: строчку с сообщением об успешном открытии драйвера, затем в регистре Data LPT порта должно появиться число 224, далее это число отобразиться на экране. (Наблюдать факт появления числа в регистре лучше используя какой-либо блок светодиодов, подключенный к LPT порту.)

"Что же здесь происходит?" - спросите Вы. Давайте разбираться. В самом начале программы, после подключения необходимых заголовочных файлов, идет определение кодов дейсвия драйвера, которые используются приложением для обращения к драйверу для выполнения определенного дейсвия. Они Вам ни чего не напоминают? Да, именно теже коды были заданы нами в прошлой статье в коде драйвере. Теперь используя их, мы можем требовать от драйвера делать то, что ему положено при поступлении того или иного кода дейсвия.
Далее мы получаем доступ к драйверу (налаживаем канал связи) с помощью функции CreateFile(), используя в качестве имени драйвера его специально определенное символическое имя MYDRIVER в пространстве имен NT. Если результат открытия был неудачным (например, драйвер не установился), то мы улавливаем этот момент и выходим из программы, т.к. делать нам тогда нечего.
Теперь начанается самое интересное - обращения к драйверу. Немного теории. Для общения с драйвером существует специальная функция DeviceIoControl(), принимающая целую кучу парпаметров. Ниже представлены пронуменрованные параметры этой функции:
1. handle на открытый драйвер. Определяет к какому драйверу обращаться. |
2. Код действия, который необходимо выполнить драйверу. |
3. адрес на буфер, в котором находятся данные для отправки в драйвер |
4. размер отправляемых данных в байтах |
5. адрес на буфер, куда драйвер поместит данные, отправляемые программе |
6. число байт, которое мы расчитываем получить от драйвера |
7. реальное число переданных байт из драйвера |
8. не используем, и ставим NULL |
Сначала проводится тестирование записи данных в порт. Для этого создается массив DataToDriver из 2-х элементов. В первый элемент массива помещается адрес порта, куда необходимо записать данные. В этом примере используется адрес 888 - адрес регистра Data LPT порта в десятичной форме. Во второй элемент размещаем данные, которые надо переслать в порт. В данном случае это число 224. Потом идет вызов упомянутой выше функции DeviceIoControl(), которой мы передаем следующие параметры:
- hadle hDrv на наш драйвер,
- код действия IOCTL_WRITE (теперь при поступлении это кода в драйвер, он будет выполнять действия, соответствующие этому коду),
- адрес на массив с данными для отправки в драйвер
- говорим что посылаем в драйвер 4 байта (т.к. один USHORT это 2 байта),
- в качестве приемного буфера указываем NULL, т.к. нам ничего принимать обратно при этом действиии не нужно,
- опять указываем что принимать ни чего не хотим, указываем 0 как размер принимаемых данных,
- адрес на переменную, где будет лежать реальное число переданных байт из драйвера - здесь можно было поставить NULL, но я по инерции написал
- не используем, ставим NULL
Далее идет тестирование чтения данных из порта. Создаем массив из одного элемента (можно было просто переменную) DataToDriver_. В единственный элемент этого массива записываем адрес порта откуда нужно читать данные. В данном случае это все тотже регистр Data LPT порта. Определяем переменную DataFromDriver, в которую поместим результат чтения. Опять вызываем DeviceIoControl(), передавая ей параметры:
- hadle hDrv на наш драйвер,
- код действия IOCTL_READ,
- адрес на массив с данными для отправки в драйвер,
- говорим что посылаем в драйвер 2 байта (т.к. один USHORT это 2 байта),
- в качестве приемного буфера указываем адрес переменной DataFromDriver,
- указываем 2 как размер буфера принимаемых данных,
- адрес на переменную, где будет лежать реальное число переданных байт из драйвера (в идеале, это число надо проверять на соответствие тому что мы ожидаем, и если они отличаются, принимать какие-то действия по обнаружению и исправлению возникшей ошибки)
- не используем, ставим NULL
После последнего вызова функции DeviceIoControl() в переменной DataFromDriver будет находиться число, прочитанное из порта. Его мы именно в конце программы и выводим на экран.
Ну вот и все. Теперь Вы вполне можете сами написать простой драйвер и использовать его в своих целях. На этом статьи по драйверам не заканчиваются; в следующей статье мы поговорим про динамическую загрузку драйвера, что является гораздо более удобным способом установки драйвера нежели используя реестр с дальнейшей перезагрузкой компьютера.
© Дмитрий Иванов
18 Ноября 2008 года
http://www.kernelchip.ru