Операционная система UNIX - Робачевский Андрей Михайлович
Шрифт:
Интервал:
Закладка:
Базовая архитектура драйверов
Драйвер устройства адресуется старшим номером (major number) устройства. Напомним, что среди атрибутов специальных файлов устройств, которые обеспечивают пользовательский интерфейс доступа к периферии компьютера, это число присутствует наряду с другим, также имеющим отношение к драйверу, — младшим номером (minor number). Младший номер интерпретируется самим драйвером (например, для клонов, оно задает старшее число устройства, которое требуется "размножить"). Другим примером использования младших номеров может служить драйвер диска. В то время как доступ к любому из разделов диска осуществляется одним и тем же драйвером и, соответственно, через один и тот же старший номер, младший номер указывает, к какому именно разделу требуется обеспечить доступ.
Доступ к драйверу осуществляется ядром через специальную структуру данных (коммутатор устройств), каждый элемент которой содержит указатели на соответствующие функции драйвера — точки входа. Старшее число, по существу, является указателем на элемент коммутатора устройств, обеспечивая, тем самым, ядру возможность вызова необходимой функции указанного драйвера. Таким образом, коммутатор устройств определяет базовый интерфейс драйвера устройств.
Этот интерфейс различен для блочных и символьных устройств. Ядро содержит коммутаторы устройств двух типов: bdevsw для блочных и cdevsw для символьных устройств. Ядро размещает отдельный массив для каждого типа коммутатора, и любой драйвер устройства имеет запись в соответствующем массиве. Если драйвер обеспечивает как блочный, так и символьный интерфейсы, его точки входа будут представлены в обоих массивах.
Типичное описание этих двух массивов имеет следующий вид (назначение различных точек входа мы рассмотрим далее в этом разделе):
struct bdevsw[] {
int (*d_open)();
int (*d_close)();
int (*d_strategy)();
int (*d_size)();
int (*d_xhalt)();
...
} bdevsw[];
struct cdevsw[] {
int (*d_open)();
int (*d_close)();
int (*d_read)();
int (*d_write)();
int (*d_ioctl)();
int (*d_xpoll)();
int (*d_xhalt)();
struct streamtab *d_str;
...
} cdevsw[];
Ядро вызывает функцию open() требуемого драйвера следующим образом:
(*bdevsw[getmajor(dev)].d_open)(dev, ...);
передавая ей в качестве одного из параметров переменную dev (типа dev_t), содержащую старший и младший номера. Макрос getmajor() служит для извлечения старшего номера из переменной dev. Благодаря этому драйвер имеет возможность определить, с каким младшим номером была вызвана функция open(), и выполнить соответствующие действия.
Коммутатор определяет абстрактный интерфейс драйвера устройства. Каждый драйвер обеспечивает соответствующую реализацию функций этого интерфейса. Если драйвер не поддерживает каких-либо функций стандартного интерфейса, он заменяет соответствующие точки входа специальными заглушками, предоставляемыми ядром. Когда ядру требуется запросить какую-либо операцию у драйвера устройства, оно определяет элемент коммутатора, соответствующий данному драйверу (используя его старший номер), и вызывает требуемую функцию.
В названиях точек входа драйвера используются определенные соглашения. Поскольку в ядре системы одновременно присутствует большое количество различных драйверов, каждый их них должен иметь уникальное имя во избежание проблем при компиляции (точнее, при редактировании связей) ядра. Каждый драйвер имеет уникальное двухсимвольное обозначение, используемое в качестве префикса названий функций. Например, драйвер виртуальной памяти ядра /dev/kmem имеет префикс mm, таким образом функции этого драйвера будут иметь названия mmopen(), mmclose(), mmread() и mmwrite().
В табл. 5.1 приведены некоторые точки входа, общие для различных типов драйверов, а символами xx, с которых начинается имя каждой функции, обозначен уникальный префикс драйвера. Стандартные точки входа драйвера отличаются для разных версий UNIX. Например, некоторые версии имеют расширенный коммутатор блочных устройств, включающий такие функции, как <i>xx</i>ioctl(), <i>xx</i>read() и <i>xx</i>write(). В некоторых версиях включены точки входа для инициализации и сброса шины данных.
Таблица 5.1. Типичные точки входа в драйвер устройства
Точка входа Сим- вольный Блочный Низкого уровня Назначение <i>xx</i>open() + + + Вызывается при каждой операции открытии устройства. Обеспечивает необходимую реинициализацию физического устройства и внутренних данных драйвера. Например, для каждого последующего открытия драйвера могут размещаться дополнительные буферы, обеспечивающие возможность независимой работы с устройством нескольким процессам. <i>xx</i>close() + + + Вызывается, когда число ссылок на данный драйвер становится равным нулю, т. е. ни один из процессов системы не работает с устройством (не имеет открытым соответствующий файл устройства). Может вызывать отключение физического устройства. Например, драйвер накопителя на магнитной ленте может перемотать ленту в начало. <i>xx</i>read() + - + Производит чтение данных от устройства. <i>xx</i>write() + - + Производит запись данных на устройство. <i>xx</i>ioctl() + - + Является общим интерфейсом управления устройством. Драйвер может определить набор команд, которые могут быть переданы ему, например с помощью системного вызова ioctl(2). <i>xx</i>intr() + + + Вызывается при поступлении прерывания, связанного с данным устройством. Может выполнить копирование данных от устройства в промежуточные буферы, которые затем считываются функцией <i>xx</i>read() по запросу прикладного процесса. <i>xx</i>poll() + - + Производит опрос устройства. Обычно используется для устройств, не поддерживающих прерывания, например, для определения поступления данных для чтения. <i>xx</i>halt() + + + Вызывается для останова драйвера при останове системы или при выгрузке драйвера. <i>xx</i>strategy() - + + Общая точка входа для операций блочного ввода/вывода. Название функции говорит о том, что устройство может обеспечивать собственную стратегию обработки поступающих запросов, например, изменять их порядок для повышения производительности ввода/вывода. Если устройство занято, функция помещает запросы в очередь. В этом случае фактический ввод/вывод инициирует функция обработки прерывания, которая вызывается, когда устройство закончит предыдущую операцию ввода/вывода. xxprint() - + + Выводит сообщение драйвера на консоль, обычно при запуске системы.