Назначение и функции драйверов. Драйвер: это что такое и зачем он нужен

  • 10. Управление оперативной памятью (оп). Менеджер памяти; swapping; виртуальная память.
  • 11. Модели организации виртуальной памяти.
  • 12. История ос unix.
  • 13. Общая архитектура unix. Основные подсистемы ядра.
  • 14. Пользовательская среда unix.
  • Командный интерпретатор shell.
  • 16. Система каталогов в oс unix. Управление с помощью команд языка Bourn shell.
  • 17. Управление файлами с помощью команд языка Bourn shell. Перенаправление ввода/вывода.
  • 19. Обработка аргументов командной строки. Переменные окружения.
  • 20. Пользователь и группа. Права доступа к файлу.
  • 21. Системные вызовы и функции стандартных библиотек. Обработка ошибок.
  • 22.Структура программы на языке с. Параметры главной функции (пример).
  • 23. Файловая система ос unix: монтирование, индексные дескрипторы, жесткие и символические ссылки, файлы устройств.
  • 24.Системные вызовы для работы с файлами
  • 25. Понятие «процесс» в ос unix. Контекст процесса; свойства процесса; состояние процесса.
  • 26.Создание процессов и упр-е ими.
  • 27. Запуск внешней программы
  • 29.Общая классификация средств взаимодействия процессов в ос unix.
  • 30.Иерархия процессов в ос unix. Понятие сеанса. Фоновые процессы.
  • 31.Каналы – средства взаимодействия процессов. Неименованные каналы. Организация конвейера (пример программы).
  • 33.Сигналы как средство взаимодействия процессов в ос unix. Диспозиция сигналов.
  • 34.Ограничения для процесса в ос unix (по ресурсам). Связь со свойствами процесса.
  • 36.Отображение файлов в виртуальное адресное пространство. Разделяемая память.
  • 37. Взаимодействие процессов через псевдотерминал.
  • 38. Недостатки потокового взаимодействия процессов. Средства System vipc. Пространство имен. Общие принципы работы со средствами System vipc.
  • 39. Организация очереди сообщений в ос unix. Структура сообщения. Отправка и принятие сообщений.
  • 40.Семафоры, как средство взаимодействия процессов System vipc. Понятие атомарной операции. Массив семафоров.
  • 41.Разделяемая память, как средство взаимодействия процессов System vipc.
  • 42.Взаимодействие по сети. Понятие протокола. Семейства адресации и типы взаимодействия. Создание сокета в ос unix.
  • 45. Потоковые сокеты. Клиент – серверная модель.
  • 46. Проблема очередности действий и ее решение.
  • 47. Процессы-демоны. Система журнализации.
  • 48. Загрузка и жизненный цикл в ос unix.
  • 49. Взаимоисключения. Понятие критической секции. Устаревшие подходы к организации взаимного исключения.
  • 50.Поддержка взаимоисключения на уровне ос. Мьютексы и семафоры (Дейкстры). Команда ассемблера tsl.
  • 51.Проблема тупиков. Граф ожидания
  • 52. Нити исполнения (pthreads) в ос unix. Мьютексы pthreads.
  • 53.Графический интерфейс в ос unix. Базовые принципы построения x_window.
  • 54.Файловая подсистема. Общая структура. Методы выделения дискового пространства. Управление дисковым пространством.
  • 55.Файловая подсистема. Структура файловой системы на диске. Реализация директорий. Поиск в директории (хеширование).
  • 56.Подсистема ввода/вывода. Схема взаимодействия подсистем ос. Понятие драйвера. Типы драйверов.
  • 56.Подсистема ввода/вывода. Схема взаимодействия подсистем ос. Понятие драйвера. Типы драйверов.

    Подсистема управления вводом-выводом позволяет процессам поддерживать связь с периферийными устройствами, такими как накопители на магнитных дисках и лентах, терминалы, принтеры и сети, с одной стороны, и с модулями ядра, которые управляют устройствами и именуются драйверами устройств, с другой. Между драйверами устройств и типами устройств обычно существует однозначное соответствие: в системе может быть один дисковый драйвер для управления всеми дисководами, один терминальный драйвер для управления всеми терминалами и один ленточный драйвер для управления всеми ленточными накопителями. Драйверы обеспечивают интерфейс между ядром ос Unix и аппаратными частями компьютера (скрыта архитектура). Часть драйвера-доступ к физическим устройствам, другие предоставляют аппар независимые услуги /dev/null. В процессе запуска ос ядро вызывает соответствующий процесс инициализации установленных драйверов. Типы драйверов (различаются возможностями и способами доступа, управления)

      Символьные (работа с устройствами, побайтовый доступ и обмен данными для модемов, принтеров, терминалов, доступ не включает использование буферного КЭШа)

      Блочные производит обмен данными с устройствами фиксированными порциями, те порциями (например жесткий диск). Используется буферный кэш, он является интерфейсом между файловой системой и устройствами.

      Драйвера низкого уровня. Тип интерфейса позволяет производить обмен с блочными устройствами минуя буферный КЭШ. Устройство мб адресовано элементами размер которых не совпадает с размером блока. Обмен данными не зависит от файла подсистемы и буферного КЭШа.

    Управляющая схема взаимодействия драйвера устройств с другими подсистемами ОС Unix .

    как видно из схемы не все драйверы предназначены для работы с физическими устройствами. Часть служит для предоставления различных услуг ядра прикладным процессам. Такие драйверы программные или драйверы псевдоустройств /dev/kmem. Доступ к виртуальной памяти ядра. Зная адреса внутренней структуры ядра процесс может считывать хранимую там информацию. С помощью драйвера может быть реализована версия утилиты БС которая выводит информацию о версии процесса (/dev/mem доступ к физической памяти, /dev/zero обеспечивает заполнение 0ми определенного буфера. Для инициализации определенной области памяти. Базовая архитектура драйвера Адресуемся старшим номером устройства среди атребут файловых устройств, наряду с другими. Младший номер интерпретируется наряду с драйверами. В то время как доступ к любому разделу производится через старший номер. Младший номер указывает к какому разделу доступ. Доступ к другим осуществляется ядром через структуру данных (коммуникатор устройств) Каждый элемент содержит указатель на соответствующие функции драйвера. Указатели называются точками входа. Старшее число в структуре указатель на коммуникатор (разделяется для блочных и символьных устройств) bde vsw блочные; cdcvew символьные. Ядро размещает отдельный массив для каждого типа коммуникаторов. Любой драйвер имеет доступ в соответствующем массиве. Если драйвер имеет как символьный так и блочный доступ, то точки входа фиксируются и в том и в другом массиве. Коммуникатор определяет интерфейс драйвера устройства. Каждый драйвер обеспечивает соотношение реализаций функций интерфейса. Если драйвер не поддерживает ф-й стандартного интерфейса тогда он заменен соответствующими точками, специальными заглушками, предоставляемыми ядром. Когда ядру надо запросить операц у драйвера устройств, ядро определяет элемент коммуникатора, затем вызывает требуемую функцию.

    Имя точки входа XX open () (конкретный драйвер, действие). Соответствует вызову при выполнении каждой операции, открытие устройства. Обеспечивает необходимость реинициализации для физического устранения и внутренних данных драйвера.

    Можно выделить 5 основных случаев в которых ядро обращается к функциям драйвера

      Автоконфигурация (инициализация ОС Unix)

      ВВ/выв запрос на операцию вв/выв прикладных процессов

      Обработка прерываний

      Специальные запросы

      Реинициализация и остальное

    Некоторые типы аппаратных архитектур требуют сброса и реинициализации устройств. Вызов при сбросе.

    Встраивание драйвера в ядро

    Есть два метода встраивания кода и данных драйвера в ядро ос:

      Перекопиляция ядра

    Динамическая установка драйвера требует выполнения следующих операций: размещение и динамическое связывание драйвера, инициализация драйвера и устройства, добавление т входа драйвера в соответствующий коммутатор устройств. Установка обраб прерывания

    Если перекомпиляции не отличаются от компиляции обычной программы, все составляющие ядра являются объектами модуля (для получения исполнительного файла).

    1.5 Структура драйвера устройства Windows

    Все драйверы устройств Windows имеют одинаковую структуру. Каждый драйвер имеет объект драйвера, который создается диспетчером ввода-вывода при загрузке драйвера. В разделе 1.4 представлены структуры данных, которые относятся к драйверам устройств, в том числе и объекты драйверов. В этом разделе описываются процедуры, реализуемые драйвером, а также другие характеристики драйвера устройства хранения данных.

    Драйвер устройства Windows реализует множество стандартных процедур, причем некоторые из них обязательны для выполнения, а некоторые – нет, что зависит от свойств драйвера. Ниже перечислены основные стандартные процедуры.

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

    Обязательный набор процедур диспетчеризации для обеспечения работы определенных функций, например чтения, записи, создания и закрытия файлов. Эти процедуры вызываются диспетчером ввода-вывода и получают в качестве параметра пакет запроса ввода-вывода.

    Необязательная процедура запуска (startup routine – StartIO), которая инициирует ввод-вывод данных на физическое устройство. Очевидно, что только драйверы, работающие непосредственно с физическими устройствами (это касается не всех драйверов такого типа), требуют наличия такой процедуры.

    Необязательная процедура обслуживания прерывания (interrupt service routine – ISR). Может использоваться драйверами, взаимодействующими с физическими устройствами. Процедуры обслуживания прерываний рассматриваются в разделе 1.5.1.

    Необязательный отложенный вызов процедуры " (deferred procedure call – DPC), который может использоваться драйвером для дополнительной обработки процедуры обслуживания прерывания. Отложенный вызов процедуры рассматривается в разделе 1.5.2.

    Необязательная процедура завершения, которая вызывается диспетчером ввода-вывода (в качестве механизма уведомления), когда драйвер более низкого уровня завершает обработку пакета запроса ввода-вывода. Поскольку вся операция ввода-вывода обрабатывается в качестве асинхронной, процедура завершения используется довольно часто, особенно в высокоуровневых драйверах, которые всегда обеспечивают обработку пакетов IRP более низкого уровня.

    Обязательная процедура выгрузки, которая вызывается диспетчером ввода-вывода для выгрузки драйвера.

    Необязательная процедура отмены (cancellation routine – CancellO), которая вызывается диспетчером ввода-вывода для отмены выполнения длительной операции.

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

    Необязательная процедура протоколирования ошибок.

    Обработка пакета запроса ввода-вывода совершается драйвером различными способами, в зависимости от структуры драйвера и запроса ввода-вы- вода в пакете. Ниже приведены некоторые примеры работы драйвера.

    Выполнение запрошенной операции и завершение обработки IRP.

    Выполнение элемента операции и передача IRP драйверу более низкого уровня.

    Обычная передача IRP драйверу более низкого уровня.

    Генерация нескольких пакетов IRP для драйвера более низкого уровня в ответ на получение одного пакета IRP. Например, в ответ на запрос об открытии файла, поступивший от драйвера NTFS, драйверу может потребоваться считать метаданные файла для поиска каталога и подкаталогов, в которых расположен необходимый файл.

    Обычно драйверы получают доступ к своей области стека в пакете IRP, а также к области стека следующего драйвера. Самый нижний драйвер в цепочке стека получает доступ только к своему фрагменту стека в пакете IRP. Драйвер отвечает за изменение указателя в пакете IRP, который указывает на область стека, используемую следующим драйвером.

    Обратите внимание, что один и тот же код драйвера может одновременно выполняться на разных центральных процессорах в одной системе Windows NT. Код драйвера должен обладать возможностью синхронизировать доступ к критическим данным кода, выполняемого на разных процессорах. Иногда повторное выполнение одного запроса может стать просто катастрофой, например при записи на магнитную ленту одних и тех же данных повторно.

    1.5.1 Процедура обслуживания прерывания

    Процедура обслуживания прерывания (interrupt service routine – ISR) обычно выполняется в ответ на получение прерывания от аппаратного устройства и может вытеснять любой код с более низким приоритетом. Процедура обслуживания прерывания должна использовать минимальное количество операций, чтобы центральный процессор имел свободные ресурсы для обслуживания других прерываний. Эта процедура собирает минимум необходимой информации и размещает в очереди вызов отложенной обработки (deffered processing call – DPC) для завершения обслуживания прерывания. Запуск вызова отложенной обработки не планируется на определенное время, т.е. вызов может быть запущен как немедленно, так и немного позднее, в зависимости от необходимости в другой обработке.

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

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

    1.5.2 Вызов отложенной обработки

    При запуске от процедуры обслуживания прерывания требуется быстрое и эффективное выполнение поставленной задачи. Таким образом, процедура обслуживания прерывания проводит минимум операций и размещает в очереди запрос на вызов отложенной обработки , который используется для завершения оставшихся операций с низким уровнем приоритета (эти уровни обычно называются IRQ или IRQL). Вызов отложенной обработки может быть размещен в очереди не только из процедуры обработки прерывания. Запрос к очереди создает новый объект вызова отложенной обработки (средствами диспетчера объектов). После размещения в очереди создается аппаратный запрос на прерывание (IRQ level 2) для вызова отложенной обработки.

    Ниже описаны некоторые важные свойства вызова отложенной обработки.

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

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

    Вызов отложенной обработки не должен выполнять блокирующие действия, например блокирующий ввод-вывод.

    Вызов отложенной обработки напоминает процедуру обработки прерывания, поскольку также должен выполняться быстро и эффективно. Для минимизации нагрузки на систему при планировании вызовов отложенной обработки Windows NT перед передачей управления DPC сохраняет минимальную информацию о состоянии. После завершения DPC восстановление состояния также занимает мало времени, так как при передаче управления сохранялся минимум информации. В результате DPC может выполняться в контексте произвольного процесса. Например, если программа Excel выполняется в виде процесса и запускает процедуру ввода-вывода, вызов отложенной обработки (если он потребуется) может запускаться в контексте процессов Word или PowerPoint (а не обязательно в контексте процесса Excel).

    Каждый процесс имеет собственную очередь вызовов отложенной обработки. Таким образом, многопроцессорный компьютер с четырьмя центральными процессорами будет иметь четыре отдельных очереди DPC. Вызов отложенной обработки может иметь высокий, средний и низкий приоритет; по умолчанию присваивается средний приоритет. Драйвер может изменить значение приоритета. Вызов отложенной обработки

    с высоким приоритетом размещается в начало очереди, a DPC с низким и средним приоритетом – в конец очереди.

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

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

    Вызов отложенной обработки может быть размещен в очереди другого процессора, если очередь DPC текущего процессора превышает определенное значение. Ядро Windows NT периодически пытается выполнить вызов отложенной обработки, генерируя программные прерывания.

    Вызов отложенной обработки не может быть выгружен на диск.

    1.5.3 Асинхронный вызов процедуры

    Асинхронный вызов процедуры (asynchronous procedure call – АРС) немного похож на вызов отложенной обработки, но существуют и заметные различия. Как и вызов отложенной обработки, АРС выполняется на уровне привилегий, превышающем уровень привилегий обычного кода. В отличие от вызова отложенной обработки, выполняемого в контексте произвольного процесса, асинхронный вызов процедуры всегда выполняется в контексте определенного процесса. Таким образом, асинхронный вызов процедуры требует больших затрат, чем вызов отложенной обработки, так как приходится сохранять и восстанавливать большее количество параметров. Читателю, знакомому с операционными системами UNIX, асинхронные вызовы процедур напомнят процедуры обработки сигналов UNIX.

    Существует два типа АРС: вызов в режиме ядра и вызов в пользовательском режиме. Асинхронный вызов процедуры в режиме ядра связан с драйвером или другим кодом режима ядра и обычно используется для передачи данных, например для копирования данных из буфера ядра в пользовательский буфер. Помните, что пользовательский буфер должен быть доступен в контексте процесса, который владеет буфером.

    Код пользовательского режима тоже может использовать асинхронный вызов процедур. Для этого необходим прикладной интерфейс программирования QueueUserAPC, который рассматривается в документации к набору Platform SDK. Асинхронные вызовы процедур в пользовательском режиме предоставляются только тогда, когда поток получает предупреждение, например при блокировании в результате вызова функций WaitForSingleObject или WaitForMultipleObject. Подробная информация об этих функциях доступна в документации Platform SDK. Достаточно сказать, что эти функции позволяют организовать синхронизацию потоков.

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

    Драйвер устройства выполняет несколько функций:

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

    Инициализацию устройства.

    Управление энергопотреблением устройства и регистрацией событий.

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

    Проверку использования устройства в данный момент. Если устройство занято, запрос может быть поставлен в очередь. Если устройство свободно, проверяется состояние устройства. Возможно, требуется включить устройство или запустить двигатель, прежде чем начнется перенос данных. Как только устройство готово, может начинаться собственно управление устройством.

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

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

    По завершении выполнения операции драйвер должен проверить, завершилась ли операция без ошибок. Если все в порядке, драйверу, возможно, придется передать данные (например, прочитанный блок) независимому от устройств программному обеспечению. Затем драйвер возвращает некоторую информацию вызывающей программе о завершении операции. Если в очереди находились другие запросы, один из них теперь может быть выбран и запущен, в противном случае драйвер блокируется в ожидании следующего запроса.

    Описанная модель является упрощенным приближением к реальности. На самом деле программа значительно сложнее, и причиной тому служит большое количество разнообразных факторов.

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



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

    Главный вопрос операционной системы - как сделать так, чтобы все устройства ввода-вывода и драйверы выглядели примерно одинаково. Если диски, принтеры, клавиатуры и т. д. требуют различных интерфейсов, то при появлении нового устройства будет требоваться переделка операционной системы, что естественно очень неудобно.

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

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

    Другой аспект единообразного интерфейса состоит в именовании устройств ввода-вывода. Независимое от устройств программное обеспечение занимается отображением символьных имен устройств на соответствующие драйверы. Например, в системе UNIX имя устройства однозначно указывает на специальный файл, содержащий номер главного устройства , использующийся для определения расположения подходящего драйвера. Этот файл также содержит номер второстепенного устройства , передаваемый в виде параметра драйверу для указания конкретного диска или раздела диска, к которому относится операция чтения или записи. Все устройства в системе UNIX имеют главный и второстепенный номера, по которым они однозначно идентифицируются. Выбор всех драйверов осуществляется по главному номеру устройства.

    С именованием устройств тесно связан вопрос защиты. Как ОС предотвращает доступ пользователей к устройствам, на который у них нет прав? В UNIX и в Windows 2000 устройства представляются в файловой системе в виде именованных объектов, что дает возможность применять обычные правила защиты файлов к устройствам ввода-вывода. Системныйадминистратор может установить нужные разрешения для каждого устройства.

    Драйверы внешних устройств

    Драйвер (driver) представляет собой специализированный программный модуль, управляющий внешним устройством. Слово driver происходит от глагола to drive (вести) и переводится с английского языка как извозчик или шофер: тот, кто ведет транспортное средство. Драйверы обеспечивают единый интерфейс для доступа к различным устройствам, тем самым устраняя зависимость пользовательских программ и ядра ОС от особенностей аппаратуры. Драйвер не обязательно должен управлять каким-либо физическим устройством. Многие ОС предоставляют также драйверы виртуальных устройств или псевдоустройств – объектов, которые ведут себя аналогично устройству ввода-вывода, но не соответствуют никакому физическому устройству. В виде псевдоустройств реализуются трубы в системах семейства Unix и почтовые ящики в VMS. Еще одним примером полезного псевдоустройства являются устройства /dev/null в Unix и аналогичное ему \DEV\NUL в MS DOS\Windows\OS/2.

    Прикладные программы, использующие собственные драйверы, не так уж редки – примерами таких программ могут быть GhostScript (свободно распространяемый интерпретатор языка PostScript, способный выводить программы на этом языке на различные устройства.

    Большинство ОС общего назначения запрещают пользовательским программам непосредственный доступ к аппаратуре. Это делается для повышения надежности и обеспечения безопасности в многопользовательских системах. В таких системах драйверы являются для прикладных программ единственным способом доступа к внешнему миру. Еще одна важная функция драйвера – это взаимоисключение доступа к устройству в средах с вытесняющей многозадачностью. Допускать одновременный неконтролируемый доступ к устройству нескольких параллельно исполняющихся процессов просто нельзя, потому что для большинства внешних устройств даже простейшие операции ввода-вывода не являются атомарными. Например, в большинстве аппаратных реализации последовательного порта RS232 передача байта состоит из четырех шагов: записи значения в регистр данных, записи команды “передавать” в регистр команды, ожидания прерывания по концу передачи и проверки успешности передачи путем считывания статусного регистра устройства. Нарушение последовательности шагов может приводить к неприятным последствиям – например, перезапись регистра данных после подачи команды, но до завершения передачи, может привести к остановке передачи или, что еще хуже, передаче искаженных данных и т. д. Нельзя также забывать о неприятностях более высокого уровня – например, смешивании вывода разных процессов на печати или данных – на устройстве внешней памяти. Поэтому оказывается необходимо связать с каждым внешним устройством какой-то разграничитель доступа во времени. В современных ОС эта функция возлагается именно на драйвер. Обычно одна из нитей драйвера представляет собой процесс-монитор, выполняющий асинхронно поступающие запросы на доступ к устройству. В Unix, OS/2 и Windows NT/2000/XP этот процесс называется стратегической функцией. При определении интерфейса драйвера разработчики ОС должны найти правильный баланс между противоречивыми требованиями:

    1. стремлением как можно сильнее упростить драйвер, чтобы облегчить его разработку и (косвенно) уменьшить вероятность опасных ошибок;

    2. желанием предоставить гибкий и интеллектуальный интерфейс к разнообразным устройствам.

    Драйверы обычно разрабатываются не поставщиками операционной системы, а сторонними фирмами – разработчиками и изготовителями периферийного оборудования. Поэтому интерфейс драйвера является ничуть не менее внешним, чем то, что обычно считается внешним интерфейсом ОС – интерфейс системных вызовов. Соответственно, к нему предъявляются те же требования, что и к любому другому внешнему интерфейсу: он должен быть достаточно простым, исчерпывающе документированным и стабильным – не меняться непредсказуемо от одной версии ОС к другой. Идеальным вариантом была бы полная совместимость драйверов хотя бы снизу вверх, чтобы драйвер предыдущей версии ОС мог использоваться со всеми последующими версиями. Потеря совместимости в данном случае означает, что все независимые изготовители оборудования должны будут обновить свои драйверы. Организация такого обновления оказывается сложной, неблагодарной и часто попросту невыполнимой задачей – например, потому, что изготовитель оборудования уже не существует как организация или отказался от поддержки данного устройства. Таким образом, интерфейс драйвера часто оказывается наиболее консервативной частью ОС. К сожалению – несмотря даже на то, что в общих чертах архитектура драйвера в большинстве современных ОС удивительно похожа – идея эта, по-видимому, нереализуема. Даже для близкородственных ОС – например, систем семейства Unix – драйверы одного и того же устройства не всегда могут быть легко перенесены из одной ОС в другую, не говоря уж о возможности использования без модификаций. Еще более удивительным является тот факт, что две линии ОС – Windows 95/98/МЕ и Windows NT/2000/XP – поставляемых одной и той же компанией Microsoft и реализующих почти один и тот же интерфейс системных вызовов, – Win32 – имеют совсем разный интерфейс драйвера. Проблема здесь в том, что интерфейс между драйвером и ядром ОС всегда двусторонний: не только прикладные программы и ядро вызывают функции драйвера, но и, наоборот, драйвер должен вызывать функции ядра. Таким образом, до тех пор, пока используются ОС различной архитектуры, разработка универсального интерфейса драйвера, если теоретически и возможна, то практически вряд ли осуществима.

    Драйверы режима ядра: Часть 1: Основные понятия — Архив WASM.RU

    Обзор архитектуры

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

    С разделением адресного пространства все на удивление просто. Все четыре, доступного в 32-х разрядной архитектуре, гигабайта разделены на две равные части (4GT RAM Tuning и Physical Address Extension я опускаю как зкзотические). Нижняя половина отдана процессам пользовательского режима, верхняя принадлежит ядру.

    С разделением прав и обязанностей немного сложнее.

    К пользовательским относятся следующие процессы:

    • Процессы поддержки системы (System Support Processes) - например, процесс входа в систему Winlogon (реализован в \%SystemRoot%\System32\Winlogon.exe);
    • Процессы сервисов (Service Processes) - например, спулер печати;
    • Пользовательские приложения (User Applications) - бывают пяти типов: Win32, Windows 3.1, MS-DOS, POSIX и OS/2;
    • Подсистемы окружения (Environment Subsystems) - поддерживается три подсистемы окружения: Win32 (реализована в \%SystemRoot%\System32\Csrss.exe), POSIX (реализована в \%SystemRoot%\System32\Psxss.exe), OS/2 (реализована в \%SystemRoot%\System32\os2ss.exe).

    Ядро состоит из следующих компонентов:

      Исполнительная система (Executive) - управление памятью, процессами и потоками и др.;
    • Ядро (Kernel) - планирование потоков, диспетчеризация прерываний и исключений и др. (реализовано в \%SystemRoot%\System32\Ntoskrnl.exe);
    • Драйверы устройств (Device Drivers) - драйверы аппаратных устройств, сетевые драйверы, драйверы файловых систем;
    • Уровень абстрагирования от оборудования (Hardware Abstraction Layer, HAL) - изолирует три вышеперечисленных компонента от различий между аппаратными архитектурами (реализован в \%SystemRoot%\System32\Hal.dll);
    • Подсистема поддержки окон и графики (Windowing And Graphics System) - функции графического пользовательского интерфейса (Graphic User Interface, GUI) (реализована в \%SystemRoot%\System32\Win32k.sys).

    Рис. 1-1. Упрощенная схема архитектуры Windows 2000

    Режим пользователя и режим ядра

    Хотя процессоры семейства Intel x86 поддерживают четыре уровня привилегий (называемых кольцами защиты), в Windows используются только два: 0-ой для режима ядра и 3-ий для режима пользователя. Это связано с поддержкой других процессоров (alpha, mips), в которых реализовано только два уровня привилегий. Предыдущие выпуски Windows NT поддерживали эти архитектуры, но в Windows 2000 осталась поддержка только x86.

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

    Процессы пользовательского режима рассматриваются как потенциально опасные с точки зрения стабильности системы. Их права ограничиваются. И всяческие попытки выйти за пределы этих ограничений жестко пресекаются.

    Компоненты ядра разделяют единое адресное пространство, выполняются в привилегированном режиме процессора (называемом режимом ядра), могут выполнять все, в том числе и привилегированные, команды процессора, имеют неограниченный и прямой доступ к системным данным и коду, имеют прямой, или через HAL, доступ к оборудованию.

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

    Таким образом пользовательские приложения отделены от собственно операционной системы. Если вы зададитесь целью написать сколь-нибудь серьезное приложение, для работы которого необходим доступ к внутренним функциям или структурам данных системы, то столкнетесь со множеством ограничений, преодолеть которые можно только разместив свой код в системном адресном пространстве. Из документированных существует только один способ это сделать - установить драйвер устройства. Способ этот относительно прост, надежен, а главное, полностью обеспечен поддержкой со стороны самой операционной системы.

    Драйверы Windows 2000

    Windows 2000 поддерживает множество типов драйверов устройств.

    Существует два базовых, которые имеют своих представителей:

    • Драйверы пользовательского режима (User-Mode Drivers):
      • Драйверы виртуальных устройств (Virtual Device Drivers, VDD) - используются для поддержки программ MS-DOS (не путать с VxD драйверами в Windows 95/98 - это совсем разные вещи, хотя и имеют одно название);
      • Драйверы принтеров (Printer Drivers).
    • Драйверы режима ядра (Kernel-Mode Drivers):
      • Драйверы файловой системы (File System Drivers) - реализуют ввод-вывод на локальные и сетевые диски;
      • Унаследованные драйверы (Legacy Drivers) - написаны для предыдущих версий Windows NT;
      • Драйверы видеоадаптеров (Video Drivers) - реализуют графические операции;
      • Драйверы потоковых устройств (Streaming Drivers) - реализуют ввод-вывод видео и звука;
      • WDM-драйверы (Windows Driver Model, WDM) - поддерживают технологию Plag and Play и управления электропитанием. Их отличительной особенностью является совместимость на уровне исходного кода между Windows 98, Windows ME и Windows 2000.

    В разных источниках вы можете встретить классификацию немного отличную от приведенной выше, это не суть важно. Важно то, что драйверы, которые мы будем писать, не подпадают ни под один из пунктов этой классификации. Это ни драйверы файловой системы, ни унаследованные драйверы, ни драйверы видеоадаптеров или звуковых карт, ни WDM-драйверы, т.к. не поддерживают Plag"n"Play и управление электропитанием. Это не драйверы пользовательского режима (это вообще не интересно). На самом деле это просто черт знает что такое, т.к. система сама позволяет легко и просто добавить в саму себя код непонятно для какого устройства, и делать с ней все что угодно! Это как если бы к вам ночью в дверь постучался совершенно незнакомый человек, и вы ни слова не говоря впустили бы его на ночлег, да еще уложили бы в свою постель! Однако, это не является каким-то багом или дырой в системе безопастности. Просто система работает так, как она работает. Иначе и быть не может, т.к. взаимодействуя с окружением, система вынуждена предоставлять к себе доступ. И если бы это было не так, то это была бы полностью закрытая, а значит, бесполезная система.

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

    По своей структуре драйвер устройства является ни чем иным как файлом PE-формата (Portable Executable, PE). Таким же как обычные exe и dll. Только загружается и работает по другим правилам. Драйверы можно рассматривать как DLL режима ядра, предназначенные для выполнения задач не решаемых из пользовательского режима. Принципиальная разница здесь (не считая уровня привилегий) в том, что мы не сможем напрямую обращаться к драйверу, ни к его коду, ни к его данным, а будем пользоваться специальным механизмом предоставляемым диспетчером ввода-вывода (Input/Output Manager). Диспетчер ввода-вывода обеспечивает среду для функционирования драйверов, а также предоставляет механизмы для их загрузки, выгрузки и управления ими.

    Приступая к разработке драйверов режима ядра, вы почувствуете себя совершенным новичком, т.к. весь предыдущий опыт использования API тут не поможет - ядро предоставляет совершенно другой набор функций. Также придется пользоваться плохо документированными (определенными только в заголовочных файлах) или вовсе недокументированными функциями и структурами данных.

    Одно- и многоуровневые драйверы

    Большинство драйверов управляющих физическими устройствами являются многоуровневыми (layered drivers). Обработка запроса ввода-вывода разделяется между несколькими драйверами. Каждый выполняет свою часть работы. Например, запрос на чтение файла передается драйверу файловой системы, котрый, выполнив некоторые операции (например, разбиение запроса на несколько частей), передает его "ниже" - драйверу диска, а тот, в свою очередь, отправляет запрос драйверу шины. Кроме того между этими драйверами можно добавить любое количество драйверов-фильтров (например, шифрующих данные). Выполнив запрос нижестоящий драйвер (lower-level driver) передает его результаты "наверх" - вышестоящему (higher-level driver). Но, к счастью, у нас все будет значительно проще. Наши драйверы всегда будут одноуровневыми (monolithic drivers), что сильно упростит весь процесс их написания и отладки.

    Контекст потока

    Поскольку, в большинстве случаев, мы имеем всего один процессор, а приложений, которые нужно выполнять много, то естественно, что для создания иллюзии одновременного их выполнения надо последовательно подключать эти приложения к процессору, причем очень быстро. Эта процедура называется переключением контекста потока (thread context switching). Если система переключает контекст потоков принадлежащих одному процессу, то необходимо сохранить значение регистров процессора отключаемого потока, и загрузить, предварительно сохраненные значения регистров процессора подключаемого потока. И обновить кое-какие структуры данных. Если же подключаемый поток принадлежит другому процессу, то необходимо еще в регистр CR3 процессора загрузить указатель на каталог страниц (page directory) процесса. Так как каждому пользовательскому процессу предоставлено закрытое адресное пространство, то у разных процессов разные проекции адресных пространств, а значит, и разные каталоги страниц и наборы таблиц страниц по которым процессор транслирует виртуальные адреса в физические. Все это не имеет прямого отношения к программированию драйверов. Но я напоминаю об этом в связи вот с чем. Так как переключение контекста операция не самая быстрая, то драйверы, по соображениям лучшей производительности, как правило, не создают своих потоков. Но код драйвера все же нужно выполнять. Поэтому, для экономии времени на переключение контекстов, драйверы выполняются в режиме ядра в одном из трех контекстов:

    • в контексте пользовательского потока инициировавшего запрос ввода-вывода;
    • в контексте системного потока режима ядра (эти потоки принадлежат процессу System);
    • как результат прерывания (а значит, не в контексте какого-либо процесса или потока, который был текущим на момент прерывания).

    Не совсем понимаю как можно выполнить что-то "не в контексте какого-либо процесса или потока", но учитывая авторитет людей это написавших (Д. Соломон и М. Руссинович), а также то, что нам это не понадобится, т.к. мы не будем обрабатывать ни программные, ни тем более, аппаратные прерывания, про третий случай можно сразу забыть. Остаются первые два варианта. Если инициируется запрос ввода-вывода, то мы в контексте потока этот запрос инициировавшего, и значит, можем напрямую обращаться к адресному пространству процесса которому этот поток принадлежит. Если мы в контексте системного потока, то ни к какому пользовательскому процессу обращаться напрямую не можем, а к системному мы и так всегда можем обратиться. Если нужно из драйвера посмотреть, что там у какого-нибудь процесса лежит по такому-то адресу, то придется либо самим переключать контекст, либо по таблицам страниц транслировать адреса.

    Уровни запросов прерываний

    Прерывание - неотъемлемая часть любой операционной системы. Прерывание требует обработки, поэтому выполнение текущего кода прекращается и управление передается обработчику прерывания. Существуют как аппаратные, так и программные прерывания. Прерывания обслуживаются в соответствии с их приоритетом. Windows 2000 использует схему приоритетов прерываний, известную под названием уровни запросов прерываний (interrupt request levels, IRQL). Всего существует 32 уровня, с 0 (passive), имеющего самый низкий приоритет, по 31 (high), имеющего соответственно самый высокий. Причем, прерывания с IRQL=0 (passive) по IRQL=2 (DPC\dispatch) являются программными, а прерывания с IRQL=3 (device 1) по IRQL=31 (high) являются аппаратными. Не путайте уровни приоритета прерываний с уровнями приоритетов потоков - это совсем разные вещи. Прерывание с уровнем IRQL=0, строго говоря, прерыванием не является, т.к. оно не может прервать работу никакого кода (ведь для этого этот код должен выполняться на еще более низком уровне прерывания, а такого уровня нет). На этом IRQL выполняются потоки пользовательского режима. И код наших драйверов тоже будет выполняться на этом IRQL. Это отнюдь не означает, что код любого драйвера всегда выполняется на уровне "passive". Просто мы не будем обрабатывать ни программные, ни тем более аппаратные прерывания. А отсюда следуют, по крайней мере, два очень важных вывода.

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

    Второй важный момент: на уровне прерывания passive можно вызывать любые функции ядра (в DDK в описании каждой функции обязательно указано, на каком уровне прерывания ее можно вызывать), а также обращаться к страницам памяти сброшенным в файл подкачки. На более высоких уровнях прерывания (DPC/dispath и выше), попытка обращения к странице отсутствующей в физической памяти приводит к краху системы, т.к. диспетчер памяти (Memory Manager) не может обработать ошибку страницы.

    "Голубой экран смерти"

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

    В третьем кольце все было просто: набросал примерный код, расставил int3 где надо, запустил и... в отладчике уже разбираешься что к чему. Если что-то не так - прибил, исправил ошибки, перекомпилировал... и так далее, до тех пор пока код не заработает как надо. При программировании драйверов об этой технике можно забыть. Тут "сапер" ошибается один раз. Одно неверное движение... и можно откинуться на спинку кресла и минутку расслабиться.

    Для того чтобы видеть BSOD как можно реже следует придерживаться одного очень простого правила: "Семь раз отмерь - один отрежь"... в смысле "Семь раз проверь - один запусти". Это конечно просто сказать, но значительно труднее сделать. Но как правило, учитывая то, что структура драйверов, которые вы будете писать (после прочтения этих статей), относительно проста, можно разобраться с ошибками и до появления BSOD. Если же он упорно появляется перед вашими глазами, и вы никак не можете понять причину, возможным способом прояснить ситуацию является анализ аварийного дампа (crash dump). О том, что это такое, как его сделать и анализировать можно почитать в статье Марка Руссиновича "Анализ аварийных дампов памяти" http://www.osp.ru/win2000/2001/03/025.htm. Дело это (анализ) очень непростое, но думаю, что до этого не дойдет.

    Теоретик из меня хреновый, так что все вышесказанное вы можете рассматривать как очень базовые сведения о тех принципах, которые совершенно необходимо понимать. Нельзя приступать к разработке драйверов режима ядра не имея понятия о том, что такое контекст потока, уровни прерываний и приоритеты потоков, режим ядра/пользователя и т.д. и т.п. Чувствуете себе неуверенно в каком-то вопросе - список литературы внизу.

    Теперь осветим кое-какие более практические вещи (совсем практическими они станут в следующих статьях), а именно, что нам понадобится, чтобы всю эту теорию превратить в практику.

    Driver Development Kit

    Первое это конечно Комплект разработки драйверов устройств (Windows 2000 Driver Development Kit, 2KDDK), который можно свободно скачать с сайта Microsoft (во всяком случе я сливал его совершенно безвозмездно отсюда: http://www.microsoft.com/ddk/). В этот пакет входит документация, которая является богатым источником информации о внутренних структурах данных и внутрисистемных функциях используемых драйверами устройств.

    Помимо документации в DDK входит набор библиотечных файлов (*.lib), которые будут совершенно необходимы при компоновке. В DDK входит два комплекта этих файлов: для окончательной версии Windows (называемой свободным выпуском (free build)); и для отладочной (называемой проверочным выпуском (checked build)). Находятся эти файлы в каталогах %ddk%\libfre\i386 и %ddk%\libchk\i386 соответственно. Отладочная версия отличается более строгой проверкой ошибок. Использовать нужно файлы соответствующие вашей версии системы поместив их в каталог \masm32\lib\w2k.

    Включаемые файлы

    Также нам понадобятся включаемые (*.inc) файлы с определениями прототипов функций. Их нам (точнее мне ) тоже придется делать самим. Я перепробовал много разных утилит, конвертирующих *.lib -> *.inc, как входящих в пакет masm32 by hutch, так и слитых мной в разное время с бескрайних просторов Internet. Из всех что имеются у меня в наличии, только protoize.exe by f0dder справилась со своей задачей, и мне практически ничего не пришлось править руками. Эта замечательная тулза будет лежать в каталоге \tools\protoize. Сайт автора: http://f0dder.didjitalyphrozen.com/. Только вы ее там не найдете. f0dder несколько раз постил эту утилиту в конференции http://board.win32asmcommunity.net/. Инклуды будут лежать в каталоге \include\w2k. Их следует поместить в каталог \masm32\include\w2k. Для конвертации использовались *.lib для свободного выпуска Windows 2000, т. к. у меня стоит именно этот вариант (и у вас, наверняка, тоже).

    Следующая проблема более серьезна. Это практически полное отсутствие включаемых файлов с определениями необходимых структур, символьных констант и макросов. Найти что-либо путное в сети вы вряд ли сможете - уж слишком экзотическое это занятие - писать драйверы режима ядра на ассемблере. Кое-что можно найти у EliCZ http://www.anticracking.sk/EliCZ/. Кое-что у Y0da http://mitglied.lycos.de/yoda2k/index.htm (частично сделаное им самим, частично взятое у того же EliCZ). Но сделано это из рук вон плохо, (при всем моем глубоком уважении к нашим словакскому и немецкому коллегам): имена членов многих структур отличаются от определенных в оригинальных заголовочных файлах из DDK; вложенные структуры и обьединения не имеют имен; хотя в оригинале они именованы. И вообще, все находится в некотором беспорядке, и при просмотре вызывает удручающее впечатление. Неплохо сделан только ntstatus.inc. Частично это объясняется тем, что EliCZ начал создавать свои инклуды еще в отсутствие у него DDK (как он сам говорит). В любом случае, я не советую вам их использовать, по крайней мере без тщательной проверки. Кое-что, в свое время, мелькало в конференции http://board.win32asmcommunity.net/, но качество тоже не особо впечатляет. Короче, единственно правильное решение в данной ситуации - делать все самим, причем вручную, т. к. какие-либо тулзы, позволяющие автоматизировать этот процесс, мне не известны. Если вы, вдруг, наткнетесь на что-то стоящее, не сочтите за труд - дайте мне знать.

    Отладка драйверов

    Также нам потребуется отладчик, причем, поскольку отлаживать придется код режима ядра, то и отладчик нужен соответствующий. Самым лучшим выбором будет SoftICE. Или можно воспользоваться Kernel Debugger входящим в состав DDK. Этот отладчик требует двух компьютеров - ведущего и ведомого, что не каждый может себе позволить. Марк Руссинович (Mark Russinovich, http://www.sysinternals.com/) написал утилиту LiveKd, которая позволяет использовать Kernel Debugger без подключения второго компьютера. Не знаю есть ли она на сайте (не проверял), но на диске к книжке "Внутреннее устройство Microsoft Windows 2000" имеется. Также этот отладчик чрезвычайно полезен для исследования внутреннего устройства системы, при условии что у вас установлены отладочные символы, которые можно (или было можно) свободно скачать с сайта Microsoft.

  • Дэвид Соломон, Марк Руссинович, "Внутреннее устройство Microsoft Windows 2000", изд. "Питер", 2001.

    Хотя в этой книге нет ни одной строчки исходного кода, она прежде всего для программистов.

  • Свен Шрайбер, "Недокументированные возможности Windows 2000", изд. "Питер", 2002.

    Сугубо практическая книга, в которой раскрыто множество тайн Windows 2000.

  • Walter Oney, "Programming the Microsoft Driver Model", Microsoft Press, 1999

    В этой книге упор сделан на Plag"n"Play драйверы, но это нисколько не умоляет ее достоинств, т.к. базовые принципы разработки драйверов универсальны.

  • Джеффри Рихтер, "Windows для профессионалов: создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows", изд. "Питер", 2000.

    Эта книжка не имеет никакого непосредственного отношения к программированию драйверов, но тоже очень интересная;-)

    Этот список ни в коем случае не претендует на полноту. Многое, особенно по английски, можно найти в Internet (кроме Шрайбера, все книги есть в электронном варианте). В отношении книг хочу сказать еще, что все они из разряда "must have". Увидите - покупайте не глядя. Все, кроме Walter"а Oney, переведены на наш "великий и могучий".

    И последнее. Я не являюсь большим специалистом в области разработки драйверов, так что ошибки или неточности, как в этой, так и во всех последующих статьях, весьма вероятны. Найдете - смело тыкайте носом. Скажу спасибо.

  •