logo
Лекции_по_ОС / ТОС_11_п_вв_выв_слайды

Базовая архитектура драйверов

Драйвер устройства адресуется старшим номером (major number) устройства.

Напомним, что среди атрибутов специальных файлов устройств, которые обеспечивают пользовательский интерфейс доступа к периферии компьютера, это число присутствует наряду с другим, также имеющим отношение к драйверу, — младшим номером (minor number).

Младший номер интерпретируется самим драйвером (например, для клонов, оно задает старшее число устройства, которое требуется "размножить").

пример использования младших номеров(minor number). драйвером диска.

доступ к любому из разделов диска осуществляется одним и тем же драйвером (один и тот же старший номер), младший номер указывает, к какому именно разделу требуется обеспечить доступ.

Доступ к драйверу осуществляется ядром через специальную структуру данных (коммутатор устройств), каждый элемент которой содержит указатели на соответствующие функции драйвера — точки входа.

Старшее число, является указателем на элемент коммутатора устройств обеспечивая, тем самым, ядру возможность вызова необходимой функции указанного драйвера.

Т.о., коммутатор устройств определяет базовый интерфейс драйвера устройств.

Этот интерфейс различен для блочных и символьных устройств.

Ядро содержит коммутаторы устройств двух типов:

Ядро размещает отдельный массив для каждого типа коммутатора, и любой драйвер устройства имеет запись в соответствующем массиве.

Если драйвер обеспечивает как блочный, так и символьный интерфейсы, его точки входа будут представлены в обоих массивах.

Типичное описание этих двух массивов имеет следующий вид :

struct bdevsw[] {

j.nt (*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 приведены некоторые точки входа, общие для различных типов драйверов, а символами хх, с которых начинается имя каждой функции, обозначен уникальный префикс драйвера. Стандартные точки входа драйвера отличаются для разных версий UNIX. Например, некоторые версии имеют расширенный коммутатор блочных устройств, включающий такие функци, как xxioctl(), xxreadO и xxwrite(). В некоторых версиях включены точки входа для инициализации и сброса шины данных.

Ядро вызывает те или иные функции драйвера в зависимости от запроса. Например, если процесс выполняет системный вызов read(2) для специального файла символьного устройства, ядро вызовет функцию xxread() для соответствующего символьного драйвера.

Если же процесс запрашивает ту же операцию для обычного дискового файла, ядро вызовет процедуру xxstrategy() для блочного драйвера, обслуживающего данную файловую систему.

пять основных случаев, в которых ядро обращается к функциям драйвера:

На рис. 5.2 и 5.3 приведены схемы доступа к драйверам символьного и блочного устройств.

Как видно из рисунков, схема обработки запроса ядром UNIX различна для символьных и блочных устройств.

При обсуждении точек входа драйверов устройств следует иметь в виду, что большинство функций драйвера, отвечающих за передачу данных, осуществляют копирование информации из адресного пространства ядра, в котором и находится сам драйвер, в адресное пространство задачи. Когда ядро вызывает функцию драйвера, все действия выполняются в системном контексте процесса. Однако схема вызова функций может быть различной:

Различия в контексте и причинах вызова тех или иных функций драйвера позволяют представить драйвер устройства состоящим из двух частей: верхней части (top half) и нижней части (bottom half).

Функции верхней части драйвера имеют синхронный характер, т. е. Вызываются по определенным запросам прикладного процесса и выполняются в его контексте. Функции ввода/вывода и управления принадлежат верхней части драйвера.

Таким образом, для этих функций доступно адресное пространство и u-area.процесса, и при необходимости эти функции могут перевести процесс в состояние сна (вызовом функции sleep () ядра).

Вызов функций нижней части носит асинхронный характер. Например, момент вызова функции обработки прерываний нельзя предугадать, и ядро не может контролировать, когда эта функция будет вызвана. Выполнение таких функций происходит в контексте ядра и обычно не имеет отношения к контексту текущего процесса.

Таким образом, функции системного контекста не имеют права адресовать структуры данных текущего процесса, например его u-area, а также не могут перевести процесс в состояние сна, поскольку это заблокирует процесс, не имеющий непосредственного отношения к работе драйвера.

Две части драйвера требуют синхронизации. Например, в случае, когда функции обеих частей используют одну и ту же структуру данных, функция верхней части при выполнении должна заблокировать прерывания на период работы с "разделяемой" областью памяти. В противном случае прерывание может поступить в тот момент, когда целостность структуры данных нарушена, что приведет к непредсказуемым результатам.

Все представленные выше функции, за исключением xxhalt(), xxpoll() и xxintr (), принадлежат верхней части драйвера. Функция xxhalt() вызывается ядром при останове системы и, таким образом, имеет системный контекст, не связанный с контекстом прикладного процесса.

Функция xxpoll() обычно вызывается при обработке ядром прерывания таймера для всех устройств, указанных как опрашиваемые. Это необходимо, в частности, для устройств, которые не могут или "не хотят” использовать аппаратные прерывания. Вместо этого xxpoll() может использоваться для эмуляции прерываний, например вызывая функцию xxintr() на каждый n-ный тик системного таймера. Поэтому и функция xxpoll() и функция обработки прерывания xxintrO не могут рассчитывать на контекст прикладного процесса.

В большинстве версий UNIX функции опроса и обработки прерываний вызываются не через коммутатор а через специальные таблицы ядра.

В UNIX SVR4 определены две дополнительные точки входа – init() и start {}. Драйвер регистрирует эти функции в таблицах ядра io_dinit() и io_start (). Код начальной загрузки системы запускает функции xxinitO перед инициализацией ядра, а функции xxstart сразу же после инициализации.