Страница:
Подсистема управления вводом-выводом позволяет процессам поддерживать
связь с периферийными устройствами, такими как накопители на магнитных дис-
ках и лентах, терминалы, принтеры и сети, с одной стороны, и с модулями яд-
ра, которые управляют устройствами и именуются драйверами устройств, с дру-
гой. Между драйверами устройств и типами устройств обычно существует одноз-
начное соответствие: в системе может быть один дисковый драйвер для управле-
ния всеми дисководами, один терминальный драйвер для управления всеми терми-
налами и один ленточный драйвер для управления всеми ленточными накопителя-
ми. Если в системе имеются однотипные устройства, полученные от разных изго-
товителей - например, две марки ленточных накопителей, - в этом случае можно
трактовать однотипные устройства как устройства двух различных типов и иметь
для них два отдельных драйвера, поскольку таким устройствам для выполнения
одних и тех же операций могут потребоваться разные последовательности ко-
манд. Один драйвер управляет множеством физических устройств данного типа.
Например, один терминальный драйвер может управлять всеми терминалами, подк-
люченными к системе. Драйвер различает устройства, которыми управляет: вы-
ходные данные, предназначенные для одного терминала, не должны быть посланы
на другой.
Система поддерживает "программные устройства", с каждым из которых не
связано ни одно конкретное физическое устройство. Например, как устройство
трактуется физическая память, чтобы позволить процессу обращаться к ней изв-
не, пусть даже память не является периферийным устройством. Команда ps обра-
щается к информационным структурам ядра в физической памяти, чтобы сообщить
статистику процессов. Еще один пример: драйверы могут вести трассировку за-
писей в удобном для отладки виде, а драйвер трассировки дает возможность
пользователям читать эти записи. Наконец, профиль ядра, рассмотренный в гла-
ве 8, выполнен как драйвер: процесс записывает адреса программ ядра, обнару-
женных в таблице идентификаторов ядра, и читает результаты профилирования.
В этой главе рассматривается взаимодействие между процессами и подсисте-
мой управления вводом-выводом, а также между машиной и драйверами устройств.
Исследуется общая структура и функционирование драйверов и в качестве приме-
ров общего взаимодействия рассматриваются дисковые и терминальные драйверы.
Завершает главу описание нового метода реализации драйверов потоковых уст-
ройств.
В системе UNIX имеется два типа устройств - устройства вводавывода бло-
ками и устройства неструктурированного или посимвольного ввода-вывода. Как
уже говорилось в главе 2, устройства ввода-вывода блоками, такие как диски и
ленты, для остальной части системы выглядят как запоминающие устройства с
произвольной выборкой; к устройствам посимвольного ввода-вывода относятся
все другие устройства, в том числе терминалы и сетевое оборудование. Устрой-
ства ввода-вывода блоками могут иметь интерфейс и с устройствами посимволь-
ного ввода-вывода.
Пользователь взаимодействует с устройствами через посредничество файло-
вой системы (см. Рисунок 2.1). Каждое устройство имеет имя, похожее на имя
файла, и пользователь обращается к нему как к файлу. Специальный файл уст-
ройства имеет индекс и занимает место в иерархии каталогов файловой системы.
Файл устройства отличается от других файлов типом файла, хранящимся в его
индексе, либо "блочный", либо "символьный специальный", в зависимости от ус-
290
тройства, которое этот файл представляет. Если устройство имеет как блочный,
так и символьный интерфейс, его представляют два файла: специальный файл ус-
тройства ввода-вывода блоками и специальный файл устройства посимвольного
ввода-вывода. Системные функции для обычных файлов, такие как open, close,
read и write, имеют то же значение и для устройств, в чем мы убедимся позже.
Системная функция ioctl предоставляет процессам возможность управлять уст-
ройствами посимвольного ввода-вывода, но не применима в отношении к файлам
обычного типа (*). Тем не менее, драйверам устройств нет необходимости под-
держивать полный набор системных функций. Например, вышеупомянутый драйвер
трассировки дает процессам возможность читать записи, созданные другими
драйверами, но не позволяет создавать их.
Задание конфигурации системы это процедура указания администраторами
значений параметров, с помощью которых производится настройка системы. Неко-
торые из параметров указывают размеры таблиц ядра, таких как таблица процес-
сов, таблица индексов и таблица файлов, а также сколько буферов помещается в
буферном пуле. С помощью других параметров указывается конфигурация устрой-
ств, то есть производятся конкретные указания ядру, какие устройства включа-
ются в данную системную реализацию и их "адрес". Например, в конфигурации
может быть указано, что терминальная плата вставлена в соответствующий разъ-
ем на аппаратной панели.
Существует три стадии, на которых может быть указана конфигурация уст-
ройств. Во-первых, администраторы могут кодировать информацию о конфигурации
в файлах, которые транслируются и компонуются во время построения ядра. Ин-
формация о конфигурации обычно указывается в простом формате, и программа
конфигурации преобразует ее в файл, готовый для трансляции. Во-вторых, адми-
нистраторы могут указывать информацию о конфигурации после того, как система
уже запущена; ядро динамически корректирует внутренние таблицы конфигурации.
Наконец, самоидентифицирующиеся устройства дают ядру возможность узнать, ка-
кие из устройств включены. Ядро считывает аппаратные ключи для самонастрой-
ки. Подробности задания системной конфигурации выходят за пределы этой кни-
ги, однако во всех случаях результатом процедуры задания конфигурации явля-
ется генерация или заполнение таблиц, составляющих основу программ ядра.
Интерфейс "ядро - драйвер" описывается в таблице ключей устройств вво-
да-вывода блоками и в таблице ключей устройств посимвольного ввода-вывода
(Рисунок 10.1). Каждый тип устройства имеет в таблице точки входа, которые
при выполнении системных функций адресуют ядро к соответствующему драйверу.
Функции open и close, вызываемые файлом устройства, "пропускаются" через
таблицы ключей устройств в соответствии с типом файла. Функции mount и
umount так же вызывают выполнение процедур открытия и закрытия устройств, но
для устройств ввода-вывода блоками. Функции read и write, вызываемые устрой-
ствами ввода-вывода блоками и файлами в смонтированных файловых системах,
запускают алгоритмы работы с буферным кешем, инициирующие реализацию страте-
гической процедуры работы с устройствами. Некоторые из драйверов запускают
эту процедуру изнутри из процедур чтения и записи. Более подробно взаимодей-
ствие с каждым драйвером рассматривается в следующем разделе.
Интерфейс "аппаратура - драйвер" состоит из машинно-зависимых управляю-
щих регистров или команд ввода-вывода для управления устройствами и вектора-
ми прерываний: когда происходит прерывание от устройства, система идентифи-
цирует устройство, вызвавшее прерывание, и запускает программу обработки со-
----------------------------------------
(*) И наоборот, системная функция fcntl обеспечивает контроль над действия-
ми, производимыми на уровне дескриптора файла, но не на уровне устройст-
ва. В других реализациях функция ioctl применима для файлов всех типов.
291
ответствующего прерывания. Очевидно, что "программные устройства", такие как
драйвер системы построения профиля ядра (глава 8) не имеют аппаратного ин-
терфейса, однако программы обработки других прерываний могут обращаться к
"обработчику программного прерывания" непосредственно. Например, программа
обработки прерывания по таймеру обращается к программе обработки прерывания
системы построения профиля ядра.
Администраторы устанавливают специальные файлы устройств командой mknod,
в которой указывается тип файла (блочный или символьный), старший и младший
номера устройства. Команда mknod запускает выполнение системной функции с
тем же именем, создающей файл устройства. Например, в командной строке
mknod /dev/tty13 c 2 13
"/dev/tty13" - имя файла устройства, "c" указывает, что тип файла - "сим-
вольный специальный" ("b", соответственно, блочный), "2" - старший номер ус-
тройства, "13" - младший номер устройства. Старший номер устройства показы-
вает его тип, которому соответствует точка входа в таблице ключей устройств,
младший номер устройства - это порядковый номер единицы устройства данного
типа. Если процесс открывает специальный блочный файл с именем "/dev/dsk1" и
кодом 0, ядро запускает программу gdopen в точке 0 таблицы ключей устройств
блочного ввода-вывода (Рисунок 10.2); если процесс читает специальный сим-
вольный файл с именем "/dev/mem" и кодом 3,
Подсистема управления файлами
+------------------------------------------------------------+
| open close |
| open close read write ioctl read write |
| mount umount |
+--+-----+----+-----+-----+------------+-----+-----+-----+---+
| | | | | | | +-+-----+---+
| | | | | | | | функции |
| | | | | | | | работы с |
| | | | | | | | буферным |
| | | | | | | | кешем |
| | | | | | | +-----+-----+
+--+-----+----+-----+-----+-+ ++-----+---------+-----+
| Таблица ключей устройств | | Таблица ключей уст- |
| посимвольного ввода-вывода| | ройств ввода-вывода |
| | | блоками |
+--+-----+----+-----+-----+-+ +--+------+--------+---+
+--+-----+----+-----+-----+-+ +--+------+--------+---+
|open close read write ioctl| Точки | open close strategy|
| | входа | |
| Драйвер | для | Драйвер |
| | драй- | |
|программа обработки преры- | веров | программа обработки |
| ваний от устройства | |прерываний от устройст|
+------------+--------------+ +-----------+----------+
+--------+----------+ +---------+---------+
| Вектор прерывания | | Вектор прерывания |
+--------+----------+ +---------+---------+
+-------------------+----------------+
|
Прерывания от устройств
Рисунок 10.1. Точки входа для драйверов
292
ядро запускает программу mmread в точке 3 таблицы ключей устройств посим-
вольного ввода-вывода. Программа nulldev - это "пустая" программа, использу-
емая в тех случаях, когда отсутствует необходимость в конкретной функции
драйвера. С одним старшим номером устройства может быть связано множество
периферийных устройств; младший номер устройства позволяет отличить их одно
от другого. Не нужно создавать специальные файлы устройств при каждой заг-
рузке системы; их только нужно корректировать, если изменилась конфигурация
системы, например, если к установленной конфигурации были добавлены устройс-
тва.
В этом разделе рассматривается взаимодействие ядра с драйверами устрой-
ств. При выполнении тех системных функций, которые используют дескрипторы
файлов, ядро, следуя за указателями, хранящимися в пользовательском дескрип-
торе файла, обращается к таблице
+-----------------------------------------------+
| таблица ключей устройств ввода-вывода блоками |
+-------+--------+---------+--------------------+
| вход | open | close | strategy |
+-------+--------+---------+--------------------+
| 0 | gdopen | gdclose | gdstrategy |
+-------+--------+---------+--------------------+
| 1 | gtopen | gtclose | gtstrategy |
+-------+--------+---------+--------------------+
+----------------------------------------------------------------+
| таблица ключей устройств посимвольного ввода-вывода |
+------+-----------+-----------+---------+-----------+-----------+
| вход | open | close | read | write | ioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 0 | conopen | conclose | conread | conwrite | conioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 1 | dzbopen | dzbclose | dzbread | dzbwrite | dzbioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 2 | syopen | nulldev | syread | sywrite | syioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 3 | nulldev | nulldev | mmread | mmwrite | nodev |
+------+-----------+-----------+---------+-----------+-----------+
| 4 | gdopen | gdclose | gdread | gdwrite | nodev |
+------+-----------+-----------+---------+-----------+-----------+
| 5 | gtopen | gtclose | gtread | gtwrite | nodev |
+------+-----------+-----------+---------+-----------+-----------+
Рисунок 10.2. Пример заполнения таблиц ключей устройств ввода-
вывода блоками и символами
файлов ядра и к индексу, где оно проверяет тип файла, и переходит к таблице
ключей устройств ввода-вывода блоками или символами. Ядро извлекает из ин-
декса старший и младший номера устройства, использует старший номер в качес-
тве указателя на точку входа в соответствующей таблице и вызывает выполнение
функции драйвера в соответствии с выполняемой системной функцией, передавая
младший номер в качестве параметра. Важным различием в реализации системных
функций для файлов устройств и для файлов обычного типа является то, что ин-
декс специального файла не блокируется в то время, когда ядро выполняет
программу драйвера. Драйверы часто приостанавливают свою работу, ожидая свя-
зи с аппаратными средствами или поступления данных, поэтому ядро не в состо-
293
янии определить, на какое время процесс будет приостановлен. Если индекс
заблокирован, другие процессы, обратившиеся к индексу (например, посредством
системной функции stat), приостановятся на неопределенное время, поскольку
один процесс приостановил драйвер.
Драйвер устройства интерпретирует параметры вызова системной функции в
отношении устройства. Драйвер поддерживает структуры данных, описывающие
состояние каждой контролируемой единицы данного типа устройства; функции
драйвера и программы обработки прерываний реализуются в соответствии с сос-
тоянием драйвера и с тем, какое действие выполняется в этот момент (напри-
мер, данные вводятся или выводятся). Теперь рассмотрим каждый интерфейс бо-
лее подробно.
+------------------------------------------------------------+
| алгоритм open /* для драйверов устройств */ |
| входная информация: имя пути поиска |
| режим открытия |
| выходная информация: дескриптор файла |
| { |
| преобразовать имя пути поиска в индекс, увеличить значе-|
| ние счетчика ссылок в индексе; |
| выделить в таблице файлов место для пользовательского |
| дескриптора файла, как при открытии обычного файла; |
| |
| выбрать из индекса старший и младший номера устройства; |
| |
| сохранить контекст (алгоритм setjmp) в случае передачи |
| управления от драйвера; |
| |
| если (устройство блочного типа) |
| { |
| использовать старший номер устройства в качестве ука-|
| зателя в таблице ключей устройств ввода-вывода бло- |
| ками; |
| вызвать процедуру открытия драйвера по данному индек-|
| су: передать младший номер устройства, режимы откры-|
| тия; |
| } |
| в противном случае |
| { |
| использовать старший номер устройства в качестве ука-|
| зателя в таблице ключей устройств посимвольного вво-|
| да-вывода; |
| вызвать процедуру открытия драйвера по данному индек-|
| су: передать младший номер устройства, режимы откры-|
| тия; |
| } |
| |
| если (открытие в драйвере не выполнилось) |
| привести таблицу файлов к первоначальному виду, |
| уменьшить значение счетчика в индексе; |
| } |
+------------------------------------------------------------+
Рисунок 10.3. Алгоритм открытия устройства
При открытии устройства ядро следует той же процедуре, что и при откры-
294
тии файлов обычного типа (см. раздел 5.1), выделяя в памяти индексы, увели-
чивая значение счетчика ссылок и присваивая значение точки входа в таблицу
файлов и пользовательского дескриптора файла. Наконец, ядро возвращает зна-
чение пользовательского дескриптора файла вызывающему процессу, так что отк-
рытие устройства выглядит так же, как и открытие файла обычного типа. Одна-
ко, перед тем, как вернуться в режим задачи, ядро запускает
зависящую от устройства процедуру open (Рисунок 10.3). Для устройства вво-
да-вывода блоками запускается процедура open, закодированная в таблице клю-
чей устройств ввода-вывода блоками, для устройств посимвольного ввода-вывода
- процедура open, закодированная в соответствующей таблице. Если устройство
имеет как блочный, так и символьный тип, ядро запускает процедуру open, со-
ответствующую типу файла устройства, открытого пользователем: обе процедуры
могут даже быть идентичны, в зависимости от конкретного драйвера.
Зависящая от типа устройства процедура open устанавливает связь между
вызывающим процессом и открываемым устройством и инициализирует информацион-
ные структуры драйвера. Например, процедура open для терминала может приос-
тановить процесс до тех пор, пока в машину не поступит сигнал (аппаратный) о
том, что пользователь предпринял попытку зарегистрироваться. После этого
инициализируются информационные структуры драйвера в соответствии с приняты-
ми установками терминала (например, скоростью передачи информации в бодах).
Для "программных устройств", таких как память системы, процедура open может
не включать в себя инициализацию.
Если во время открытия устройства процессу пришлось приостановиться по
какой-либо из внешних причин, может так случиться, что событие, которое дол-
жно было бы вызвать возобновление выполнения процесса, так никогда и не про-
изойдет. Например, если на данном терминале еще не зарегистрировался ни один
из пользователей, процесс getty, "открывший" терминал (раздел 7.9), приоста-
навливается до тех пор, пока пользователем не будет предпринята попытка ре-
гистрации, при этом может пройти достаточно большой промежуток времени. Ядро
должно иметь возможность возобновить выполнение процесса и отменить вызов
функции open по получении сигнала: ему следует сбросить индекс, отменить
точку входа в таблице файлов и пользовательский дескриптор файла, которые
были выделены перед входом в драйвер, поскольку открытие не произошло. Ядро
сохраняет контекст процесса, используя алгоритм setjmp (раздел 6.4.4), преж-
де чем запустить процедуру open; если процесс возобновляется по сигналу, яд-
ро восстанавливает контекст процесса в том состоянии, которое он имел перед
обращением к драйверу, используя алгоритм longjmp (раздел 6.4.4), и возвра-
щает системе все выделенные процедуре open структуры данных. Точно так же и
драйвер может уловить сигнал и очистить доступные ему структуры данных, если
это необходимо. Ядро также переустанавливает структуры данных файловой сис-
темы, когда драйвер сталкивается с исключительными ситуациями, такими, как
попытка пользователя обратиться к устройству, отсутствующему в данной конфи-
гурации. В подобных случаях функция open не выполняется.
Процессы могут указывать значения различных параметров, характеризующие
особенности выполнения процедуры открытия. Из них наиболее часто использует-
ся "no delay" (без задержки), означающее, что процесс не будет приостановлен
во время выполнения процедуры open, если устройство не готово. Системная
функция open возвращает управление немедленно и пользовательский процесс не
узнает, произошло ли аппаратное соединение или нет. Открытие устройства с
параметром "no delay", кроме всего прочего, затронет семантику вызова функ-
ции read, что мы увидим далее (раздел 10.3.4).
Если устройство открывается многократно, ядро обрабатывает пользователь-
ские дескрипторы файлов, индекс и записи в таблице файлов так, как это опи-
сано в главе 5, запуская определяемую типом устройства процедуру open при
каждом вызове системной функции open. Таким образом, драйвер устройства мо-
жет подсчитать, сколько раз устройство было "открыто", и прервать выполнение
функции open, если количество открытий приняло недопустимое значение. Напри-
295
мер, имеет смысл разрешить процессам многократно "открывать" терминал на за-
пись для того, чтобы пользователи могли обмениваться сообщениями. Но при
этом не следует допускать многократного "открытия" печатающего устройства
для одновременной записи, так как процессы могут затереть друг другу инфор-
мацию. Эти различия имеют смысл скорее на практике, нежели на стадии разра-
ботки: разрешение одновременной записи на терминалы способствует установле-
нию взаимодействия между пользователями; запрещение одновременной записи на
принтеры служит повышению читабельности машинограмм (**).
+------------------------------------------------------------+
| алгоритм close /* для устройств */ |
| входная информация: дескриптор файла |
| выходная информация: отсутствует |
| { |
| выполнить алгоритм стандартного закрытия (глава 5ххх); |
| если (значение счетчика ссылок в таблице файлов не 0) |
| перейти на finish; |
| если (существует еще один открытый файл, старший и млад-|
| ший номера которого совпадают с номерами закрываемого |
| устройства) |
| перейти на finish; /* не последнее закрытие */ |
| если (устройство символьного типа) |
| { |
| использовать старший номер в качестве указателя в |
| таблице ключей устройства посимвольного ввода-выво- |
| да; |
| вызвать процедуру закрытия, определяемую типом драй- |
| вера и передать ей в качестве параметра младший но- |
| мер устройства; |
| } |
| если (устройство блочного типа) |
| { |
| если (устройство монтировано) |
| перейти на finish; |
| переписать блоки устройства из буферного кеша на уст-|
| ройство; |
| использовать старший номер в качестве указателя в |
| таблице ключей устройства ввода-вывода блоками; |
| вызвать процедуру закрытия, определяемую типом драй- |
| вера и передать ей в качестве параметра младший но- |
| мер устройства; |
| сделать недействительными блоки устройства, оставшие-|
| ся в буферном кеше; |
| } |
| finish: |
| освободить индекс; |
| } |
+------------------------------------------------------------+
Рисунок 10.4. Алгоритм закрытия устройства
----------------------------------------
(**) На практике вывод на печать обычно управляется специальными процессами
буферизации, и права доступа устанавливаются таким образом, чтобы толь-
ко система буферизации могла обращаться к принтеру.
296
Процесс разрывает связь с открытым устройством, закрывая его. Однако,
ядро запускает определяемую типом устройства процедуру close только в пос-
леднем вызове функции close для этого устройства, и то только если не оста-
лось процессов, которым устройство необходимо открытым, поскольку процедура
закрытия устройства завершается разрывом аппаратного соединения; отсюда яс-
но, что ядру следует подождать, пока не останется ни одного процесса, обра-
щающегося к устройству. Поскольку ядро запускает процедуру открытия устройс-
тва при каждом вызове системной функции open, а процедуру закрытия только
один раз, драйверу устройства неведомо, сколько процессов используют устрой-
ство в данный момент. Драйверы могут легко выйти из строя, если при их напи-
сании не соблюдалась осторожность: когда при выполнении процедуры close они
приостанавливают свою работу и какой-нибудь процесс открывает устройство до
того, как завершится процедура закрытия, устройство может стать недоступным
для работы, если в результате комбинации вызовов open и close сложилась не-
распознаваемая ситуация.
Алгоритм закрытия устройства похож на алгоритм закрытия файла обычного
типа (Рисунок 10.4). Однако, до того, как ядро освобождает индекс, в нем вы-
полняются действия, специфичные для файлов устройств.
1. Просматривается таблица файлов для того, чтобы убедиться в том, что ни
одному из процессов не требуется, чтобы устройство было открыто. Чтобы
установить, что вызов функции close для устройства является последним,
недостаточно положиться на значение счетчика ссылок в таблице файлов,
поскольку несколько процессов могут обращаться к одному и тому же уст-
ройству, используя различные точки входа в таблице файлов. Так же недос-
таточно положиться на значение счетчика в таблице индексов, поскольку
одному и тому же устройству могут соответствовать несколько файлов уст-
ройства. Например, команда ls -l покажет, что одному и тому же устройст-
ву символьного типа ("c" в начале строки) соответствуют два файла уст-
ройства, старший и младший номера у которых (9 и 1) совпадают. Значение
счетчика связей для каждого файла, равное 1, говорит о том, что имеется
два индекса.
crw--w--w- 1 root vis 9, 1 Aug 6 1984 /dev/tty01
crw--w--w- 1 root unix 9, 1 May 3 15:02 /dev/tty01
Если процессы открывают оба файла независимо один от другого, они обра-
связь с периферийными устройствами, такими как накопители на магнитных дис-
ках и лентах, терминалы, принтеры и сети, с одной стороны, и с модулями яд-
ра, которые управляют устройствами и именуются драйверами устройств, с дру-
гой. Между драйверами устройств и типами устройств обычно существует одноз-
начное соответствие: в системе может быть один дисковый драйвер для управле-
ния всеми дисководами, один терминальный драйвер для управления всеми терми-
налами и один ленточный драйвер для управления всеми ленточными накопителя-
ми. Если в системе имеются однотипные устройства, полученные от разных изго-
товителей - например, две марки ленточных накопителей, - в этом случае можно
трактовать однотипные устройства как устройства двух различных типов и иметь
для них два отдельных драйвера, поскольку таким устройствам для выполнения
одних и тех же операций могут потребоваться разные последовательности ко-
манд. Один драйвер управляет множеством физических устройств данного типа.
Например, один терминальный драйвер может управлять всеми терминалами, подк-
люченными к системе. Драйвер различает устройства, которыми управляет: вы-
ходные данные, предназначенные для одного терминала, не должны быть посланы
на другой.
Система поддерживает "программные устройства", с каждым из которых не
связано ни одно конкретное физическое устройство. Например, как устройство
трактуется физическая память, чтобы позволить процессу обращаться к ней изв-
не, пусть даже память не является периферийным устройством. Команда ps обра-
щается к информационным структурам ядра в физической памяти, чтобы сообщить
статистику процессов. Еще один пример: драйверы могут вести трассировку за-
писей в удобном для отладки виде, а драйвер трассировки дает возможность
пользователям читать эти записи. Наконец, профиль ядра, рассмотренный в гла-
ве 8, выполнен как драйвер: процесс записывает адреса программ ядра, обнару-
женных в таблице идентификаторов ядра, и читает результаты профилирования.
В этой главе рассматривается взаимодействие между процессами и подсисте-
мой управления вводом-выводом, а также между машиной и драйверами устройств.
Исследуется общая структура и функционирование драйверов и в качестве приме-
ров общего взаимодействия рассматриваются дисковые и терминальные драйверы.
Завершает главу описание нового метода реализации драйверов потоковых уст-
ройств.
В системе UNIX имеется два типа устройств - устройства вводавывода бло-
ками и устройства неструктурированного или посимвольного ввода-вывода. Как
уже говорилось в главе 2, устройства ввода-вывода блоками, такие как диски и
ленты, для остальной части системы выглядят как запоминающие устройства с
произвольной выборкой; к устройствам посимвольного ввода-вывода относятся
все другие устройства, в том числе терминалы и сетевое оборудование. Устрой-
ства ввода-вывода блоками могут иметь интерфейс и с устройствами посимволь-
ного ввода-вывода.
Пользователь взаимодействует с устройствами через посредничество файло-
вой системы (см. Рисунок 2.1). Каждое устройство имеет имя, похожее на имя
файла, и пользователь обращается к нему как к файлу. Специальный файл уст-
ройства имеет индекс и занимает место в иерархии каталогов файловой системы.
Файл устройства отличается от других файлов типом файла, хранящимся в его
индексе, либо "блочный", либо "символьный специальный", в зависимости от ус-
290
тройства, которое этот файл представляет. Если устройство имеет как блочный,
так и символьный интерфейс, его представляют два файла: специальный файл ус-
тройства ввода-вывода блоками и специальный файл устройства посимвольного
ввода-вывода. Системные функции для обычных файлов, такие как open, close,
read и write, имеют то же значение и для устройств, в чем мы убедимся позже.
Системная функция ioctl предоставляет процессам возможность управлять уст-
ройствами посимвольного ввода-вывода, но не применима в отношении к файлам
обычного типа (*). Тем не менее, драйверам устройств нет необходимости под-
держивать полный набор системных функций. Например, вышеупомянутый драйвер
трассировки дает процессам возможность читать записи, созданные другими
драйверами, но не позволяет создавать их.
Задание конфигурации системы это процедура указания администраторами
значений параметров, с помощью которых производится настройка системы. Неко-
торые из параметров указывают размеры таблиц ядра, таких как таблица процес-
сов, таблица индексов и таблица файлов, а также сколько буферов помещается в
буферном пуле. С помощью других параметров указывается конфигурация устрой-
ств, то есть производятся конкретные указания ядру, какие устройства включа-
ются в данную системную реализацию и их "адрес". Например, в конфигурации
может быть указано, что терминальная плата вставлена в соответствующий разъ-
ем на аппаратной панели.
Существует три стадии, на которых может быть указана конфигурация уст-
ройств. Во-первых, администраторы могут кодировать информацию о конфигурации
в файлах, которые транслируются и компонуются во время построения ядра. Ин-
формация о конфигурации обычно указывается в простом формате, и программа
конфигурации преобразует ее в файл, готовый для трансляции. Во-вторых, адми-
нистраторы могут указывать информацию о конфигурации после того, как система
уже запущена; ядро динамически корректирует внутренние таблицы конфигурации.
Наконец, самоидентифицирующиеся устройства дают ядру возможность узнать, ка-
кие из устройств включены. Ядро считывает аппаратные ключи для самонастрой-
ки. Подробности задания системной конфигурации выходят за пределы этой кни-
ги, однако во всех случаях результатом процедуры задания конфигурации явля-
ется генерация или заполнение таблиц, составляющих основу программ ядра.
Интерфейс "ядро - драйвер" описывается в таблице ключей устройств вво-
да-вывода блоками и в таблице ключей устройств посимвольного ввода-вывода
(Рисунок 10.1). Каждый тип устройства имеет в таблице точки входа, которые
при выполнении системных функций адресуют ядро к соответствующему драйверу.
Функции open и close, вызываемые файлом устройства, "пропускаются" через
таблицы ключей устройств в соответствии с типом файла. Функции mount и
umount так же вызывают выполнение процедур открытия и закрытия устройств, но
для устройств ввода-вывода блоками. Функции read и write, вызываемые устрой-
ствами ввода-вывода блоками и файлами в смонтированных файловых системах,
запускают алгоритмы работы с буферным кешем, инициирующие реализацию страте-
гической процедуры работы с устройствами. Некоторые из драйверов запускают
эту процедуру изнутри из процедур чтения и записи. Более подробно взаимодей-
ствие с каждым драйвером рассматривается в следующем разделе.
Интерфейс "аппаратура - драйвер" состоит из машинно-зависимых управляю-
щих регистров или команд ввода-вывода для управления устройствами и вектора-
ми прерываний: когда происходит прерывание от устройства, система идентифи-
цирует устройство, вызвавшее прерывание, и запускает программу обработки со-
----------------------------------------
(*) И наоборот, системная функция fcntl обеспечивает контроль над действия-
ми, производимыми на уровне дескриптора файла, но не на уровне устройст-
ва. В других реализациях функция ioctl применима для файлов всех типов.
291
ответствующего прерывания. Очевидно, что "программные устройства", такие как
драйвер системы построения профиля ядра (глава 8) не имеют аппаратного ин-
терфейса, однако программы обработки других прерываний могут обращаться к
"обработчику программного прерывания" непосредственно. Например, программа
обработки прерывания по таймеру обращается к программе обработки прерывания
системы построения профиля ядра.
Администраторы устанавливают специальные файлы устройств командой mknod,
в которой указывается тип файла (блочный или символьный), старший и младший
номера устройства. Команда mknod запускает выполнение системной функции с
тем же именем, создающей файл устройства. Например, в командной строке
mknod /dev/tty13 c 2 13
"/dev/tty13" - имя файла устройства, "c" указывает, что тип файла - "сим-
вольный специальный" ("b", соответственно, блочный), "2" - старший номер ус-
тройства, "13" - младший номер устройства. Старший номер устройства показы-
вает его тип, которому соответствует точка входа в таблице ключей устройств,
младший номер устройства - это порядковый номер единицы устройства данного
типа. Если процесс открывает специальный блочный файл с именем "/dev/dsk1" и
кодом 0, ядро запускает программу gdopen в точке 0 таблицы ключей устройств
блочного ввода-вывода (Рисунок 10.2); если процесс читает специальный сим-
вольный файл с именем "/dev/mem" и кодом 3,
Подсистема управления файлами
+------------------------------------------------------------+
| open close |
| open close read write ioctl read write |
| mount umount |
+--+-----+----+-----+-----+------------+-----+-----+-----+---+
| | | | | | | +-+-----+---+
| | | | | | | | функции |
| | | | | | | | работы с |
| | | | | | | | буферным |
| | | | | | | | кешем |
| | | | | | | +-----+-----+
+--+-----+----+-----+-----+-+ ++-----+---------+-----+
| Таблица ключей устройств | | Таблица ключей уст- |
| посимвольного ввода-вывода| | ройств ввода-вывода |
| | | блоками |
+--+-----+----+-----+-----+-+ +--+------+--------+---+
+--+-----+----+-----+-----+-+ +--+------+--------+---+
|open close read write ioctl| Точки | open close strategy|
| | входа | |
| Драйвер | для | Драйвер |
| | драй- | |
|программа обработки преры- | веров | программа обработки |
| ваний от устройства | |прерываний от устройст|
+------------+--------------+ +-----------+----------+
+--------+----------+ +---------+---------+
| Вектор прерывания | | Вектор прерывания |
+--------+----------+ +---------+---------+
+-------------------+----------------+
|
Прерывания от устройств
Рисунок 10.1. Точки входа для драйверов
292
ядро запускает программу mmread в точке 3 таблицы ключей устройств посим-
вольного ввода-вывода. Программа nulldev - это "пустая" программа, использу-
емая в тех случаях, когда отсутствует необходимость в конкретной функции
драйвера. С одним старшим номером устройства может быть связано множество
периферийных устройств; младший номер устройства позволяет отличить их одно
от другого. Не нужно создавать специальные файлы устройств при каждой заг-
рузке системы; их только нужно корректировать, если изменилась конфигурация
системы, например, если к установленной конфигурации были добавлены устройс-
тва.
В этом разделе рассматривается взаимодействие ядра с драйверами устрой-
ств. При выполнении тех системных функций, которые используют дескрипторы
файлов, ядро, следуя за указателями, хранящимися в пользовательском дескрип-
торе файла, обращается к таблице
+-----------------------------------------------+
| таблица ключей устройств ввода-вывода блоками |
+-------+--------+---------+--------------------+
| вход | open | close | strategy |
+-------+--------+---------+--------------------+
| 0 | gdopen | gdclose | gdstrategy |
+-------+--------+---------+--------------------+
| 1 | gtopen | gtclose | gtstrategy |
+-------+--------+---------+--------------------+
+----------------------------------------------------------------+
| таблица ключей устройств посимвольного ввода-вывода |
+------+-----------+-----------+---------+-----------+-----------+
| вход | open | close | read | write | ioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 0 | conopen | conclose | conread | conwrite | conioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 1 | dzbopen | dzbclose | dzbread | dzbwrite | dzbioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 2 | syopen | nulldev | syread | sywrite | syioctl |
+------+-----------+-----------+---------+-----------+-----------+
| 3 | nulldev | nulldev | mmread | mmwrite | nodev |
+------+-----------+-----------+---------+-----------+-----------+
| 4 | gdopen | gdclose | gdread | gdwrite | nodev |
+------+-----------+-----------+---------+-----------+-----------+
| 5 | gtopen | gtclose | gtread | gtwrite | nodev |
+------+-----------+-----------+---------+-----------+-----------+
Рисунок 10.2. Пример заполнения таблиц ключей устройств ввода-
вывода блоками и символами
файлов ядра и к индексу, где оно проверяет тип файла, и переходит к таблице
ключей устройств ввода-вывода блоками или символами. Ядро извлекает из ин-
декса старший и младший номера устройства, использует старший номер в качес-
тве указателя на точку входа в соответствующей таблице и вызывает выполнение
функции драйвера в соответствии с выполняемой системной функцией, передавая
младший номер в качестве параметра. Важным различием в реализации системных
функций для файлов устройств и для файлов обычного типа является то, что ин-
декс специального файла не блокируется в то время, когда ядро выполняет
программу драйвера. Драйверы часто приостанавливают свою работу, ожидая свя-
зи с аппаратными средствами или поступления данных, поэтому ядро не в состо-
293
янии определить, на какое время процесс будет приостановлен. Если индекс
заблокирован, другие процессы, обратившиеся к индексу (например, посредством
системной функции stat), приостановятся на неопределенное время, поскольку
один процесс приостановил драйвер.
Драйвер устройства интерпретирует параметры вызова системной функции в
отношении устройства. Драйвер поддерживает структуры данных, описывающие
состояние каждой контролируемой единицы данного типа устройства; функции
драйвера и программы обработки прерываний реализуются в соответствии с сос-
тоянием драйвера и с тем, какое действие выполняется в этот момент (напри-
мер, данные вводятся или выводятся). Теперь рассмотрим каждый интерфейс бо-
лее подробно.
+------------------------------------------------------------+
| алгоритм open /* для драйверов устройств */ |
| входная информация: имя пути поиска |
| режим открытия |
| выходная информация: дескриптор файла |
| { |
| преобразовать имя пути поиска в индекс, увеличить значе-|
| ние счетчика ссылок в индексе; |
| выделить в таблице файлов место для пользовательского |
| дескриптора файла, как при открытии обычного файла; |
| |
| выбрать из индекса старший и младший номера устройства; |
| |
| сохранить контекст (алгоритм setjmp) в случае передачи |
| управления от драйвера; |
| |
| если (устройство блочного типа) |
| { |
| использовать старший номер устройства в качестве ука-|
| зателя в таблице ключей устройств ввода-вывода бло- |
| ками; |
| вызвать процедуру открытия драйвера по данному индек-|
| су: передать младший номер устройства, режимы откры-|
| тия; |
| } |
| в противном случае |
| { |
| использовать старший номер устройства в качестве ука-|
| зателя в таблице ключей устройств посимвольного вво-|
| да-вывода; |
| вызвать процедуру открытия драйвера по данному индек-|
| су: передать младший номер устройства, режимы откры-|
| тия; |
| } |
| |
| если (открытие в драйвере не выполнилось) |
| привести таблицу файлов к первоначальному виду, |
| уменьшить значение счетчика в индексе; |
| } |
+------------------------------------------------------------+
Рисунок 10.3. Алгоритм открытия устройства
При открытии устройства ядро следует той же процедуре, что и при откры-
294
тии файлов обычного типа (см. раздел 5.1), выделяя в памяти индексы, увели-
чивая значение счетчика ссылок и присваивая значение точки входа в таблицу
файлов и пользовательского дескриптора файла. Наконец, ядро возвращает зна-
чение пользовательского дескриптора файла вызывающему процессу, так что отк-
рытие устройства выглядит так же, как и открытие файла обычного типа. Одна-
ко, перед тем, как вернуться в режим задачи, ядро запускает
зависящую от устройства процедуру open (Рисунок 10.3). Для устройства вво-
да-вывода блоками запускается процедура open, закодированная в таблице клю-
чей устройств ввода-вывода блоками, для устройств посимвольного ввода-вывода
- процедура open, закодированная в соответствующей таблице. Если устройство
имеет как блочный, так и символьный тип, ядро запускает процедуру open, со-
ответствующую типу файла устройства, открытого пользователем: обе процедуры
могут даже быть идентичны, в зависимости от конкретного драйвера.
Зависящая от типа устройства процедура open устанавливает связь между
вызывающим процессом и открываемым устройством и инициализирует информацион-
ные структуры драйвера. Например, процедура open для терминала может приос-
тановить процесс до тех пор, пока в машину не поступит сигнал (аппаратный) о
том, что пользователь предпринял попытку зарегистрироваться. После этого
инициализируются информационные структуры драйвера в соответствии с приняты-
ми установками терминала (например, скоростью передачи информации в бодах).
Для "программных устройств", таких как память системы, процедура open может
не включать в себя инициализацию.
Если во время открытия устройства процессу пришлось приостановиться по
какой-либо из внешних причин, может так случиться, что событие, которое дол-
жно было бы вызвать возобновление выполнения процесса, так никогда и не про-
изойдет. Например, если на данном терминале еще не зарегистрировался ни один
из пользователей, процесс getty, "открывший" терминал (раздел 7.9), приоста-
навливается до тех пор, пока пользователем не будет предпринята попытка ре-
гистрации, при этом может пройти достаточно большой промежуток времени. Ядро
должно иметь возможность возобновить выполнение процесса и отменить вызов
функции open по получении сигнала: ему следует сбросить индекс, отменить
точку входа в таблице файлов и пользовательский дескриптор файла, которые
были выделены перед входом в драйвер, поскольку открытие не произошло. Ядро
сохраняет контекст процесса, используя алгоритм setjmp (раздел 6.4.4), преж-
де чем запустить процедуру open; если процесс возобновляется по сигналу, яд-
ро восстанавливает контекст процесса в том состоянии, которое он имел перед
обращением к драйверу, используя алгоритм longjmp (раздел 6.4.4), и возвра-
щает системе все выделенные процедуре open структуры данных. Точно так же и
драйвер может уловить сигнал и очистить доступные ему структуры данных, если
это необходимо. Ядро также переустанавливает структуры данных файловой сис-
темы, когда драйвер сталкивается с исключительными ситуациями, такими, как
попытка пользователя обратиться к устройству, отсутствующему в данной конфи-
гурации. В подобных случаях функция open не выполняется.
Процессы могут указывать значения различных параметров, характеризующие
особенности выполнения процедуры открытия. Из них наиболее часто использует-
ся "no delay" (без задержки), означающее, что процесс не будет приостановлен
во время выполнения процедуры open, если устройство не готово. Системная
функция open возвращает управление немедленно и пользовательский процесс не
узнает, произошло ли аппаратное соединение или нет. Открытие устройства с
параметром "no delay", кроме всего прочего, затронет семантику вызова функ-
ции read, что мы увидим далее (раздел 10.3.4).
Если устройство открывается многократно, ядро обрабатывает пользователь-
ские дескрипторы файлов, индекс и записи в таблице файлов так, как это опи-
сано в главе 5, запуская определяемую типом устройства процедуру open при
каждом вызове системной функции open. Таким образом, драйвер устройства мо-
жет подсчитать, сколько раз устройство было "открыто", и прервать выполнение
функции open, если количество открытий приняло недопустимое значение. Напри-
295
мер, имеет смысл разрешить процессам многократно "открывать" терминал на за-
пись для того, чтобы пользователи могли обмениваться сообщениями. Но при
этом не следует допускать многократного "открытия" печатающего устройства
для одновременной записи, так как процессы могут затереть друг другу инфор-
мацию. Эти различия имеют смысл скорее на практике, нежели на стадии разра-
ботки: разрешение одновременной записи на терминалы способствует установле-
нию взаимодействия между пользователями; запрещение одновременной записи на
принтеры служит повышению читабельности машинограмм (**).
+------------------------------------------------------------+
| алгоритм close /* для устройств */ |
| входная информация: дескриптор файла |
| выходная информация: отсутствует |
| { |
| выполнить алгоритм стандартного закрытия (глава 5ххх); |
| если (значение счетчика ссылок в таблице файлов не 0) |
| перейти на finish; |
| если (существует еще один открытый файл, старший и млад-|
| ший номера которого совпадают с номерами закрываемого |
| устройства) |
| перейти на finish; /* не последнее закрытие */ |
| если (устройство символьного типа) |
| { |
| использовать старший номер в качестве указателя в |
| таблице ключей устройства посимвольного ввода-выво- |
| да; |
| вызвать процедуру закрытия, определяемую типом драй- |
| вера и передать ей в качестве параметра младший но- |
| мер устройства; |
| } |
| если (устройство блочного типа) |
| { |
| если (устройство монтировано) |
| перейти на finish; |
| переписать блоки устройства из буферного кеша на уст-|
| ройство; |
| использовать старший номер в качестве указателя в |
| таблице ключей устройства ввода-вывода блоками; |
| вызвать процедуру закрытия, определяемую типом драй- |
| вера и передать ей в качестве параметра младший но- |
| мер устройства; |
| сделать недействительными блоки устройства, оставшие-|
| ся в буферном кеше; |
| } |
| finish: |
| освободить индекс; |
| } |
+------------------------------------------------------------+
Рисунок 10.4. Алгоритм закрытия устройства
----------------------------------------
(**) На практике вывод на печать обычно управляется специальными процессами
буферизации, и права доступа устанавливаются таким образом, чтобы толь-
ко система буферизации могла обращаться к принтеру.
296
Процесс разрывает связь с открытым устройством, закрывая его. Однако,
ядро запускает определяемую типом устройства процедуру close только в пос-
леднем вызове функции close для этого устройства, и то только если не оста-
лось процессов, которым устройство необходимо открытым, поскольку процедура
закрытия устройства завершается разрывом аппаратного соединения; отсюда яс-
но, что ядру следует подождать, пока не останется ни одного процесса, обра-
щающегося к устройству. Поскольку ядро запускает процедуру открытия устройс-
тва при каждом вызове системной функции open, а процедуру закрытия только
один раз, драйверу устройства неведомо, сколько процессов используют устрой-
ство в данный момент. Драйверы могут легко выйти из строя, если при их напи-
сании не соблюдалась осторожность: когда при выполнении процедуры close они
приостанавливают свою работу и какой-нибудь процесс открывает устройство до
того, как завершится процедура закрытия, устройство может стать недоступным
для работы, если в результате комбинации вызовов open и close сложилась не-
распознаваемая ситуация.
Алгоритм закрытия устройства похож на алгоритм закрытия файла обычного
типа (Рисунок 10.4). Однако, до того, как ядро освобождает индекс, в нем вы-
полняются действия, специфичные для файлов устройств.
1. Просматривается таблица файлов для того, чтобы убедиться в том, что ни
одному из процессов не требуется, чтобы устройство было открыто. Чтобы
установить, что вызов функции close для устройства является последним,
недостаточно положиться на значение счетчика ссылок в таблице файлов,
поскольку несколько процессов могут обращаться к одному и тому же уст-
ройству, используя различные точки входа в таблице файлов. Так же недос-
таточно положиться на значение счетчика в таблице индексов, поскольку
одному и тому же устройству могут соответствовать несколько файлов уст-
ройства. Например, команда ls -l покажет, что одному и тому же устройст-
ву символьного типа ("c" в начале строки) соответствуют два файла уст-
ройства, старший и младший номера у которых (9 и 1) совпадают. Значение
счетчика связей для каждого файла, равное 1, говорит о том, что имеется
два индекса.
crw--w--w- 1 root vis 9, 1 Aug 6 1984 /dev/tty01
crw--w--w- 1 root unix 9, 1 May 3 15:02 /dev/tty01
Если процессы открывают оба файла независимо один от другого, они обра-