- Драйверы устройств в системе WINDOWS
- Устройство
- Драйвер устройства в системе MS-DOS
- Драйвер устройства в системе Windows
- Библиотека DLL для драйвера
- Драйверный интерфейс API
- Интерфейс системы MS-DOS для защищенного режима
- Программа ISR в реальном режиме
- Приложение WINTEST
- Драйвер виртуального устройства
- События, управляющие устройством
- «Фиктивное» устройство
- Заключение
Драйверы устройств в системе WINDOWS
Драйверы устройств, как правило, — наиболее критическая часть программного обеспечения компьютеров. По иронии судьбы это также и наиболее скрытая часть разработки программного обеспечения. Драйверы устройств системы Windows фирмы Microsoft не являются исключением. Если вы когда-либо писали обычное приложение в системе Windows, то вам известно, что требуется определенное количество скрытых способов, чтобы приложение работало надежно. Как подмножество приложений Windows, драйверы устройств системы Windows следуют этому же правилу. В данной статье автор рассматривает работающий драйвер устройства, который обеспечивает доступ к портам ввода-вывода и обрабатывает прерывания, и виртуальный драйвер устройства (VxD), который имитирует технические средства. Предполагается, что читатель знает основы программирования в системе Windows, включая библиотеки динамической связи (dynamic link libraries — DLLs).
Устройство
Рассматриваемое устройство — это не часть технических средств, которая была разработана, чтобы продемонстрировать, как писать драйвер устройства в системе Windows. Скорее, это виртуальное устройство, полностью реализованное в программном обеспечении. Программа-пример выполняется только с виртуальным устройством, которое автор определил работая с системой Windows в расширенном режиме (Enhanced mode) процессора 386, и при условии, что установлен виртуальный драйвер устройства (VxD). Далее в статье более детально будет описан исходный код для этого устройства. На данный момент следует знать, что устройство имеет два порта: порт состояния и порт управления, оба на одном и том же адресе. На рис. 1 показаны биты, используемые в порте состояния. Бит 2 указывает, что имела место ошибка устройства, бит 1 показывает, что запрос на прерывание является отложенным, а бит 0 указывает, что устройство занято. Бит 7 говорит о том, что устройство есть в наличии. В этом случае данный бит равен нулю. Если же устройство не установлено или к нему нет доступа, то бит принимает значение, равное 1.
- PRESENT — устройство есть в наличии;
- ERROR — произошла ошибка устройства;
- IRQ — прерывание отложено;
- BUSY — устройство занято.
(Остальные биты игнорируются для дальнейшей совместимости.)
На рис. 2 показаны биты, используемые в порте управления. Бит 1 указывает устройству, что ЦПУ закончило обработку прерывания. Бит 0 показывает, что устройство может начать обработку ввода-вывода. (В данный момент не следует заострять внимание на том, что фактически устройство делает. Вместо этого, необходимо уделить внимание тому, как написать драйвер для такого устройства, которое обеспечивает аппаратные прерывания.)
- EOI — сигнал для устройства, подтверждающий прием прерывания;
- START — сигнал для устройства начинать пересылку ввода-вывода.
(Остальные биты должны быть установлены в 1 для дальнейшей совместимости.)
Драйвер устройства в системе MS-DOS
На листинге 1 показана программа dostest.asm, представляющая собой обычный драйвер устройства для системы MS-DOS, который общается с устройством. Несмотря на простоту и малый размер данная программа содержит основные компоненты драйвера устройства, который обрабатывает прерывания.
Драйвер устройства начинает работу с проверки старшего бита порта состояния, чтобы убедиться в наличии устройства. Затем он устанавливает связь с вектором прерывания MS-DOS для прерывания 11. Драйвер сохраняет предыдущее значение, хранимое в этом векторе, так чтобы можно было заменить значение, если программа существует.
Далее драйвер устройства выдает приглашение для пользователя : Start(начать) или Quit(выйти). Если пользователь нажимает S, программа начинает пересылку ввода-вывода. Если пользователь нажимает Q, то программа отключает устройство, восстанавливает вектор прерывания и завершается.
Чтобы начать операцию ввода-вывода, драйвер MS-DOS сначада размаскирует программируемый контроллер прерываний (programmable interrupt controller — PIC) для уровня прерывания устройства (в примере прерывание 11). Затем драйвер начинает операцию ввода-вывода для устройства путем записи 1 в бит 0 порта управления. Так как прерывания включены, то при возникновении прерываний на устройстве получит управление программа обслуживания прерываний (interrupt service routine — ISR).
Если происходит прерывание на устройстве, то программа ISR подтверждает прием прерывания, посылая значение EOI устройству (т.е. записывая 1 в бит 1 порта управления устройства) и контроллеру PIC. Если программа, выполняющая ввод-вывод, существует, то программа ISR выполняется. В противном случае программа ISR осуществляет инициализацию пересылки ввода-вывода вновь, записывая 1 в бит 0 порта управления устройства. Итак, программа ISR возобновляет ввод-вывод всякий раз, когда происходит прерывание, таким образом устройство непрерывно выполняет операцию ввода-вывода. Кроме обеспечения непрерывного ввода-вывода программа ISR увеличивает счетчик (dwCount1) всякий раз, когда обрабатывает прерывание.
В процессе выполнения ввода-вывода программа следит за счетчиком прерываний, отображает точку («.») для каждой законченной пересылки ввода-вывода и продолжает сканировать клавиатуру, чтобы определить, хочет ли пользователь остановить пересылку.
Чтобы завершить программу, пользователь нажимает клавишу Q. Программа устанавливает флаг, который информирует программу ISR о том, что следует остановить обработку. После того, как операция ввода-вывода остановлена, программа маскирует уровень прерывания в контроллере PIC и восстанавливает вектор прерывания.
Драйвер устройства в системе Windows
Чрезвычайно тривиальный драйвер устройства MS-DOS, описанный в предыдущем разделе, по существу довольно сложно реализовать в системе Windows. При написании драйвера устройства в системе Windows, обрабатывающего прерывания, необходимо использовать архитектуру, отличную от той, которая была использована для драйвера MS-DOS. В частности, необходимо отделить компоненту обработки прерывания от компоненты приложения. Вместо единственной программы, управляющей как программой ISR, так и интерфейсом пользователя, как сделано в системе MS-DOS, в системе Windows необходимо выделить эти функции в отдельные программные модули, называемые библиотекой динамической связи (DLL) и интерфейсом прикладных программ (Application Program Interface — API).
Библиотека DLL для драйвера
При написании приложений в системе Windows обычно в программном модуле имеют дело только с двумя типами сегментов: перемещаемым (moveable) и выгружаемым (discardable). Сегменты данных программы являются перемещаемыми, т.е. их линейные адреса в памяти могут изменяться, когда программе управления памятью системы Windows требуется организовать память. Селектор (selector) и смещение, используемые для доступа к определенной ячейке памяти, остаются фиксированными, но под схемой селектор-смещение система Windows может перемещать фактические данные в линейной памяти.
Сегменты программ-кодов также перемещаемые, но имеют дополнительный атрибут — выгружаемые. Их содержимое может быть выгружено полностью, а при необходимости загружено с диска, так как нельзя писать и (или) модифицировать информацию в сегменте программы-кода. Если при обращении к сегменту из программы Windows, он оказался выгруженным, программа управления памятью системы Windows автоматически обратится к диску и прочитает ранее выгруженный сегмент.
Итак, каким образом это обстоятельство влияет на код для программы ISR? Так как прерывание может произойти в любое время, а код ISR может оказаться выгруженным, то возникнет проблема загрузить код в память, если фиксируется прерывание. Вместо этого, можно описать сегмент как FIXED (фиксированный), а не как MOVEABLE (перемещаемый) или DISCARDABLE (выгружаемый). Сегмент с атрибутом FIXED будет оставаться в единственном месте линейной памяти и не будет выгружаться, даже если он содержит код. В этом случае, если произойдет прерывание, код будет доступен и готов к выполнению. Однако следует отметить один малоизвестный факт, а именно: в системе Windows только те сегменты будут считаться FIXED, которые были описаны в библиотеке DLL. Сегмент FIXED в обычном программном модуле будет рассматриваться как MOVEABLE. Таким образом в системе Windows нельзя будет поместить программу ISR в обычный программный модуль. Вместо этого ее необходимо поместить в библиотеку DLL.
Листинг 2 представляет исходный код bogusa.asm на ассемблере для библиотеки DLL, который содержит программу ISR и может выполняться в окружении Windows. Программа IntSvcRtn очень похожа на свой дубликат, работающий в системе MS-DOS. Однако кроме увеличения переменной-счетчика данная программа ISR также записывает в очередь сообщение Windows. Чтобы избежать переполнения очереди, запись сообщения производится только в случае, когда переменная-счетчик wCount изменяет значение от 0 к 1. Функция обнуления счетчика wCount после того, как закончена обработка сообщения, передана высокоуровневой программе системы Windows.
С первого взгляда все эти рассуждения кажутся простыми, однако обработка прерываний в системе Windows совсем не так проста, как в системе MS-DOS.
Драйверный интерфейс API
Кроме отдельного программного модуля для программы ISR (в форме библиотеки DLL системы Windows), для работы драйвера необходим также программный модуль пользовательского интерфейса, называемый интерфейс API. На листинге 3 приведена программа bogus.h, представляющая собой пример интерфейса API. Эта программа содержит 4 точки входа в библиотеку DLL.
В точке входа BogusCheck просто проверяется наличие устройства. Программа возвращает значение TRUE, если устройство обнаружено (бит 7 порта состояния), и значение FALSE в противном случае.
Точки входа BogusStart и BogusStop начинают и завершают работу устройства. Кроме того, точка входа BogusStart разрешает прерывания и обеспечивает связь с аппаратным прерыванием, а точка входа BogusStop выключает прерывания устройства и восстанавливает аппаратное прерывание.
Точка входа BogusGetEvent возвращает количество прерываний, обработанных со времени первого старта устройства, либо со времени последнего вызова точки входа BogusGetEvent. (Точка входа BogusGetEvent обнуляет счетчик прерываний при каждом ее вызове.)
Прерывания при стандартном режиме работы системы Windows
При написании драйвера, который будет выполняться в стандартном режиме работы системы Windows, необходимо учитывать возможность появления прерывания, когда процессор работает в реальном режиме. Даже если работают только приложения системы Windows, а не приложения системы MS-DOS, процессор часто переключается из реального в защищенный режим. Так как система Windows 3.1 не является операционной системой, а скорее представляет собой окружение пользовательского интерфейса, она возлагает выполнение определенного количества основных функций, включая функцию ввода-вывода файлов, на операционную систему (а именно MS-DOS).
Поэтому когда приложение системы Windows выполняет функцию MS-DOS ввода-вывода файла и процессор при этом работает в реальном режиме, устройство может прерывать ЦПУ. По умолчанию, если библиотека DLL обеспечила связь с прерыванием, то система Windows переключит ЦПУ в защищенный режим для обработки прерывания и, как только программа ISR завершит работу, переключит ЦПУ обратно в реальный режим для продолжения выполнения функций системы MS-DOS.
Хотя это в меньшей мере относится к ЦПУ 80386, переключение процессора из защищенного режима в реальный режим, например на процессоре 80286, создает огромные накладные расходы, требующие контролируемого сброса ЦПУ, который выполняется в течении миллисекунд. Если необходимо ускорить среднее время ответа, нужно предотвратить переключение процессора в защищенный режим, если он получает прерывание, работая в реальном режиме.
Обеспечение связи с вектором прерывания в защищенном режиме из библиотеки DLL системы Windows — тривиально, что и показано в программе SetPMVector, представленной в листинге 4 (программа bogus.c). Установление связи с вектором производится таким же способом, как и в системе MS-DOS, — с помощью функции setvector системы MS-DOS. Однако в отличие от вызова в системе MS-DOS, в системе Windows при обращении к функции передаются селектор и смещение, а не сегмент и смещение. Ядро системы Windows следит за всем. Функции следует передать нормальный селектор и смещение (натуральный указатель far для системы Windows), а не сегмент и смещение (натуральный указатель far для системы MS-DOS).
Однако, как уже упоминалось, установления связи с вектором прерывания в защищенном режиме недостаточно. Необходимо также обеспечить связь с вектором прерывания в реальном режиме, а это не тривиальная задача.
Интерфейс системы MS-DOS для защищенного режима
Чтобы установить связь с вектором реального режима из кода системы Windows защищенного режима, необходимо работать с интерфейсом системы MS-DOS для защищенного режима (MS-DOS Protected Mode Interface — DPMI). (Текущая версия DPMI представляет собой уровень 1.0, но система Windows наиболее полно реализует только уровень 0.9. Некоторые функции уровня 1.0 реализованы в системе Windows 3.1.)
Функция DPMI_SetRMVector вызывает интерфейс DPMI, чтобы установить вектор реального режима. Можно видеть, что интерфейс DPMI взаимодействует через регистры (регистр AX содержит функциональный код) и INT31h. Автор включил высокоуровневый интерфейс в данную и другие функции DPMI (доступен только на диске кодов или в интерактивном режиме), чтобы можно было иметь доступ к интерфейсу DPMI из языка Си и выделил код, написанный на языке ассемблер, на случай, если возникнет необходимость использовать что-то отличное от компилятора Си фирмы Microsoft.
Функция DPMI_AllocateRMCallback вызывает интерфейс DPMI, чтобы распределить обратный вызов (callback), представляющий собой адрес, вызываемый из реального режима, который передает управление коду защищенного режима. Например, программа TSR системы MS-DOS может вызвать код в библиотеке DLL системы Windows через обратный вызов.
Функция DPMI_AllocateRMCallback принимает два параметра: адрес кода защищенного режима, который будет вызываться обратно, и регистровую структуру, которая обновляется при выполнении реального обратного вызова, таким образом код защищенного режима может исследовать содержимое регистров реального режима во время обратного вызова.
Функция DPMI_FreeRMCallback освобождает все структуры, которые были распределены в результате обращения к функции DPMI_AllocateRMCallback. Функция DPMI_FreeRMCallback должна вызываться только тогда, когда больше нет необходимости в обратном вызове.
Программа ISR в реальном режиме
Несмотря на то, что автор рекомендовал обеспечивать раздельную программу ISR в реальном режиме, в данном примере эта рекомендация не была выполнена. Вместо этого, автор предоставил программы, необходимые при реализации программы ISR на языке Си. Фактически, данный пример устанавливает связь с прерываниями реального режима только для того, чтобы переключить ЦПУ в защищенный режим для обработки прерывания. Таково по умолчанию поведение системы Windows, когда с прерываниями реального режима не устанавливается связь вообще, таким образом автор рассматривает несколько циклов, которые не имеют никакого другого назначения, кроме как показать, каким образом все работает.
Рассмотрим код для точки входа BogusStart. По существу он работает так же, как работал бы в системе MS-DOS. Код сохраняет старое значение прерывания, обеспечивает связь с текущим значением и подает устройству знак начать работу. Однако вместо обеспечения связи только с вектором защищенного режима, он устанавливает связь как с вектором реального режима, так и с вектором защищенного режима. Устанавливая связь с вектором реального режима, код вызывает AllocIntReflector, чтобы обеспечить ссылку вектора прерываний реального режима на обратный вызов, который просто обращается к программе ISR защищенного режима. Точка входа BogusStart подает знак устройству начинать работу одинаковым образом при обоих режимах работы: защищенном и реальном. Она размаскирует бит IRQ для контроллера PIC и подает знак устройству начинать работу, записывая 1 в бит START порта управления устройством. Как только приложение обращается к данной программе, начинается обработка прерываний и регистрация сообщений в соответствии с программой ISR.
Программа BogusStop тривиальна и просто отключает устройство и разрывает связи, установленные программой BogusStart. Итак, осталось привести пример прикладной программы, чтобы показать работу операций ввода-вывода.
Приложение WINTEST
Приложение wintest.c, показывающее работу ввода-вывода (см. листинг 5), состоит главным образом из немодульного диалогового блока, в котором непрерывно высвечивается количество прерываний, обработанных с начала работы программы.
Программа MainDlgProc вызывает программу BogusStart во время выполнения WM_INITDIALOG, передавая в качестве параметра обработчик окна диалогового блока. Программа ISR регистрирует сообщения к данному обработчику в тех случаях, когда счетчик прерываний изменяется от нуля к единице.
Программа MainDlgProc сохраняет текущее суммарное значение счетчика в переменной wCountTotal. Всякий раз, когда диалог получает сообщение WM_COMMAND с параметром wParam, равным IDM_BOGUSEVENT, программа обновляет суммарный счетчик, отображаемый в диалоговом блоке. Следует отметить, что хотя программа ISR регистрирует сообщение только тогда, когда счетчик изменяется от нуля к единице, возможна (и весьма вероятно) обработка количества прерываний до того, как сообщение WM_COMMAND фактически будет передано диалоговой процедуре. Методика, показанная в данной программе, при которой программа ISR регистрирует сообщение только при первом переходе, а программа BogusCheck чистит счетчик, обеспечивает точный подсчет количества прерываний, даже если на уровне приложения нельзя учесть каждое прерывание в момент его возникновения.
При выполнении данной программы можно наблюдать, что счетчик прерывания в диалоговом блоке непрерывно увеличивается, указывая количество выполненных операций ввод-вывода.
Драйвер виртуального устройства
Файл vxd2.asm (листинги 6 и 7) представляет собой исходный код драйвера фиктивного устройства. Следует отметить, что для того, чтобы построить этот драйвер, необходимо иметь комплект драйверов устройств (Device Driver Kit — DDK) системы Windows фирмы Microsoft, т.к. код написан для 32-битового ассемблера, предусмотренного в комплекте DDK (MASM5). Результирующий модуль может быть скомпонован только DDK-компоновщиком LINK386 и утилитой послекомпоновочной обработки ADDHDR. Кроме того, данный исходный код ссылается на определенное количество включаемых файлов (include files), которые входят в состав только комплекта DDK.
Как было указано, типичный драйвер VxD содержит обязательные включаемые файлы, а кроме того он начинается с вызова макроса Declare _Virtual_Device, который создает блок данных, описывающий виртуальный драйвер для ядра системы Windows. Этот блок данных, фактически, -единственное обозначение, экспортируемое из драйвера VxD. Все остальные точки входа являются производными от данных, содержащихся внутри. Кроме всего прочего, данный макрос описывает имя устройства, порядок его инициализации и его точки входа. Виртуальный драйвер VxD может обслуживать запросы приложений как в реальном, так и в защищенном режимах. Точки входа для такого обслуживания также описываются данным макросом.
События, управляющие устройством
По мере того, как система Windows в своей работе проходит различные стадии, начиная со стадии инициализации самой системы, через инициализацию виртуальной машины VM и так далее, каждый установленный драйвер VxD вызывается неоднократно, а именно один раз на каждую стадию. В таблице, приведенной ниже, перечисляются фазы системы Windows и главные события, для которых вызывается каждый драйвер VxD.
Драйвер VxD примера выполняет управление только фазой Device_Init. На этой стадии устанавливается связь с портом ввода-вывода и уровнем прерывания 11, а также производится их виртуализация. Обычно драйвер VxD виртуализирует порты ввода-вывода и прерывание в соответствии с физическим аппаратным оборудованием. Но в данном случае драйвер VxD может виртуализировать и делает это с портом и прерыванием, которые не имеют соответствующего подключенного аппаратного оборудования.
Код Install_IO_Handler вызывается, чтобы виртуализировать единственный порт ввода-вывода. Затем всякий раз, когда осуществляется доступ к описанному порту ввода-вывода из виртуальной машины VM, программа управления виртуальной машиной системы Windows (Virtual Machine Manager — VMM) вызывает обратно драйвер VxD для того, чтобы разрешить ему имитировать операции ввода-вывода.
Код VPICD_Virtualize_IRQ вызывается, чтобы виртуализировать уровень прерывания. Выполняя его, можно имитировать прерывание аппаратного оборудования (в частности IRQ 11) в виртуальной машине.
«Фиктивное» устройство
Когда к порту ввода-вывода (141) устройства осуществляется доступ виртуальной машиной VM (либо в реальном, либо в защищенном режиме), то машина вызывает программу драйвера VxD Port_IO_Callback (см. Листинг 6). В этой программе подпрограмма Dispatch_Byte_IO сводит большое количество возможных типов доступа ввода-вывода (а именно: byte, word, dword, string и т.д.) к двум: байтовому вводу и байтовому выводу.
Для устройства из примера байтовый ввод представляет собой чтение из регистра состояния устройства. Он возвращает просто переменную, которая сохраняется в памяти.
Байтовый вывод — немного более сложная операция, так как представляет фактическую работу устройства. При запуске устройства также запускается таймер, который выполняет обратный вызов (к коду TimeoutProc) в течении 1/10 секунды и устанавливает состояние BUSY. Если вывод подтверждает прием прерывания, то производится очистка виртуального запроса на прерывание путем вызова кода VPICD_Clear_Int_Request и очистка состояния в регистре состояния.
Обратный вызов кода TimeoutProc представляет завершение операции ввода-вывода на устройстве и именно в данный момент он моделирует прерывание аппаратного оборудования к виртуальной машине VM путем вызова кода VPICD_Clear_Int_Request и очистки состояния занятости устройства. Драйвер устройства в приложениях dostest и wintest будет обычно обрабатывать прерывание путем подтвержения приема его (посылая EOI) и повторного запуска процесса на всем протяжении снова.
Следует отметить процедуры VxD2_VInt_Proc и VxD2_IRET _Proc. На данные две процедуры существует ссылка в структуре, которая передается коду VPICD_Virtualize_IRQ. Они вызываются в начале и конце процесса виртуализации прерывания в виртуальную машину VM. Все их функции сводятся к увеличению и сохранению приоритета виртуальной машины VM, которая временно обрабатывает данное прерывание. Таким способом драйвер VxD может управлять приоритетом виртуальной машины VM, которая считается соответствующей. (Всегда желательно, чтобы программа обслуживания прерывания в любой виртуальной машине VM имела приоритет выше, чем приоритет обычной обработки в других виртуальных машинах VM.)
Установка драйвера VxD
После построения драйвера VxD, до первого обращения к нему программы Windows необходимо добавить его как строку device= в секцию [386Enh] кода system.ini. Система Windows должна быть запущена заново, чтобы включить драйвер VxD и виртуальное устройство. После этого, можно выполнять и тестировать приложения dostest и wintest.
Заключение
Хотя драйверы устройств системы Windows кажутся в настоящее время очень сложными, обычные и виртуальные драйверы устройств предоставляют огромное количество возможностей. Однако следует учитывать, насколько более сложными они должны быть на машине MIPS, эксплуатирующей систему Windows NT и код эмулятора 80×86, чтобы обеспечить работу виртуальной машины системы MS-DOS.