Введение
Современный уровень аппаратных средств, используемых в автоматизации, давно «размыл» чёткую границу между компьютерами верхнего уровня и контроллерами полевого уровня, с точки зрения используемого системного программного обеспечения. Аппаратные ресурсы современных контроллеров позволяют использовать на них не только оптимизированные к минимальным аппаратным требованиям операционные системы, например Windows CE.NET, но и стандартные ОС класса Windows или Linux. Кроме того, из встраиваемых, но более ресурсоёмких операционных систем, всё более активно применяющихся на рынке автоматизации отечественной промышленности, можно назвать Embedded Windows XP, общая структурная организация которой во многом схожа с организацией стандартного (офисного) варианта. Возможность «конструирования» разработчиком образа этой ОС из большого числа компонентов не влияет на основные принципы общей организации ОС в части затрагиваемых в этой статье вопросов, поэтому рассматриваемые здесь возможности написания драйверов уровня ядра применимы и для этой ОС. На текущий момент доминирующее положение среди стандартных ОС Windows в сфере автоматизации занимают ОС Windows 2000/XP, поэтому дальнейшее изложение материала касается именно этих ОС. Использование стандартных ОС позволяет максимально использовать опыт программирования, накопленный специалистами, привыкшими работать со знакомым набором API (Application Programming Interface — интерфейс прикладного программирования). В числе решаемых программистами задач, кроме собственно написания прикладной программы, может возникнуть необходимость в написании драйвера, например, если по
Необходимые инструменты программирования
Для решения поставленной задачи используем пакет MASM32
Краткий обзор архитектуры Windows
В Windows 2000/XP существует чёткое разграничение двух областей в оперативной памяти и режимов процессора для исполняемого кода: 1) область исполняемого кода в непривилегированном режиме работы процессора (пользовательском режиме) для приложений пользователя и части компонентов ОС, и область исполняемого кода операционной системы в привилегированном режиме процессора (режиме ядра). Под областью исполняемого кода надо понимать области загрузки (диапазон адресов) в оперативной памяти вычислительной системы. Windows 2000/XP —
Как уже отмечалось, в режиме пользователя функционируют не только прикладные программы пользователя, но и часть процессов самой ОС.
К компонентам операционной системы, работающим в режиме пользователя, относятся:
- некоторые процессы поддержки системы, например процесс обработки входа в систему (Winlogon);
- процессы
Windows-сервисов (о сервисах поговорим чуть позже). В виде сервисов оформлены как некоторые системные сервисы (например Task Scheduler), так и отдельные компоненты прикладных программ, например Microsoft SQL Server, а также некоторые драйверы; - пользовательские приложения. На текущий момент они бывают шести типов: Win32, Win64 (в
64-битовой версии ОС), Windows 3.1,MS-DOS , POSIX и OS/2; - подсистемы окружения. Это часть операционной системы (программные оболочки), предоставляющая приложениям пользователя определённый для конкретной подсистемы набор функций. Windows обеспечивает работу с тремя подсистемами окружения: Win32, POSIX и OS/2. Windows 2000 поставляется с двумя подсистемами, а в Windows XP, кроме Win32, не поставляются другие подсистемы окружения.
К компонентам ОС, работающим в режиме ядра, относятся:
- исполнительная система, обеспечивающая базовыми сервисами в части управления памятью, процессами и потоками,
вводом-выводом и т.д.; - ядро, которое содержит обобщённый набор функций ОС, скрывающий различия между аппаратными платформами (на разных этапах развития ОС NT поддерживались не только процессоры Intel, но и MIPS, Alpha AXP, Motorola PowerPC). Ядро предоставляет процедуры/функции и базовые объекты, используемые исполнительной системой и драйверами для реализации структур и функций более высокого уровня. К таким функциям относятся планирование потоков, диспетчеризация прерываний, синхронизация процессов и т.д.;
- драйверы устройств;
- уровень абстрагирования от оборудования (Hardware Abstraction Layer, HAL) — набор низкоуровневых функций (около 92), обеспечивающий стандартный интерфейс взаимодействия с
аппаратно-зависимыми элементами для функций, вызываемых компонентами ядра, драйверов и исполнительной системы, позволяющий абстрагироваться от того, на какой конкретно элементной базе (чипе контроллера прерывания, контроллера ПДП) реализовано выполнение доступа к шине, таймеру и т.д.; - подсистема поддержки окон и графики.
Драйверы устройств в Windows, в отличие от DOS, для поддержки переносимости не обращаются к оборудованию напрямую, а используют функции, предоставляемые HAL. Драйверы устройств режима ядра делятся на следующие основные категории:
- драйверы файловой системы (например сетевые редиректоры и серверы). Не стоит понимать буквально, что речь идёт только о файловой системе ОС. На самом деле многие физические устройства (например
COM-порты ) представляются в системе как файлы, и обращение к ним осуществляется посредством вызова функций, как к обычным файлам, но со специфическими параметрами. Далее уже драйверы файловой системы, получившие запрос наввод-вывод , определяют, о каком устройстве идёт речь, и вызывают соответствующие физическому устройству драйверы следующего уровня; - драйверы с поддержкой
Plug-and-Play (PnP) и ACPI (advanced configurationpower-management interface — усовершенствованный интерфейс управления конфигурацией и энергопотреблением); - драйверы, не поддерживающие спецификации PnP и ACPI (например драйверы протоколов TCP/IP, IPX/SPX и т.д.), которые расширяют функциональность системы, предоставляя доступ из режима пользователя к системным сервисам и драйверам режима ядра.
В свою очередь, в каждой из категорий есть группы драйверов, которые различаются в зависимости от модели устройства и места драйверов в цепочке обработки запроса на обслуживание операций
Начиная с Windows 2000, была введена поддержка PnP и энергосберегающих технологий (ACPI), что привело к расширению модели драйверов, называемой Windows Driver Model (WDM); напомню, что речь идёт о линейке NT, модель драйверов WDM ранее реализована в Windows 98 и Windows Millennium Edition. ОС Windows 2000 и более поздние версии ОС линейки NT поддерживают и так называемые унаследованные драйверы (NT4), естественно, с некоторой потерей функциональности.
Модель WDM предусматривает существование трёх типов драйверов:
- драйвер шины. Интересным моментом является то, что, в отличие от ОС NT4, Windows 2000 и выше, позволяют реализовать поддержку новых типов шин, не поддерживаемых самой ОС, не путём создания своего HAL (DLL), а всего лишь добавлением своего драйвера шины. Это крайне существенно для поставщиков
OEM-оборудования ; - функциональный драйвер;
- драйвер фильтра.
В рамках обобщения понятия устройства в Windows существует понятие класса устройств. Введение этого уровня абстрагирования сопровождается неизбежным появлением типа драйверов, отвечающих за обслуживание устройств одного класса (например
- классов устройств;
порт-драйверы ;минипорт-драйверы .
Драйверы устройств в ОС Windows могут работать как в режиме ядра, так и в пользовательском режиме. К последним относятся:
- драйверы виртуальных устройств (VDD);
- драйверы принтеров.
Важнейшим компонентом исполнительной системы, отвечающим за связь с устройствами, является подсистема
Подсистема
Сервисы
Сервисы (Services), или службы, являются процессами, предоставляющими дополнительную функциональность в системе, не зависящую от интерактивных действий пользователя. То есть это приложения, запускаемые без учёта того, зарегистрировался ли в системе
- Service application — сервисное приложение (или драйвер);
- Service control program (SCP) — программа управления сервисом;
- Service Control Manager (SCM) — диспетчер управления сервисом.
Весь необходимый API для реализации механизма диспетчеризации (
Сервисное приложение — это драйвер или обычное
SCP — стандартное
SCM отвечает за загрузку как сервисов, так и драйверов, поэтому не удивительно, что значения некоторых параметров явным образом указывают на контекст текущего приложения. Общий термин (Services), используемый Microsoft для обозначения как служб, так и драйверов устройств (они действительно во многом схожи по стилю работы и характеру выполняемых функций), часто вызывает путаницу в понимании программной документации. Наиболее важные для нашей прикладной задачи параметры созданного раздела реестра и их возможные значения для драйверов и сервисов показаны в таблице 1.
На конечном этапе загрузки ОС системный процесс Winlogon (перед появлением диалогового окна с приглашением к регистрации) запускает SCM, который в созданной внутренней базе данных сервисов SCM ищет записи драйверов и сервисов с параметром Start, имеющим значение SERVICE_AUTO_START, и запускает их.
Далее рассмотрим пример реализации SCP на языке ассемблера (листинг 1). Минимально необходимый материал, позволяющий понять правила и нотацию для оформления программ на этом языке, можно найти в [3].
Действия программы заключаются в следующем. Устанавливается канал связи с SCM. Если при установлении канала связи происходит ошибка, выводится сообщение «Attempt of connection with SCM has failed!» и программа завершается. В случае установления канала регистрируем драйвер. Если происходит ошибка регистрации, выводим соответствующее сообщение «Attempt to register the driver has failed!» и, закрыв дескриптор SCM, завершаем программу. В случае успеха регистрации драйвера запускаем драйвер, удаляем его, закрываем дескриптор драйвера, закрываем дескриптор SCM, завершаем программу. В этой программе драйвер запускается один раз, и поэтому его действия носят характер, только подтверждающий факт его работы. Теперь более подробно.
Для вызова
OpenSCManager proto
lpMachineName:LPSTR,
lpDatabaseName:LPSTR,
wDesiredAccess:DWORD
lpMachineName
Указатель на строку, завершающуюся нулём, содержащую имя компьютера. Устанавливая этот параметр в NULL, мы устанавливаем связь с локальным SCM (на этом компьютере).
lpDatabaseName
Указатель на строку, завершающуюся нулём, содержащую имя открываемой базы. Устанавливая этот параметр в NULL, мы устанавливаем связь с локальной, активной в текущий момент базой данных — SERVICES_ACTIVE_DATABASE.
dwDesiredAccess
Права доступа, запрашиваемые при открытии канала. Могут быть следующие значения:
SC_MANAGER_CONNECT (устанавливаются по умолчанию, параметр 0);
SC_MANAGER_CREATE_SERVICE (доступ для внесения в базу данных записи о новом драйвере);
SC_MANAGER_ALL_ACCESS (полный доступ).
Если эта функция возвращает не NULL (NULL говорит об ошибке), то мы получаем дескриптор активной базы данных. Следующий шаг — это регистрация нашего драйвера путём вызова функции CreateService, прототип функции выглядит так:
CreateService proto
hSCManager:HANDLE,
lpServiceName:LPSTR,
lpDisplayName:LPSTR,
dwDesiredAcces:DWORD,
dwServiceType:DWORD,
dwStartType:DWORD,
dwErrorControl:DWORD,
lpBinaryPathName:LPSTR,
lpLoadOrderGroup:LPSTR,
lpdwTagId:LPDWORD,
lpDependencies:LPSTR,
lpServiceStartName:LPSTR,
lpPassword:LPSTR
hSCManager
Дескриптор базы данных SCM.
lpServiceName
Указатель на строку, завершающуюся нулём, содержащую имя драйвера/сервиса. Длина до 256 символов. Соответствует имени подраздела в реестре.
lpDisplayName
Указатель на строку, завершающуюся нулём, содержащую экранное имя драйвера/сервиса. Длина до 256 символов. Соответствует значению параметра DisplayName в реестре.
dwDesiredAcces
Запрашиваемый тип доступа. Могут быть значения: SERVICE_ALL_ACCESS (полный доступ); SERVICE_START (доступ на запуск драйвера/сервиса); SERVICE_STOP (доступ на останов драйвера/сервиса); DELETE (доступ на удаление драйвера/сервиса из базы SCM).
dwServiceType
Тип сервиса, в нашем случае SERVICE_KERNEL_DRIVER.
Соответствует значению параметра TYPE в реестре.
dwStartType
Тип запуска, в нашем случае SERVICE_DEMAND_START.
Соответствует значению параметра START в реестре.
dwErrorControl
Характер контроля ошибок,
в нашем случае SERVICE_ERROR_IGNORE.
Соответствует значению параметра ErrorControl в реестре.
lpBinaryPathName
Указатель на строку, завершающуюся нулём, содержащую полный путь к файлу загружаемого драйвера. Соответствует значению параметра ImagePath в реестре.
lpLoadOrderGroup
Указатель на строку, завершающуюся нулём, содержащую имя группы, в случае если загружаемый драйвер является членом группы. В противном случае значение NULL или указатель на пустую строку.
lpdwTagId
Указатель на переменную, содержащую уникальное значение тега, идентифицирующее группу. В противном случае NULL.
lpDependencies
Указатель на массив имен драйверов или групп драйверов, загрузка которых должна быть осуществлена до запуска текущего драйвера. Массив заканчивается двумя нулями, имена драйверов или групп разделяются одним нулём. Если запуск драйвера не связан с предварительным запуском других драйверов, значение NULL.
lpServiceStartName
Указатель на строку, завершающуюся нулём, содержащую имя учётной записи (account), с правами которой запускается текущий драйвер. В случае типа сервиса SERVICE_KERNEL_DRIVER параметр содержит имя объекта драйвера. Если используется имя объекта драйвера, присвоенное подсистемой
lpPassword
Указатель на строку, завершающуюся нулём, содержащую пароль учётной записи, с правами которой запускается текущий драйвер. В случае SERVICE_KERNEL_DRIVER значение этого параметра игнорируется.
Остальные функции — StartService, DeleteService и CloseServiceHandle — запускают, удаляют и закрывают дескриптор нашего драйвера. Далее приводятся прототипы функций и описания их параметров.
StartService proto
hService:HANDLE,
dwNumServiceArgs:DWORD,
lpServiceArgVectors:LPSTR
hService
Дескриптор драйвера/сервиса.
dwNumServiceArgs
Количество аргументов, передаваемых сервису. Драйверу не передаются аргументы, поэтому значения NULL.
lpServiceArgVectors
Указатель на массив указателей, ссылающихся на строки, завершающиеся нулём. В строках содержатся передаваемые службе аргументы. В нашем случае аргументы отсутствуют, поэтому значение NULL.
DeleteService proto hService: HANDLE
hService
Дескриптор удаляемой службы.
CloseServiceHandle proto hSCObject:HANDLE
hSCObject
Дескриптор закрываемого SCM, сервиса, драйвера.
Прототип драйвера режима ядра
Рассмотрим шаблон простейшего драйвера (листинг 2).
Не правда ли, если Вы знакомы с динамически подключаемой библиотекой (DLL) [3], то приведённый пример во многом напомнит Вам структуру простейшей динамической библиотеки. Надо сказать, что между драйверами и DLL очень много общего. После загрузки драйвера операционная система передаёт управление на его точку входа. Предполагается, что выполняемая функция программы, которой передано управление сразу после загрузки драйвера, — это инициализация структур и переменных, необходимых для дальнейшей работы драйвера, и отчёт перед ОС о выполненной задаче («DriverEntry is the first routine called after a driver is loaded, and is responsible for initializing the driver», — читаем мы в MSDN). Точкой входа является метка, указанная после директивы End, то есть в нашем случае это EntryDriverUNIO (у Вашего драйвера может быть другое имя). В нашем примере после передачи управления на точку входа происходит формирование перехода из 0 в 1 на одном из выходов
Рассмотрим прототип DriverEntry (у нас это процедура EntryDriverUNIO).
DriverEntry proto
DriverObject:PDRIVER_OBJECT,
RegistryPath:PUNICODE_STRING
DriverObject
Указатель на объект созданного драйвера. Если говорить проще, то речь идёт о структуре типа DRIVER_OBJECT, описанной в файле NTDDK.h DDK. Часть полей в этой структуре необходимо заполнить загруженному драйверу, именно по этой причине ему и передаётся указатель на эту структуру. В нашем примере мы не занимались этой работой, так как «не задерживаемся надолго». В полнофункциональном драйвере эту работу придётся проделать.
RegistryPath
Указатель на структуру типа UNICODE_STRING, содержащую указатель на
Надо особо подчеркнуть, что, в отличие от пользовательского режима, в режиме ядра ОС работает только со строками типа UNICODE_STRING. Теперь несколько слов о компиляции и компоновке драйвера. Строка компиляции достаточно традиционна и, если мы работаем с masm32, выглядит следующим образом:
ML /nologo /c /coff CardUNIO.asm
Опции компоновщика, естественно, отличаются от опций для стандартного исполняемого файла:
LINK /nologo /driver /base:0×1000 /out:CardUNIO.sys /subsistem:native CardUNIO.obj
/driver
Выходной файл — драйвер.
/base:0×1000
Предопределённый адрес загрузки драйвера.
/out:CardUNIO.sys
Выходной файл драйвера должен иметь соответствующее расширение (по умолчанию — .exe).
/subsistem:native
Тип подсистемы, необходимый для работы выходного файла. Этого вопроса мы кратко касались в данной статье, в части, посвящённой обзору архитектуры Windows. Напомню, что речь шла о существовании трёх подсистем окружения: Win32, POSIX и OS/2. Параметр Native говорит о том, что нет необходимости ни в одной из этих подсистем. Драйвер работает в «родной» или естественной среде, то есть использует базовый API самой ОС Windows.
К сожалению, в статье
Заключение, или что впереди
В статье изложен минимально необходимый материал, для того чтобы обозначить основные направления развития темы написания драйверов в ОС Windows. Следующим шагом в создании полнофункционального драйвера должно быть создание объекта «устройство» функцией IoCreateDevice в фазе инициализации драйвера (в рамках процедуры EntryDriverUNIO) и достаточно полное изучение структуры DRIVER_OBJECT. Многие поля структуры — это просто указатели на процедуры обработки различных пакетов запросов на
Литература
-
В. Пирогов. Ассемблер для Windows. —
3-е изд. — СПб. :БХВ-Петербург , 2005. -
М. Руссинович, Д. Соломон. Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP, Windows 2000. —
4-е изд. — М. : Русская Редакция, Питер, 2005. -
Валерий Яковлев. Написание пользовательской DLL доступа к универсальному
OPC-серверу Fastwel // Современные технологии автоматизации. 2005. № 3. С. 74–81.
Автор — сотрудник фирмы ПРОСОФТ
Телефон: (812) 448–04-44
Факс: (812) 448–03-39
Если вам понравился материал, кликните значок - вы поможете нам узнать, каким статьям и новостям следует отдавать предпочтение. Если вы хотите обсудить материал - не стесняйтесь оставлять свои комментарии : возможно, они будут полезны другим нашим читателям!