Функции драйверов
Прежде всего, драйвер должен иметь функции, вызываемые ядром при загрузке и выгрузке модуля и при подключении модуля к конкретным устройствам. Например, в Sun Solans это перечисленные функции.
- int _init(void) – инициализация драйвера. Эта функция вызывается при загрузке модуля. Драйвер должен зарезервировать все необходимые ему системные ресурсы и проинициализировать собственные глобальные переменные. Инициализация устройства на этом этапе не происходит.
- int probe (dev_info_t *dip) – проверить наличие устройства в системе. Во многих системах эта функция реализуется не самим драйвером, а специальным модулем – сниффером (sniffer – дословно, "нюхач"), используемым программой автоконфигурации.
- int attach (dev_info_t * dip, ddi_attach_cmd_t crtid) – инициализация копии драйвера, управляющей конкретным устройством. Эту функцию можно рассматривать как аналог конструктора объекта в объектно-ориентированном программировании. Если в системе присутствует несколько устройств, управляемых одним драйвером, некоторые ОС загружают несколько копий кода драйвера, но в системах семейства Unix функция attach просто вызывается многократно.
Каждая из инициализированных копий драйвера имеет собственный блок локальных переменных, в которых хранятся переменные состояния устройства. При вызове attach драйвер должен прочитать конфигурационный файл, где записаны параметры устройства (номенклатура этих параметров зависит от устройства и от драйвера), разместить и проинициализировать блок переменных состояния, зарегистрировать обработчики прерываний, проинициализировать само устройство и, наконец, зарегистрировать устройство как доступное для пользовательских программ, создав для него минорную запись (minor node). В ряде случаев драйвер создает для одного устройства несколько таких записей.
Например, каждый жесткий диск в Unix SVR4 должен иметь 16 записей – по две (далее мы поймем, для чего они нужны) для каждого из восьми допустимых слайсов (логических разделов, см. разд. "Загрузка самой ОС") диска. Другой пример: в большинстве систем семейства Unix лентопротяжные устройства имеют две минорные записи. Одно из этих устройств при открытии перематывает ленту к началу, другое не перематывает. В действительности оба устройства управляются одним и тем же драйвером, который определяет текущий режим работы в зависимости от указанной минорной записи.
Современные Unix системы, в частности Solaris, используют отложенную инициализацию, когда для многих устройств attach вызывается только при первой попытке доступа пользовательской программы к устройству.
- int detach(dev_info_t *dip, ddi_detach_cmd_t cmd) – аналог деструктора объекта в ООП. Впрочем, в отличие от деструктора, эта операция не безусловна – если не удается нормально завершить обрабатываемые в данный момент операции над устройством, драйвер может и даже обязан отказаться деинициализироваться. При деинициализации драйвер должен освободить все системные ресурсы, которые он занял при инициализации и в процессе работы (в том числе и уничтожить минорную запись) и может, если это необходимо, произвести какие-то операции над устройством, например, выключить приемопередатчик, запарковать головки чтения-записи и т. д. После того, как все устройства, управляемые драйвером, успешно деинициализированы, система может его выгрузить.
- int _fini (void) – функция, вызываемая системой перед выгрузкой дуля. Драйвер обязан освободить все ресурсы, которые он занял на этапе инициализации модуля, а также все ресурсы, занятые им во время работы на уровне модуля (не привязанные к конкретному управляемому устройству).
После того, как драйвер проинициализировался и зарегистрировать минорную запись, пользовательские программы могут начинать обращаться к не му и к управляемым им устройствам. Понятно, что обеспечить единый интерфейс к разнообразным категориям устройств, перечисленным в Главе 9 по меньшей мере сложно. Наиболее радикально подошли к этой проблеме разработчики системы UNIX, разделившие все устройства на два класса-блочные (высокоскоростные устройства памяти с произвольным доступом в первую очередь, дисковые устройства) и последовательные или символьные устройства (все остальное) (в действительности, у современных систем семейства Unix типов драйверов несколько больше, но об этом далее).
Над последовательными устройствами определен следующий набор операций, которые могут осуществляться прикладной программой (в простых случаях эти операции непосредственно транслируются в вызовы функций драйвера).
- int open (char * fnarne, int flags, mode_t mode) – Процедура открытия устройства. В некоторых случаях она может содержать и дополнительные шаги инициализации устройства – например, для лентопротяжек эта процедура может включать в себя перемотку ленты к началу. Функция возвращает целочисленный идентификатор-"ручку" (handle), часто называемый также дескриптором файла, который используется программой при всех последующих обращениях к устройству.
- int readfint handle, char * where, size_t how_much) – чтение данных с устройства. Если устройство приспособлено только для вывода (например, принтер), эта функция может быть не определена.
- int write (int handle, char * what, size_t how_much) – запись данных на устройство. Если устройство приспособлено только для ввода, (например, перфоленточный ввод или мышь), эта функция также может быть не определена.
- void dose (int handle) – процедура закрытия (освобождения) устройства.
- int ioctitint handle, int cmd,…) – процедура задания специальной команды, которая не может быть сведена к операциям чтения и записи. Набор таких команд зависит от устройства. Например, для растровых графических устройств могут быть определены операции установки видеорежима; для последовательных портов RS232 это могут быть команда установки скорости, количества битов, обработки бита четности и т. д., для дисководов – команды форматирования носителя.
- off r lseek<int handle, off_t offset, int whence), long seek – команда перемещения головки чтения/записи к заданной позиции. Драйверы устройств, не являющихся устройствами памяти, например модема или Принтера, как правило, не поддерживают эту функцию.
Слово long в названии функции появилось по историческим причинам: версиях Unix для 16-разрядных машин индекс позиции не мог обозначать-я словом, потому что это ограничивало бы логическую длину устройства недопустимо малым значением 65334 байт. Поэтому необходимо было использовать двойное слово, что соответствовало типу long языка С. Современные системы используют 64-разрядный off_t. - caddr_t rranap (caddr_t addr, size_t len, int prot, int flags, int handle, off_t offset) memory map – отображение устройства в адресное пространство процесса. Параметр prot задает права доступа к отображенному участку: на чтение, на запись и на исполнение. Отображение может происходить на заданный виртуальный адрес, или же система может выбирать адрес для отображения сама.
Эта функция отсутствовала в старых версиях системы, но большинство современных систем семейства (BSD 4.4, ряд наследников BSD 4.3, SVR4 и Linux) поддерживают ее.