Интернет-магазин

Просмотр корзины
В корзине:

товаров - 0 шт.



§ 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



© KERNELCHIP 2006 - 2023