Программы драйверы можно разделить на следующие группы
Опять вернёмся в традиционную область разработки операционных систем (и приложений для микроконтроллеров) — написание драйверов.
Я попробую выделить некоторые общие правила и каноны в этой области. Как всегда — на примере Фантома.
Драйвер — функциональная компонента ОС, ответственная за отношения с определённым подмножеством аппаратуры компьютера.
С лёгкой руки того же Юникса драйвера делятся на блочные и байт-ориентированные. В былые времена классическими примерами были драйвер диска (операции — записать и прочитать сектор диска) и драйвер дисплея (прочитать и записать символ).
В современной реальности, конечно, всё сложнее. Драйвер — типичный инстанс-объект класса, и классов этих до фига и больше. В принципе, интерфейс драйверов пытаются как-то ужать в прокрустово ложе модели read/write, но это самообман. У драйвера сетевой карты есть метод «прочитать MAC-адрес карты» (который, конечно, можно реализовать через properties), а у драйвера USB — целая пачка USB-специфичных операций. Ещё веселее у графических драйверов — какой-нибудь bitblt( startx, starty, destx, desty, xsize, ysize, operation ) — обычное дело.
Цикл жизни драйвера, в целом, может быть описан так:
- Инициализация: драйвер получает ресурсы (но не доступ к своей аппаратуре)
- Поиск аппаратуры: драйвер получает от ядра или находит сам свои аппаратные ресурсы
- Активация — драйвер начинает работу
- Появление/пропадание устройств, если это уместно. См. тот же USB.
- Засыпание/просыпание аппаратуры, если это уместно. В контроллерах часто неиспользуемая аппаратура выключается для экономии.
- Деактивация драйвера — обслуживание запросов прекращается
- Выгрузка драйвера — освобождаются все ресурсы ядра, драйвер не существует.
(Вообще я написал в прошлом году черновик открытой спецификации интерфейса драйвера — см. репозиторий и документ.)
Мне известны три модели построения драйвера:
Драйвер на основе поллинга (циклического опроса) устройства
Такие драйвера применяются только с большого горя или по большой необходимости. Или если это простая встроенная микроконтроллерная система, в которой и есть-то всего два драйвера. Например, конвертор интерфейсов serial port TCP, в котором сеть работает по прерываниям, работу с последовательным портом может, в принципе, выполнять и поллингом. Если не жалко избытка тепла и затрат энергии.
Есть и ещё одна причина: такие драйвера практически неубиваемы. Поэтому, например, в ОС Фантом отладочная выдача ядра в последовательный порт сделана именно так.
В цикле проверяем готовность порта принять байт, передаём байт, закончили упражнение.
Такой драйвер, как нетрудно видеть, пожирает процессор в ожидании готовности устройства. Это можно починить, если скорость работы самого драйвера некритична:
Но, конечно, в целом это никуда не годная (кроме вышеприведённого случая:) модель.
Драйвер на основе прерываний
Общая структура такого драйвера выглядит вот как:
Фактически, такой драйвер порождает для себя псевдо-нить: поток управления, который живёт только на поступлении прерываний от устройства.
Как только драйвер получает очередной запрос на запись, он включает прерывания и «вручную» инициирует отправку в устройство первого байта данных. После чего входящая нить засыпает, ожидая конца передачи. А может и вернуться, если нужна асинхронная работа. Теперь драйвер будет ждать прерывания от устройства. Когда устройство «прожуёт» полученный байт, оно сгенерирует прерывание, при обслуживании которого драйвер или отправит очередной байт (и будет ждать очередного прерывания), или закончит работу, выключит прерывания и «отпустит» ждущую внутри dev_write() нить.
Что забыто
Прежде чем мы перейдём к последней модели драйвера, перечислим вещи, которые я (намеренно) пропустил в предыдущем повествовании.
Обработка ошибок
В нашем псевдокоде никак не проверяется успешность ввода-вывода. Реальное устройство может отказать или сообщить о неисправности носителя. Вынули кабель из порта локалки, на диске случился плохой блок. Драйвер должен обнаружить и обработать.
Таймауты
Устройство может сломаться и просто не ответить на запрос прерыванием, или никогда не выставить бит готовности. Драйвер должен запросить таймерное событие, которое бы вывело его из «ступора» на такой случай.
Смерть запроса
Если окружающая нас ОС это позволяет, то надо быть готовым к тому, что вошедшая в драйвер нить, в рамках которой «пришёл» запрос ввода-вывода, может быть просто убита. Это не должно приводить к фатальным последствиям для драйвера.
Синхронизация
Для простоты я указываю в качестве примитива синхронизации cond. В реальном драйвере это невозможно — cond требует объемлющего mutex в точке синхронизации, а в прерывании какой уж mutex — нельзя! Вот в последней модели, драйвере с собственной нитью, можно применять cond как средство синхронизации нити пользователя и нити драйвера. А вот синхронизация с прерыванием — только spinlock и семафор, причём реализация семафора должна быть готова к возможности активировать (открыть) семафор из прерывания. (В Фантоме это так и есть)
Драйвер на основе нити
От предыдущего он отличается тем, что имеет собственную нить, которая выполняет ввод-вывод.
Преимущество такого драйвера в том, что из нити можно позволить себе куда больше, чем из хендлера прерывания — можно выделять память, управлять таблицами страниц и вообще звать любую функцию ядра. Из прерывания нельзя позволить себе длинные и, тем более, блокирующие операции.
Отметим, что есть третья, промежуточная модель, в которой драйвер не имеет своей нити, а выполняет всё то же самое из нити запроса ввода-вывода. Но, во-первых, см. пункт о том, что её могут убить, во-вторых это жлобство :), а в третьих — не всегда она (нить) такого хочет. Иным бы хотелось асинхронного обслуживания.
Блочный ввод-вывод, сортировка и заборы
Дисковые драйвера обычно имеют на входе очередь запросов — ОС генерирует запросы на ввод-вывод пачками, и все запросы на уровне драйвера асинхронны. При этом хороший драйвер имеет собственную стратегию обслуживания запросов, причём — обслуживания не в порядке поступления.
Действительно, если на обычном дисковом устройстве выполнять запросы в том порядке, в котором они прилетели, головке накопителя придётся совершать хаотичные движения по диску, замедляя ввод-вывод.
Обычно драйвер просто сортирует очередь запросов по номеру блока и обслуживает их так, чтобы головка диска последовательно двигалась от внешней дорожки к внутренней, или наоборот. Это сильно помогает.
Но не всякую пару запросов можно поменять местами. Если файловая система (или её аналог) решила, что ей надо гарантировать целостность данных на диске, она бы очень хотела убедиться в том, что определённая группа запросов завершена. Для этого в очередь запросов вставляется специальный запрос ввода-вывода, который запрещает перемешивать запросы до себя с запросами после себя.
Кроме того, плохая идея переставлять местами запрос на запись блока N и запрос на чтение того же блока. Впрочем, это вопрос договорённостей.
Драйвером считается фрагмент кода операционной системы (ОС), позволяющий ей общаться с аппаратурой. Другими словами, драйвер - набор обработчиков запросов на ввод/вывод к обслуживаемому им устройству, поступающих от пользовательского приложения, других драйверов или самой ОС. Все запросы на ввод/вывод поступают Диспетчеру ввода/вывода (ДВВ), который распределяет их по назначению. Таким образом, процедуры драйвера пассивно ждут, пока к ним обратится Диспетчер ввода/вывода.
Драйверы, используемые Windows NT, можно разделить на следующие группы:
- · драйверы пользовательского режима
- (являются системным программным кодом, функционирующим в пользовательском режиме. В качестве примера можно привести драйверы симуляторы (виртуализаторы) для воображаемой аппаратуры или новых исполнительных подсистем. Так как Windows NT не допускает непосредственной работы с аппаратурой для кода пользовательского режима, то такие драйверы должны полагаться в этой части на драйверы, работающие в режиме ядра)
- · драйверы режима ядра
- (целиком состоят из системного кода, выполняющегося в режиме ядра. Такие драйверы имеют прямой доступ к управлению аппаратурой)
Драйверы режима ядра можно поделить еще на 2 категории:
- · наследованные (legacy, доставшиеся как наследство от Windows NT 3.5, 4)
- (предназначены для работы с теми устройствами, которые не поддерживают PnP спецификацию. После модификации унаследованного драйвера необходимо выполнить перезагрузку операционной системы для того, чтобы загрузить новую версию драйвера. Достоинством унаследованных драйверов является их простота.)
- · WDM драйверы
- (Способность WDM драйверов работать по PnP спецификации включает в себя участие в управление энергосбережением системы, автоматическое конфигурирование устройства и возможность его «горячего» подключения. Таким образом, достоинством WDM драйверов является возможность их выгрузки, изменения и загрузки в систему без перезапуска операционной системы благодаря механизму Plug and Play.)
Наследуемые и WDM драйверы также можно разделить на другие три категории: высокоуровневые, средне и низкоуровневые драйверы.
К высокоуровневым драйверам, например, относятся драйверы файловых систем. Такие драйверы предоставляют инициаторам запроса нефизическую абстракцию получателя, и уже эти запросы транслируются в специфические запросы к лежащим ниже драйверам. Необходимость в создании высокоуровневых драйверов возникает тогда, когда основные услуги аппаратуры уже реализованы драйверами нижнего уровня, и требуется только создать новую фигуру абстрагирования, которая необходима для предъявления инициатору запросов (клиенту драйверов).
Драйверы среднего уровня могут быть проиллюстрированы такими примерами, как драйверы зеркальных дисков, классовые драйверы, мини-драйверы и драйверы-фильтры. Эти драйверы позиционируют себя между высокоуровневыми абстракциями высокоуровневых драйверов и средствами физической поддержки на нижних уровнях. Например, драйверы-фильтры позиционируют себя во время загрузки над или под интересующим их драйвером и перехватывают запросы, идущие к нему или от него. Драйверы-фильтры, как правило, предназначены для модификации запроса к существующему драйверу или для реализации некоей дополнительной функции, изначально не заложенной в существующем драйвере.
Драйверы низкого уровня работают непосредственно с аппаратурой, они обычно разрабатываются вместе с тем устройством, для которого они предназначены.
Драйверы Windows работают в режиме ядра. В этой статье я расскажу про различные типы драйверов. И покажу как посмотреть на установленные драйверы в системе.
Драйверы
Все привыкли что драйверы это прослойка между оборудованием и операционной системой. И отчасти это верно. Но они могут и не относится к физическому устройству, например есть драйвер файловой системы.
Драйверы представляют собой файлы .sys и обеспечивают интерфейс между вводом/выводом и соответствующим оборудованием или модулем ядра. Про диспетчер ввода/вывода я уже рассказывал в прошлой статье.
Драйверы в основном написаны на языке C, но недавно стало возможно писать их для Windows на C++.
Типы драйверов
Можно выделить следующие типы драйверов:
- Драйверафизических устройств. Они необходимы для работы обычных устройств. Например принтеров, сканеров и другого оборудования.
- Фильтрыфайловой системы. Необходимы, например, для создания программных RAID или шифрования дисков.
- Сетевые перенаправители. Это драйверы файловой системы, которые передают запросы по сети на другую машину. В качестве клиента в сетевой операции ввода/вывода отправляет запросы на сервер и обрабатывает ответы. Как сервер получает запросы ввода/вывода и обрабатывает их. Таким образом они позволяют приложению получать доступ к ресурсам на удаленных серверах и управлять ими, как если бы они находились на локальном компьютере.
- Драйвера потоков. Необходимы для поддержки сетевых протоколов, например TCP/IP.
- Потоковые драйвера-фильтры ядра. Они могут объединятся в цепочки для обработки потоков данных. Например для записи или воспроизведения аудио и видио.
- Программные драйвера. Модули ядра, работающие только в режиме ядра. Например многие программы из Sysinternals (Process Explorer, Process Monitor) устанавливают, а затем используют такие модули.
Еще можно разделить их на работающие в пользовательском режиме и в режиме ядра.
В пользовательском режиме работают драйверы принтеров, они переводят аппаратно-независимые запросы в понятные принтеру команды. Которые затем передаются драйверу порта в режиме ядра, например usbprint.sys.
В режиме ядра работают драйверы файловой системы, которые принимают запросы к файлам на ввод / вывод. Тут же работают драйверы PnP, которые общаются с диспетчером PnP. Это драйверы запоминающих устройств, видеоадаптеров, устройств ввода и сетевых адаптеров. Здесь же работают расширения ядра и драйверы сетевых протоколов, но они не связаны с физическими устройствами.
Просмотр установленных драйверов
Чтобы посмотреть информацию о загруженных в систему драйверах можно воспользоваться программой “Сведения о системе” (msinfo32.exe). Эта программа является стандартной для Windows. В программе необходимо перейти в “Программная среда” / “Системные драйверы“. В открывшемся окне вы увидите информацию об установленных драйверах:
Также можно посмотреть список запущенных драйверов программой Process Explorer. Для этого нужно включить отображение всех пользователей в меню “View“. Далее выбрать процесс “System“. И включить отображение нижней панели (View / Lower Pane View):
То есть драйверы мапятся к процессу “System“.
Из чего состоит драйвер
Драйверы состоят из набора процедур, вызываемых для обработки различных запросов. Например можно выделить следующие процедуры:
- инициализации — при загрузке драйвера в ОС диспетчер ввода / вывода выполняет эту процедуру;
- добавления устройства — если это PnP драйвер, то при добавлении нового устройства PnP диспетчер выполняет эту процедуру;
- диспетчеризации — когда происходит запрос от оборудования к системе, то генерируется IRQ запрос и через эту процедуру вызывается драйвер;
- начального ввода / вывода — эта процедура используется если нужно организовать передачу данных на устройство или с него;
- обработки прерываний — диспетчер прерываний передает управление этой процедуре когда устройство прерывает работу процессора для своих нужд;
- dpc — а тут выполняются dpc функции, которые были поставлены в очередь при работе прерываний.
- завершения ввода/ вывода;
- отмены ввода / вывода;
- выгрузки — когда драйвер выгружается из системы и освобождает все занятые им ресурсы.
Драйверы Windows работают в режиме ядра. В этой статье я расскажу про различные типы драйверов. И покажу как посмотреть на установленные драйверы в системе
Одной из неотъемлемых частей операционной системы Windows являются драйверы. В общем случае драйвер — это специальное программное обеспечение, которое обеспечивает работу внешних устройств, а также некоторой базовой функциональности операционной системы. Драйверы используются не только как механизм управления аппаратными устройствами, но и как составная часть прикладного программного обеспечения. В частности, к подобным решениям относятся различные виртуальные дисководы, технологии защиты от копирования, механизмы шифрования, антивирусное программное обеспечение и многое другое. Назначение драйвера — избавить разработчиков пользовательского программного обеспечения от рутинной реализации протоколов работы с оборудованием и предоставить дополнительный сервис и удобные средства по настройке и управлению устройствами. Таким образом, драйвер можно назвать интерфейсной «прослойкой» между «железом» и «софтом». С помощью драйвера клиентское приложение получает возможность управлять подключенным оборудованием.
Кольца защиты
Сначала давайте рассмотрим общие моменты, касающиеся взаимоотношений между операционной системой и драйверами, с точки зрения их последующего администрирования. Прежде всего стоит заметить, что, несмотря на то что драйвер — это обычная программа, в операционной системе Windows она исполняется особым образом и к ней предъявляются специфические требования. В основном это касается контекста работы драйверов — они в большинстве своем работают в нулевом кольце защиты процессора. В процессорах семейства х86 существует защитный механизм, условно называемый кольцами защиты или уровнями привилегий. Всего уровней привилегий четыре, нумеруются они от нулевого до третьего. Самый привилегированный — нулевой уровень. Операционная система Windows использует всего два уровня привилегий, нулевой и третий. Это связано с тем, что изначально система создавалась для нескольких процессоров, в частности для процессоров Alpha, у которых было всего два таких уровня. Конечно, было бы намного лучше, если бы операционная система использовала все эти уровни и располагала ядро на нулевом уровне привилегий, а все остальное — на других уровнях. Тогда код и данные ядра были бы защищены более надежно. К слову сказать, в будущей версии серверной операционной системы Microsoft Windows 2008, ранее известной как Longhorn, предусмотрены отдельные компоненты, использующие первый уровень привилегий. На нулевом уровне расположено ядро системы, ее управляющие структуры данных. Кроме того, здесь расположены драйверы. Именно по этой причине при написании драйверов нужно соблюдать осторожность и очень внимательно относиться к коду. Неверно написанный драйвер может повредить системные данные ядра или другие драйверы, что приведет к появлению «голубого экрана» BSOD. Этот режим в терминах Windows называется режимом ядра (kernel-mode). На третьем уровне привилегий расположены собственно пользовательские приложения. Стоит заметить, что из приложений, выполняющихся на третьем уровне привилегий, невозможно получить прямой доступ к памяти, находящейся в нулевом кольце. То есть нельзя ни читать, ни писать туда. Таким образом, уровень ядра аппаратно изолирован от приложений пользовательского уровня. Однако, обладая административными правами в системе, пользователь может установить драйвер, который загрузится в пространство ядра и получит полную власть над операционной системой. Поэтому всегда нужно следить за тем, что и как устанавливают приложения. Кроме того, работая с административными привилегиями, вы подвергаете свою систему риску, поскольку любое приложение может без вашего ведома установить в систему вредоносный драйвер, а вы даже не узнаете об этом. Чтобы этого избежать, в Windows Vista применяется механизм UAC. Этот режим в терминах Windows называется пользовательским (user-mode).
Процесс загрузки
Следующим немаловажным элементом взаимодействия между системой и драйверами является порядок их загрузки. С этой точки зрения драйверы можно разделить на загружаемые в процессе запуска операционной системы, так называемые драйверы этапа BOOT-START, и загружаемые после старта ядра драйверы этапа SYSTEM-START. BOOT-START. Эти драйверы необходимы для процесса загрузки и инициализации операционной системы. К таким драйверам, например, относятся драйверы файловых систем и драйверы шин. Эти драйверы загружаются в память загрузчиком до запуска ядра операционной системы. Сначала загрузчик читает ветвь реестра SYSTEM. В этой ветви осуществляется поиск драйверов, имеющих значение START, равное нулю, что означает SERVICE_BOOT_START. Эти драйверы загружаются, а инициализирует их диспетчер ввода/вывода после старта ядра. SYSTEM-START. Эти драйверы загружаются и инициализируются PnP-менеджером (см. врезку «PnP-менеджер») после того, как будут инициализированы драйверы этапа boot-start и построено дерево устройств. Кроме того, после инициализации драйверов устройств PnP-менеджер загружает и инициализирует драйверы, помеченные как SYSTEM-START, но до сих пор не загруженные. Эти драйверы не относятся к каким-либо устройствам или создают неперечисляемые в дереве устройств элементы.
Как уважаемый хабрапользователь наверняка знает, «драйвер устройства» — это компьютерная программа управляющая строго определенным типом устройства, подключенным к или входящим в состав любого настольного или переносного компьютера.
Основная задача любого драйвера – это предоставление софтового интерфейса для управления устройством, с помощью которого операционная система и другие компьютерные программы получают доступ к функциям данного устройства, «не зная» как конкретно оно используется и работает.
Обычно драйвер общается с устройством через шину или коммуникационную подсистему, к которой подключено непосредственное устройство. Когда программа вызывает процедуру (очередность операций) драйвера – он направляет команды на само устройство. Как только устройство выполнило процедуру («рутину»), данные посылаются обратно в драйвер и уже оттуда в ОС.
Любой драйвер является зависимым от самого устройства и специфичен для каждой операционной системы. Обычно драйверы предоставляют схему прерывания для обработки асинхронных процедур в интерфейсе, зависимом от времени ее исполнения.
Любая операционная система обладает «картой устройств» (которую мы видим в диспетчере устройств), для каждого из которых необходим специфический драйвер. Исключения составляют лишь центральный процессор и оперативная память, которой управляет непосредственно ОС. Для всего остального нужен драйвер, который переводит команды операционной системы в последовательность прерываний – пресловутый «двоичный код».
Как работает драйвер и для чего он нужен?
Основное назначение драйвера – это упрощение процесса программирования работы с устройством.
Он служит «переводчиком» между хардовым (железным) интерфейсом и приложениями или операционными системами, которые их используют. Разработчики могут писать, с помощью драйверов, высокоуровневые приложения и программы не вдаваясь в подробности низкоуровневого функционала каждого из необходимых устройств в отдельности.
Как уже упоминалось, драйвер специфичен для каждого устройства. Он «понимает» все операции, которые устройство может выполнять, а также протокол, с помощью которого происходит взаимодействие между софтовой и железной частью. И, естественно, управляется операционной системой, в которой выполняет конкретной приложение либо отдельная функция самой ОС («печать с помощью принтера»).
Если вы хотите отформатировать жесткий диск, то, упрощенно, этот процесс выглядит следующим образом и имеет определенную последовательность: (1) сначала ОС отправляет команду в драйвер устройства используя команду, которую понимает и драйвер, и операционная система. (2) После этого драйвер конкретного устройства переводит команду в формат, который понимает уже только устройство. (3) Жесткий диск форматирует себя, возвращает результат драйверу, который уже впоследствии переводит эту команду на «язык» операционной системы и выдает результат её пользователю (4).
Как создается драйвер устройства
Для каждого устройства существует свой строгий порядок выполнения команд, называемой «инструкцией». Не зная инструкцию к устройству, невозможно написать для него драйвер, так как низкоуровневые машинные команды являются двоичным кодом (прерываниями) которые на выходе отправляют в драйвер результат, полученный в ходе выполнения этой самой инструкции.
При создании драйвера для Линукса, вам необходимо знать не только тип шины и ее адрес, но и схематику самого устройства, а также весь набор электрических прерываний, в ходе исполнения которых устройство отдает результат драйверу.
Написание любого драйвера начинается с его «скелета» — то есть самых основных команд вроде «включения/выключения» и заканчивая специфическими для данного устройства параметрами.
И чем драйвер не является
Часто драйвер устройства сравнивается с другими программами, выполняющими роль «посредника» между софтом и/или железом. Для того, чтобы расставить точки над «i», уточняем:
- Драйвер не является интерпретатором, так как не исполняется напрямую в софтовом слое приложения или операционной системы.
- Драйвер не является компилятором, так как не переводит команды из одного софтового слоя в другой, такой же.
Ну и на правах рекламы – вы всегда знаете, где скачать новейшие драйвера для любых устройств под ОС Windows.
Читайте также: