Прежде всего, драйвер должен иметь функции, вызываемые ядром при загрузке и выгрузке модуля и при подключении модуля к конкретным устройствам. Например, в Sun Solans это перечисленные функции.
Каждая из инициализированных копий драйвера имеет собственный блок локальных
переменных, в которых хранятся переменные состояния устройства. При вызове
attach драйвер должен прочитать конфигурационный
файл, где записаны параметры устройства (номенклатура этих параметров
зависит от устройства и от драйвера), разместить и проинициализировать
блок переменных состояния, зарегистрировать обработчики прерываний, проинициализировать
само устройство и, наконец, зарегистрировать устройство как доступное
для пользовательских программ, создав для него минорную
запись (minor node). В ряде случаев драйвер создает для одного
устройства несколько таких записей.
Например, каждый жесткий диск в Unix SVR4 должен иметь 16 записей — по
две (далее мы поймем, для чего они нужны) для каждого из восьми допустимых
слайсов (логических разделов, см. разд.
Загрузка самой ОС) диска. Другой пример: в большинстве систем семейства
Unix лентопротяжные устройства имеют две минорные записи. Одно из этих
устройств при открытии перематывает ленту к началу, другое не перематывает.
В действительности оба устройства управляются одним и тем же драйвером,
который определяет текущий режим работы в зависимости от указанной минорной
записи.
Современные Unix системы, в частности Solaris, используют отложенную инициализацию,
когда для многих устройств attach вызывается
только при первой попытке доступа пользовательской программы к устройству.
После того, как драйвер проинициализировался и зарегистрироват
мино ную запись, пользовательские программы могут начинать обращаться
к не му и к управляемым им устройствам. Понятно, что обеспечить единый
ин терфейс к разнообразным категориям устройств, перечисленным в главе
9 по меньшей мере сложно. Наиболее радикально подошли к этой проблеме
разработчики системы UNIX, разделившие все устройства на два класса-блочные
(высокоскоростные устройства памяти с произвольным доступом в первую очередь,
дисковые устройства) и последовательные или символьные устройства (всё
остальное) (в действительности, у современных систем семейства Unix типов
драйверов несколько больше, но об этом далее).
Над последовательными устройствами определен следующий набор операций,
которые могут осуществляться прикладной программой (в простых случаях
эти операции непосредственно транслируются в вызовы функций драйвера).
Слово long в названии функции появилось по историческим причинам: версиях Unix для 16-разрядных машин индекс позиции не мог обозначать-я словом, потому что это ограничивало бы логическую длину устройства недопустимо малым значением 65334 байт. Поэтому необходимо было использовать двойное слово, что соответствовало типу long языка С. Современные системы используют 64-разрядный off_t.
Эта функция отсутствовала в старых версиях системы,
но большинство современных систем семейства (BSD 4.4, ряд наследников
BSD 4.3, SVR4 и Linux) поддерживают ее.
Речь идет об отображении в память данных, хранящихся на устройстве. Для
устройств ввода-вывода, например, для принтера или терминала, эту функцию
невозможно реализовать разумным образом. Напротив, для лент и других последовательных
устройств памяти, поддерживающих функцию Iseek,
отображение может быть реализовано с использованием аппаратных средств
виртуализации памяти и операции read и write.
Необходимость специальной функции отображения появляется у драйверов устройств,
использующих большие объемы памяти, отображенной в адресное пространство
системной шины, например, для растровых видеоадаптеров, некоторых звуковых
устройств или страниц общей памяти (backpane memory — двухпортовой памяти,
используемой как высокоскоростной канал обмена данными в многопроцессорных
системах).
Механизм отображения доступных прикладной программе системных вызовов
в функции драйвера относительно сложен. Этот механизм должен включать
в себя следующее.
Способы, которыми эти вопросы решаются в современных
операционных системах, обсуждаются в последующих разделах. А пока что
мы подробнее обсудим, какие именно операции над устройством следует определить
и почему.
Видно, что предлагаемый системами семейства Unix набор операций рассматривает
устройство как неструктурированный поток байтов (или, ддя устройств ввода-вывода,
два разнонаправленных потока — для ввода и для вывода). Такое рассмотрение
естественно для устройств алфавитно-цифрового ввода-вывода и простых запоминающих
устройств, например магнитных лент, однако далеко не столь естественно
для более сложных устройств.
Стандартный ответ Unix-культуры в этом случае таков: любая, сколь угодно
сложная структура данных может быть сериализована — преобразована в последовательный
поток байтов. Например, изображение может быть превращено в последовательный
поток байтов в виде растровой битовой карты или последовательности описаний
графических примитивов — линий, прямоугольников и пр. Примерами такой
сериализации для изображений могут являться язык PostScript [partners.adobe.com]
и протокол распределенной оконной системы X Window [www.x.org]
(оба протокола поддерживают как растровые образы, так и довольно богатые
наборы векторных примитивов).
Нередки, впрочем, ситуации, когда нам интересна не только структура поступающих
данных, но и время их поступления (в предыдущей главе мы предложили классифицировать
устройства, которые могут быть использованы подобным образом, как генераторы
событий) — это бывает в приложениях реального времени, а также в задачах,
которые сейчас стало модно называть "задачами мягкого реального времени"
— мультимедийных программах, генерирующих поток звука, синхронизованного
с изображением, и, особенно, в компьютерных играх.
Для работы с таким устройством прикладная программа, так или иначе, должна
зарегистрировать обработчик поступающих от устройства событии. В системах
Unix такая регистрация состоит в открытии устройства для чтения, а ожидание
события заключается в выполнении над этим устройством операции чтения.
Для последовательных устройств ввода операция чтений разблокируется, когда
с устройства поступят хоть какие-то данные (а не тогда, когда будет заполнен
весь буфер), поэтому, если пришло только одно событие, мы его не пропустим.
Драйверы многих устройств, способных работать в качестве генераторов событий,
имеют команды ioctl, позволяющие более тонко
управлять условием разблокирования функции read.
Для того чтобы во время ожидания события от генератора, заниматься еще
какой-то полезной работой, предлагается либо выделить ожидающий события
вызов read в отдельную нить, либо пользоваться
системными вызовами lect и poll,
позволяющими ожидать событий на нескольких устройствах (а также средствах
межпроцессного взаимодействия) одновременно.
Другие ОС предоставляют для работы с устройствами-генераторами событий
более сложные механизмы, зачастую основанные на callback
(дословно — "вызов назад"; механизм взаимодействия подсистем,
когда подсистема, запрашивающая сервис, передает обслуживающей подсистеме
указатель на функцию, которую необходимо вызвать при наступлении определенного
события).
Работа с генераторами событий требует решения еще одной задачи — хранения
поступающих событий в периоды, когда пользовательская программа их не
успевает обрабатывать. Необходимость относительно сложных схем работы
с требуемыми для этого буферами вынудила разработчиков Unix System V Release
3 ввести еще один тип драйверов — потоковые
(STREAMS) [docs.sun.com 805-7478-10]. Для
прикладной программы потоковый драйвер не отличается от обычного символьного
устройства, но отличий с точки зрения системы довольно много. Некоторые
из этих отличий будут рассматриваться далее.
Unix System V Release 3 (SCO Open Desktop, SCO OpenServer), Release (SCO
UnixWare, SGI Irix, Sun Solaris) и системы, испытавшие влияние OSF Unix
(IBM AIX, HP/UX) используют потоковые драйверы для реализации таких важных
псевдоустройств, как трубы и сокеты TCP/IP. Кроме того, потоковыми в этих
системах являются драйверы сетевых адаптеров и терминальных устройств.
С другой стороны, в OS/2 и Windows NT/2000/XP существуют обширные номенклатуры
типов драйверов с различными наборами функций. Так, в OS/2 используются
драйверы физических устройств следующих типов: