Операционная система UNIX - Робачевский Андрей Михайлович
Шрифт:
Интервал:
Закладка:
Возможным сценарием доступа к такому устройству может являться перебор различных младших номеров (соответствующих специальных файлов), пока операция open() не завершится успешно. Это будет гарантировать, что процесс получил в свое распоряжение отдельное логическое устройство. Другой сценарий возлагает всю работу по поиску неиспользуемого младшего номера устройства на специальные драйверы, получившие названия клонов.[53]
Когда процесс открывает специальный файл устройства, происходит инициализация соответствующего snode и вызов функции spec_open(), реализованной в файловой системе specfs, о которой только что говорилось. Эта функция, в свою очередь, вызывает функцию драйвера <i>xx</i>open(), передавая ей в качестве аргумента указатель на номера устройства, сохраненного в поле s_dev snode. Одной из схем реализации клонов является использование зарезервированного младшего номера. Когда процесс открывает специальный файл устройства с этим номером, функция <i>xx</i>open() выбирает неиспользуемый младший номер и соответственно модифицирует данные snode (с помощью указателя на vnode, передаваемые ей spec_open()). Поскольку доступ процесса к драйверу осуществляется через vnode файловой системы specfs, все последующие операции будут использовать новый младший номер. Таким образом, процесс получит доступ к новому логическому устройству. Эта схема приведена на рис. 5.6.
Рис. 5.6. Создание клонов с помощью зарезервированного младшего номера
Другой подход заключается в использовании специального драйвера, обеспечивающего создание клонов, — драйвера клонов (clone driver). При этом все драйверы, чье "размножение" обеспечивается таким образом, имеют один и тот же старший номер, адресующий драйвер клонов. Младший номер адресует собственно драйвер, т.е. представляет собой старший номер реального устройства, для которого создается клон. Примеры использования такой схемы можно обнаружить для драйверов системы STREAMS, с помощью которых часто реализуются сетевые протоколы и терминальный доступ, включая псевдотерминалы. Это можно заметить, рассмотрев подробный список файлов, отвечающих за эти устройства:
$ <b>ls -l</b>
...
crw-rw-rw- 1 root sys 11, 44 Oct 31 16:36 arp
crw------- 1 root sys 11, 5 Oct 31 16:36 icmp
crw-rw---- 1 root sys 11, 3 Nov 3 1995 ip
crw------- 1 root sys 11, 40 Nov 3 1995 le
crw-rw-rw- 1 root sys 11, 42 Oct 31 16:36 tcp
crw-rw-rw- 1 root sys 11, 41 Nov 3 1995 udp
...
В данном случае старший номер всех драйверов равен 11 — это драйвер клонов. Если проанализировать информацию файла, скажем, tcp, то станет понятно, что старший номер драйвера этого протокола равен 42, для файла tcp он представлен младшим номером устройства. Когда процесс открывает этот файл, производится вызов функции clopen() драйвера клонов, которой передаются номера устройства. Функция clopen() использует младший номер для поиска требуемых точек входа драйвера TCP в коммутаторе устройств cdevswf[]. После этого clopen() вызывает процедуру <i>xx</i>open() драйвера, в данном случае tcpopen(), передавая ей указатель на номера устройства и флаг CLONEOPEN. В ответ на это tcpopen() генерирует неиспользуемый младший номер, создает отдельный логический драйвер (т.е. копирует необходимые структуры данных) и соответствующим образом модифицирует поле s_dev индексного дескриптора файловой системы specfs. Таким образом, для получения уникального TCP-соединения процессу нет необходимости самостоятельно производить поиск неиспользуемого младшего номера.
Встраивание драйверов в ядро
Драйвер устройства является частью кода ядра операционной системы и обеспечивает взаимодействие других подсистем UNIX с физическими или псевдоустройствами. Существует два основных метода встраивания кода и данных драйвера в ядро операционной системы: перекомпиляция ядра, позволяющая статически поместить драйвер, и динамическая загрузка драйвера в ядро в процессе работы системы.
Традиционно для встраивания драйвера в ядро UNIX требуется перекомпиляция ядра и перезапуск системы. Принципиально эта процедура не отличается от компиляции обычной программы, все компоненты ядра являются объектными модулями и редактор связей объединяет их с объектным модулем драйвера для получения исполняемого файла. В этом случае драйвер встраивается в ядро статически, т. е. независимо от фактического наличия устройства и ряда других причин, код и данные драйвера будут присутствовать в ядре UNIX до следующей перекомпиляции.
Однако тенденция развития современных версий операционной системы UNIX заключается в предоставлении возможности динамического расширения функциональности ядра. Это, в частности, относится к файловой системе, драйверам устройств и сетевым протоколам (точнее, драйверам подсистемы STREAMS). Возможность работы с новыми периферийными устройствами без необходимости перекомпиляции ядра обеспечивается загружаемыми драйверами. Вместо того чтобы встраивать модуль драйвера, основываясь на статических таблицах и интерфейсах, ядро содержит набор функций, позволяющих загрузить необходимые драйверы и, соответственно, выгрузить их, когда необходимость работы с данным устройством отпадает. При этом структуры данных для доступа к драйверам устройств также являются динамическими.
Динамическая установка драйвера в ядро операционной системы требует выполнения следующих операций:
□ Размещение и динамическое связывание символов драйвера. Эта операция аналогична загрузке динамических библиотек, и выполняется специальным загрузчиком.
□ Инициализация драйвера и устройства.
□ Добавление точек входа драйвера в соответствующий коммутатор устройств.
□ Установка обработчика прерываний драйвера.
Естественно, код динамически загружаемых драйверов сложнее, и содержит, помимо стандартных точек входа, ряд функций, отвечающих за загрузку и выгрузку драйвера, а также ряд дополнительных структур. Пример дополнительных функций и структур данных, которые должны быть определены в динамически загружаемом драйвере операционной системы Solaris 2.5, приведен в табл. 5.2.
Таблица 5.2. Дополнительные функции и структуры данных для загружаемых драйверов
_init() Функция инициализации и установки, вызываемая при загрузке драйвера _fini() Функция, вызываемая перед выгрузкой драйвера, удаляющая его из системы _infо() Функция, возвращающая информацию о драйвере по запросу ядра struct modlinkage Структура, используемая функциями _init(), _fini() и _info() при загрузке, выгрузке и получении информации о драйвере struct modldrv Структура, экспортируемая ядру при загрузке драйвера, в частности, содержит адреса точек входа в драйвер