Чтение*запись данных

April 19, 2012 by admin Комментировать »

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

#include  <windows.h>

#include  <stdio.h>

#define  IOCTL_READ   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

SC_HANDLE  scm,  svc; SERVICE_STATUS   ServiceStatus;

void  main(void)

{

HANDLE  fh;

int i1;

DWORD   bytes;

scm =  OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!scm)

{

printf("Cannot open SCM!\n");

return;

}

svc  =  CreateService(scm, "Testr", "Testr",

SERVICE_ALL_ACCESS,

SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, "i:\\testr.sys",

NULL, NULL, NULL, NULL, NULL);

if (!svc)

{

printf("Cannot  open service!\n"); CloseHandle(scm);

return;

}

StartService(svc, 0, NULL); CloseServiceHandle(svc); CloseServiceHandle(scm);

fh  = CreateFile("\\\\.\\Testr",

GENERIC_READ  | GENERIC_WRITE,

0, NULL, OPEN_EXISTING,

0,

NULL);

if (fh   !=  INVALID_HANDLE_VALUE)

{

DeviceIoControl(fh, IOCTL_READ, NULL,

0,

&i1, sizeof(i1),

&bytes, NULL);

printf("Testr: IOCTL_READ  = %d\n",  i1);

}

CloseHandle(fh);

scm =  OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!scm)

{

printf("Cannot open SCM!\n");

return;

}

svc  = OpenService(scm,   "Testr", SERVICE_ALL_ACCESS); ControlService(svc,  SERVICE_CONTROL_STOP,  &ServiceStatus); DeleteService(svc);

CloseServiceHandle(svc); CloseServiceHandle(scm);

}

Наша программа  будет взаимодействовать  с «виртуальным» устройством testr,  драй вер для которого мы разработаем  позже,  а сейчас остановимся  более детально на функ ции WINAPI DeviceIoControl(), с помощью которой мы прочитаем данные из нашего уст ройства.

Прежде всего, для функции DeviceIoControl() нужно определить коды команд, которые

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

ства, поэтому используется одна команда, которой можем присвоить имя IOCTL_READ:

#define  IOCTL_READ   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

Команда формируется при помощи макроса CTL_CODE и ее второй параметр должен пре вышать число 0x800  (пользовательский  диапазон). Коды IOCTL команд, а также их атрибуты в приложении и в драйвере должны совпадать.

Функция DeviceIoControl в качестве параметров, кроме всего прочего, принимает адрес и размер  входного  и выходного  буферов данных. При этом «входной» буфер функции содержит данные для записи  в  устройство,  а «выходной» буфер – данные,  принятые  от устройства. В нашей  программе   данные  будут только  читаться,  поэтому  параметры  входного  буфера принимаются  равными  NULL и 0, а в выходной буфер i1 будет записано значение целочислен ной переменной,  считанной из устройства. Значение  этой переменной  затем будет выведено на экран дисплея.

Остальные фрагменты программного  кода  используются загрузки выгрузки  драйвера

устройства и нами проанализированы  ранее.

Перейдем теперь к драйверу устройства. Наше устройство называется testr и из него можно только читать данные. Исходный текст драйвера этого устройства показан  далее:

#include  <ntddk.h>

#define  IOCTL_READ   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

#define  NT_DEVICE_NAME   L"\\Device\\Testr"

#define  DOS_DEVICE_NAME   L"\\DosDevices\\Testr"

typedef struct  _MY_DEVICE_EXTENSION

{

LONG  L1;

}  MY_DEVICE_EXTENSION,   *PMY_DEVICE_EXTENSION;

VOID

Testr_Unload   (IN  PDRIVER_OBJECT  DriverObject)

{

UNICODE_STRING  DosDeviceName;

RtlInitUnicodeString(&DosDeviceName,  DOS_DEVICE_NAME); IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DriverObject->DeviceObject);

}

NTSTATUS

Testr_Create(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

Irp->IoStatus.Status  = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

Testr_Close(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

Irp->IoStatus.Status  = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

Testr_IoctlR(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

PMY_DEVICE_EXTENSION   dx=(PMY_DEVICE_EXTENSION)DeviceObject-

>DeviceExtension;

PIO_STACK_LOCATION  pIoStack   = IoGetCurrentIrpStackLocation(Irp);

ULONG   ctlCode   = pIoStack->Parameters.DeviceIoControl.IoControlCode; ULONG   OutputLength  = pIoStack-

>Parameters.DeviceIoControl.OutputBufferLength;

PUCHAR   buf  = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;

dx->L1 = 337; OutputLength  = 4;

if (ctlCode == IOCTL_READ)

{

RtlMoveMemory(buf,

(PUCHAR)&dx->L1, OutputLength);

}

Irp->IoStatus.Information  = OutputLength; Irp->IoStatus.Status  = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

DriverEntry(IN  PDRIVER_OBJECT  DriverObject, IN PUNICODE_STRING  RegistryPath)

{

PDEVICE_OBJECT  DeviceObject; UNICODE_STRING  NtDeviceName; UNICODE_STRING  DosDeviceName; NTSTATUS  status;

RtlInitUnicodeString(&NtDeviceName,  NT_DEVICE_NAME);

status =  IoCreateDevice(DriverObject, sizeof(MY_DEVICE_EXTENSION),

&NtDeviceName, FILE_DEVICE_UNKNOWN,

0, FALSE,

&DeviceObject);

if (!NT_SUCCESS(status))

{

IoDeleteDevice(DeviceObject);

return status;

}

RtlInitUnicodeString(&DosDeviceName,  DOS_DEVICE_NAME);

status = IoCreateSymbolicLink(&DosDeviceName, &NtDeviceName);

if (!NT_SUCCESS(status))

{

IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DeviceObject);

return status;

}

DeviceObject->Flags  &=   ~DO_DEVICE_INITIALIZING; DeviceObject->Flags  |=  DO_BUFFERED_IO;

DriverObject->MajorFunction[IRP_MJ_CREATE]  = Testr_Create; DriverObject->MajorFunction[IRP_MJ_CLOSE]     = Testr_Close; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = Testr_IoctlR; DriverObject->DriverUnload  = Testr_Unload;

return status;

}

Анализ исходного текста драйвера (назовем его testr.sys) начнем с раздела деклараций в начале листинга. Как и в приложении пользователя, только что нами рассмотренном, здесь присутствует объявление команды IOCTL_READ:

#define  IOCTL_READ   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

Как видно, это объявление совпадает с тем, что указано в пользовательском приложении. Для хранения данных нам нужна область памяти, для чего воспользуемся областью, вы деляемой под расширение  драйвера  устройства (DeviceExtension).  считываться будет всего одна целочисленная переменная, поэтому область расширения объявляется следующим об

разом:

typedef struct  _MY_DEVICE_EXTENSION

{

LONG  L1;

}  MY_DEVICE_EXTENSION,   *PMY_DEVICE_EXTENSION;

В этой структуре указана  32 битовая  целочисленная переменная L1, значение которой будет прочитано приложением.

В нашем драйвере будет выполняться чтение данных по IOCTL команде, поэтому должен присутствовать обработчик запроса IRP_MJ_DEVICE_CONTROL, что и указывается в функции DriverEntry следующей строкой:

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  =  Testr_IoctlR;

В качестве обработчика запроса выступает функция Test_IoctlR. Проанализируем еe ис ходный текст. Прежде  всего,  создаем  переменную указатель  dx на  область расширения, в которой хранится значение переменной L1. Затем для удобства работы создаем перемен

ные ctlCode и InputLength, которые будут содержать соответственно код команды IOCTL и раз мер передаваемых  в приложение  данных. Для передачи данных система создает буфер памя ти, адрес которого мы присваиваем переменной buf.

Переменной L1, находящейся в области расширения, присваиваем  произвольное значе ние (в данном случае, 337), которое и будет передано в приложение пользователя. Кроме того, нам потребуется указать точный размер (в байтах) передаваемой области данных. Эти действия выполняются двумя операторами:

dx->L1 = 337; OutputLength  = 4;

Далее  анализируем   код  команды   (оператор  if)  и,  если  это  IOCTL_READ, то  выполняем пересылку данных из области расширения  драйвера  в системный буфер с помощью функции RtlMoveMemory:

RtlMoveMemory(buf,

(PUCHAR)&dx->L1, OutputLength);

В этой функции первый параметр – адрес области памяти, куда будут перемещены дан ные, второй параметр – адрес области памяти, откуда будут перемещаться  данные, и, нако нец, третий параметр показывает размер пересылаемых данных в байтах.

Перед завершением выполнения запроса функция должна передать Менеджеру ввода

вывода информацию о размере выводимых данных, что выполняет опреатор

Irp->IoStatus.Information  = OutputLength;

Затем запрос завершается, как обычно, установкой статуса операции и вызовом функ

ции IoCompleteRequest:

Irp->IoStatus.Status  = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT);

Вот, собственно, и все об этом драйвере. Перед компиляцией  исходного текста в Windows DDK не забудьте указать корректное имя в файле sources. Напомню, что сборки драйвера нужны два файла – makefile и sources. Оба можно взять из примеров, затем откорректиро вать sources, после чего выполнить компиляцию драйвера.

Скопируем файл драйвера testr.sys в корневой каталог i: и запустим пользовательское приложение. На экране дисплея увидим результат работы (рис. 7.11):

В рассмотренном нами примере приложение пользователя выполнило чтение данных из

«виртуального» устройства testr.  С таким же успехом можно и записывать данные в устрой ство. Сейчас мы рассмотрим более сложный пример, в котором пользовательское приложе ние записывает  целочисленное  значение  в «виртуальное» устройство  ввода вывода, а затем читает из него значение, умноженное на 3. Вот исходный текст приложения:

#include  <windows.h>

#include  <stdio.h>

Рис. 7.11

Вид окна работающей программы чтения данных

из устройства testr

Рис. 7.12

Вид окна работающей программы

Таким  образом,  мы  рассмотрели базовые  концепции  функционирования  драйверов устройств операционных  систем Windows. Перед тем как  двигаться дальше, хочу обратить внимание читателей на еще один аспект программирования  драйверов. Во многих случаях, особенно при разработке  драйверов для электронных любительских устройств, управляемых от компьютера,  отдельные фрагменты программного кода можно разрабатывать на встроен ном ассемблере. Например, фрагмент программного кода, в котором выполняется умноже ние числа на 3 в драйвере testw.sys (оператор dx >L1 = dx >L1 * 3;) может быть заменен сле дующей последовательностью команд:

. . .

PUCHAR   Lbuf = (PUCHAR)&dx->L1;

switch   (ctlCode)

{

case  IOCTL_READ:

asm {

. . .

mov     ECX, DWORD PTR   Lbuf mov    EAX,  DWORD  PTR [ECX] imul  EAX,  3

mov      DWORD PTR   [ECX], EAX

}

На первый взгляд может показаться, что оператор

dx->L1 = dx->L1 * 3;

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

Перейдем к некоторым практически полезным применениям драйверов устройств при разработке  устройств домашней электроники,  управляемым от параллельного порта ПК.

Источник:  Магда Ю. С. Компьютер  в домашней лаборатории.  – М.: ДМК Пресс, 2008. – 200 с.: ил.

Оставить комментарий

микросхемы мощности Устройство импульсов питания пример приемника провода витков генератора выходе напряжение напряжения нагрузки радоэлектроника работы сигнал сигнала сигналов управления сопротивление усилитель усилителя усиления устройства схема теория транзистора транзисторов частоты