Введение
Написание драйвера дает возможность доступа ко всем системным ресурсам WINDOWS, так как его код может выполняться в привилегированном режиме процессора. Наибольший смысл это имеет, если необходим доступ к портам ввода-вывода для связи с каким-либо внешним устройством.
1 Структура драйвера
Простейшая программа драйвера состоит из следующих функций:
2 Работа с драйвером
Пользовательское приложение работает с зарегистрированным в драйвере устройством, видя его как файл. Приложение открывает устройство по имени драйвера и закрывает с помощью функций CreateFile и CloseHandle соответственно, вызовы этих функций формируют пакеты запроса ввода/вывода (Input/output Request Packet – IRP) и передают их в функции драйвера, отвечающие за подготовку к началу работы с устройством или завершению, соответственно [2]. Имя драйвера в функцию CreateFile нужно передавать в формате «\\.\имя», функция вернет дескриптор устройства в случае успеха, или код ошибки в случае неудачи. Далее обращение к драйверу будет производиться с помощью полученного дескриптора, для закрытия устройства также передается этот дескриптор.
Любой обмен данными с устройством производится при помощи функции DeviceIoControl, в которую передаются следующие параметры:
3 Сборка драйвера
Для сборки драйвера необходимо установить пакет Windows Driver Developer
Kit (WinDDK). Пакет будет запускаться как консольное приложение, для компиляции
нужно зайти в папку, где расположен драйвер, и ввести команду «build».
В папке с драйвером, для драйверов, которые написаны на «С», должны находиться
файлы «makefile» и «sources» (без расширения). Первый файл имеет стандартное
содержание и его можно и нужно скопировать из любого примера, включенного
в пакет, во втором находится информация о входных и выходных файлах, например:
TARGETNAME=driv – имя выходного *.sys файла
TARGETPATH=C:\WinDDK\7600.16385.1\MY_TESTS – папка для выходных файлов
TARGETTYPE=DRIVER – тип создаваемого объекта
SOURCES=driv.c – исходный файл с кодом драйвера
При отсутствии ошибок мы получим *.sys файл и сообщение «1 executable built»,
иначе ошибки будут указаны.
4 Установка драйвера
Существует два основных варианта установки – статический и динамический.
Статический – информация о драйвере заносится в реестр, а сам файл
драйвера – в папку windows/system32/drivers, при этом его загрузка происходит
на старте системы, а выгрузка – по завершению работы системы. Конкретно
в реестре в папке HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\
создается папка с тем же именем, что и у файла драйвера, а в ней создаются
следующие ключи:
"Type"=dword:00000001
"Start"=dword:00000002
"ErrorControl"=dword:00000001
"Group"="Extended Base"
Теперь драйвер будет запущен при следующем старте системы.
Динамический – драйвер устанавливается как сервис, через менеджер сервисов,
в этом случае его также можно и удалить в любой момент времени.
Все вышеупомянутые этапы рассмотрены на примере написания драйвера некоторого
псевдо - устройства. В функции разрабатываемого драйвера входило вычисление
квадрата числа, переданного пользовательской программой, и возврат результата.
Был проведен эксперимент по созданию, установке драйвера, и проверке
функционирования его обмена данными с пользовательским приложением.
Функция обмена со стороны драйвера выглядит следующим образом:
NTSTATUS CtlDispatch(IN PDEVICE_OBJECT pDeviceObject,IN PIRP Irp)
{
PIO_STACK_LOCATION pIrpStack;
PUSHORT pIBuffer,pOUT; // указатели типа unsigned short на вх. и вых. буфер
USHORT val;
pIrpStack = IoGetCurrentIrpStackLocation(Irp);
pIBuffer = (PUSHORT)(Irp->AssociatedIrp.SystemBuffer);
pOUT = (PUSHORT)(Irp->UserBuffer);
switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_TASK: // заданный нами код операции
val = pIBuffer[0]; // берем данные из входного буфера
pOUT[0] = val*val; // формируем ответ в выходной буфер
Irp->IoStatus.Information = 2; // количество переданных байт
break;
}
Irp->IoStatus.Status = STATUS_SUCCESS; // код успешного завершения запроса
IoCompleteRequest(Irp,IO_NO_INCREMENT); // завершение запроса
return STATUS_SUCCESS; // успешное завершение функции
};
Открытие драйвера приложением выглядит следующим образом:
hDrv = CreateFile("\\\\.\\MYDRIVER", GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Обмен данными со стороны приложения выглядит следующим образом:
DeviceIoControl(hDrv, // HANDLE драйвера
IOCTL_TASK, // код действия
ToDriver, // указатель на отправляемые данные (в драйвере – pIBuffer)
4, // размер буфера
FromDriver, // указатель на получаемые данные (в драйвере – pOUT)
4, // размер буфера
&cbRet, NULL);
Драйвер был протестирован в операционной системе WINDOWS XP.
Выводы
Написание драйвера является достаточно непростым делом, в меру сложности конкретной задачи. Драйвера могут использоваться как для доступа к аппаратным средствам компьютера, так и скрытым системным ресурсам. Был успешно проведен эксперимент по созданию и использованию драйвера.
Литература