Рис. 5.1. Драйверы устройств UNIX
Не все драйверы служат для работы с физическими устройствами, такими как сетевой адаптер, последовательный порт или монитор. Часть драйверов служат для предоставления различных услуг ядра прикладным процессам и не имеют непосредственного отношения к аппаратной части компьютера. Такие драйверы называются программными или драйверами псевдоустройств . Можно привести несколько примеров псевдоустройств и соответствующих им программных драйверов:
/dev/kmem |
Обеспечивает доступ к виртуальной памяти ядра. Зная виртуальные адреса внутренних структур ядра, процесс может считывать хранящуюся в них информацию. С помощью этого драйвера может, например, быть реализована версия утилиты ps(1) , выводящей информацию о состоянии процессов в системе. |
/ dev/ksyms |
Обеспечивает доступ к разделу исполняемого файла ядра, содержащего таблицу символов. Совместно с драйвером /dev/kmemобеспечивает удобный интерфейс для анализа внутренних структур ядра. |
/dev/mem |
Обеспечивает доступ к физической памяти компьютера. |
/dev/null |
Является "нулевым" устройством . При записи в это устройство данные просто удаляются, а при чтении процессу возвращается 0 байтов. Примеры использования этого устройства рассматривались в главе 1, когда с помощью /dev/nullмы подавляли вывод сообщений об ошибках. |
/dev/zero |
Обеспечивает заполнение нулями указанного буфера. Этот драйвер часто используется для инициализации области памяти. |
Базовая архитектура драйверов
Драйвер устройства адресуется старшим номером (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()
, и выполнить соответствующие действия.
Коммутатор определяет абстрактный интерфейс драйвера устройства. Каждый драйвер обеспечивает соответствующую реализацию функций этого интерфейса. Если драйвер не поддерживает каких-либо функций стандартного интерфейса, он заменяет соответствующие точки входа специальными заглушками, предоставляемыми ядром. Когда ядру требуется запросить какую-либо операцию у драйвера устройства, оно определяет элемент коммутатора, соответствующий данному драйверу (используя его старший номер), и вызывает требуемую функцию.
Читать дальше