тятся к разным индексам одного и того же устройства.
2. Если устройство символьного типа, ядро запускает процедуру закрытия уст-
ройства и возвращает управление в режим задачи. Если устройство блочного
типа, ядро просматривает таблицу результатов монтирования и проверяет,
не располагается ли на устройстве смонтированная файловая система. Если
такая система есть, ядро не сможет запустить процедуру закрытия устройс-
тва, поскольку не был сделан последний вызов функции close для устройст-
ва. Даже если на устройстве нет смонтированной файловой системы, в бу-
ферном кеше еще могут находиться блоки с данными, оставшиеся от смонти-
рованной ранее файловой системы и не переписанные на устройство, пос-
кольку имели пометку "отложенная запись". Поэтому ядро просматривает бу-
ферный кеш в поисках таких блоков и переписывает их на устройство перед
запуском процедуры закрытия устройства. После закрытия устройства ядро
вновь просматривает буферный кеш и делает недействительными все буферы,
которые содержат блоки для только что закрытого устройства, в то же вре-
мя позволяя буферам с актуальной информацией остаться в кеше.
3. Ядро освобождает индекс файла устройства.
Короче говоря, процедура закрытия устройства разрывает связь с устройст-
вом и инициализирует заново информационные структуры драйвера и аппаратную
часть устройства с тем, чтобы ядро могло бы позднее открыть устройство

297

вновь.


    10.1.2.3 Read и Writе



Алгоритмы чтения и записи ядром на устройстве похожи на аналогичные ал-
горитмы для файлов обычного типа. Если процесс производит чтение или запись
на устройстве посимвольного ввода-вывода, ядро запускает процедуры read или
write, определяемые типом драйвера. Несмотря на часто встречающиеся ситуа-
ции, когда ядро осуществляет передачу данных непосредственно между адресным
пространством задачи и устройством, драйверы устройств могут буферизовать
информацию внутри себя. Например, терминальные драйверы для буферизации дан-
ных используют символьные списки (раздел 10.3.1). В таких случаях драйвер
устройства выделяет "буфер", копирует данные из пространства задачи при вы-
полнении процедуры write и выводит их из "буфера" на устройство. Процедура
записи, управляемая драйвером, регулирует объем выводимой информации (т.н.
управление потоком данных): если процессы генерируют информацию быстрее, чем
устройство выводит ее, процедура записи приостанавливает выполнение процес-
сов до тех пор, пока устройство не будет готово принять следующую порцию
данных. При чтении драйвер устройства помещает данные, полученные от устрой-
ства, в буфер и

Память

| |
+-----+
160110| CSR | +------+ +---------tty00
| RDB +---------+ dz00 +-+---------tty01
| TDB | +------+ | ...
+-----+ +---------tty07
160120| CSR | +------+ +---------tty08
160122| RDB +---------+ dz01 +-+---------tty09
160126| TDB | +------+ | ...
+-----+ +---------tty15
| |

Рисунок 10.5. Отображение в памяти ввода-вывода с использова-
нием контроллера VAX DZ11


копирует их из буфера в пользовательские адреса, указанные в вызове систем-
ной функции.
Конкретный метод взаимодействия драйвера с устройством определяется осо-
бенностями аппаратуры. Некоторые из машин обеспечивают отображение ввода-вы-
вода в памяти, подразумевающее, что конкретные адреса в адресном пространст-
ве ядра являются не номерами ячеек в физической памяти, а специальными ре-
гистрами, контролирующими соответствующие устройства. Записывая в указанные
регистры управляющие параметры в соответствии со спецификациями аппаратных
средств, драйвер осуществляет управление устройством. Например, контроллер
ввода-вывода для машины VAX-11 содержит специальные регистры для записи ин-
формации о состоянии устройства (регистры контроля и состояния) и для пере-
дачи данных (буферные регистры), которые формируются по специальным адресам
в физической памяти. В частности, терминальный контроллер VAX DZ11 управляет
8 асинхронными линиями терминальной связи (см. [Levy 80], где более подробно
объясняется архитектура машин VAX). Пусть регистр контроля и состояния (CSR)
для конкретного терминала DZ11 имеет адрес 160120, передающий буферный ре-
гистр (TDB) - адрес 120126, а принимающий буферный регистр (RDB) - адрес
160122 (Рисунок 10.5). Для того, чтобы передать символ на терминал
"/dev/tty09", драйвер терминала записывает единицу (1 = 9 по модулю 8) в

298

указанный двоичный разряд регистра контроля и состояния и затем записывает
символ в передающий буферный регистр. Запись в передающий буферный регистр
является передачей данных. Контроллер DZ11 выставляет бит "выполнено" в ре-
гистре контроля и состояния, когда готов принять следующую порцию данных.
Дополнительно драйвер может выставить бит "возможно прерывание передачи" в
регистре контроля и состояния, что заставляет контроллер DZ11 прерывать ра-
боту системы, когда он готов принять следующую порцию данных. Чтение данных
из DZ11 производится аналогично.
На других машинах имеется программируемый ввод-вывод, подразумевающий,
что в машине имеются инструкции по управлению устройствами. Драйверы управ-
ляют устройствами, выполняя соответствующие инструкции. Например, в машине
IBM 370 имеется инструкция "Start I/O" (Начать ввод-вывод), которая иниции-
рует операцию ввода-вывода, связанную с устройством. Способ связи драйвера с
периферийными устройствами незаметен для пользователя.
Поскольку интерфейс между драйверами устройств и соответствующими аппа-
ратными средствами является машинно-зависимым, на этом уровне не существует
стандартных интерфейсов. Как в случае вводавывода с отображением в памяти,
так и в случае программируемого ввода-вывода драйвер может посылать на уст-
ройство управляющие последовательности с целью установления режима прямого
доступа в память (ПДП) для устройства. Система позволяет осуществлять массо-
вую передачу данных между устройством и памятью в режиме ПДП параллельно с
работой центрального процессора, при этом устройство прерывает работу систе-
мы по завершении передачи данных. Драйвер организует управление виртуальной
памятью таким образом, чтобы ячейки памяти с их действительными номерами ис-
пользовались для ПДП.
Быстродействующие устройства могут иногда передавать данные непосредст-
венно в адресное пространство задачи, без вмешательства буфера ядра. В ре-
зультате повышается скорость передачи данных, поскольку при этом производит-
ся на одну операцию копирования меньше, и, кроме того, объем данных, переда-
ваемых за одну операцию, не ограничивается размером буферов ядра. Драйверы,
осуществляющие такую передачу данных без "обработки", обычно используют
блочный интерфейс для процедур посимвольного чтения и записи, если у них
имеется двойник блочного типа.


    10.1.2.4 Стратегический интерфейс



Ядро использует стратегический интерфейс для передачи данных между бу-
ферным кешем и устройством, хотя, как уже говорилось ранее, процедуры чтения
и записи для устройств посимвольного вводавывода иногда пользуются процеду-
рой strategy (их двойника блочного типа) для непосредственной передачи дан-
ных между устройством и адресным пространством задачи. Процедура strategy
может управлять очередностью выполнения заданий на ввод-вывод, связанный с
устройством, или выполнять более сложные действия по планированию выполнения
подобных заданий. Драйверы в состоянии привязывать передачу данных к одному
физическому адресу или ко многим. Ядро передает адрес заголовка буфера стра-
тегической процедуре драйвера; в заголовке содержится список адресов (стра-
ниц памяти) и размеры данных, передаваемых на или с устройства. Аналогичное
действие имеет место при работе механизма свопинга, описанного в главе 9.
При работе с буферным кешем ядро передает данные с одного адреса; во время
свопинга ядро передает данные, расположенные по нескольким адресам (страницы
памяти). Если данные копируются из или в адресное пространство задачи, драй-
вер должен блокировать процесс (или по крайней мере, соответствующие страни-
цы) в памяти до завершения передачи данных.
Например, после монтирования файловой системы ядро идентифицирует каждый
файл в файловой системе по номеру устройства и номеру индекса. В номере уст-
ройства закодированы его старший и младший номера. Когда ядро обращается к
блоку, который принадлежит файлу, оно копирует номер устройства и номер бло-
ка в заголовок буфера, как уже говорилось ранее в главе 3. Обращения к дис-

299

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


    10.1.2.5 Ioctl



Системная функция ioctl является обобщением специфичных для терминала
функций stty (задать установки терминала) и gtty (получить установки терми-
нала), имевшихся в ранних версиях системы UNIX. Она выступает в качестве об-
щей точки входа для всех связанных с типом устройства команд и позволяет
процессам задавать аппаратные параметры, ассоциированные с устройством, и
программные параметры, ассоциированные с драйвером. Специальные действия,
выполняемые функцией ioctl для разных устройств различны и определяются ти-
пом драйвера. Программы, использующие вызов ioctl, должны должны знать, с
файлом какого типа они работают, так как они являются аппаратно-зависимыми.
Исключение из общего правила сделано для системы, которая не видит различий
между файлами разных типов. Более подробно использование функции ioctl для
терминалов рассмотрено в разделе 10.3.3.
Синтаксис командной строки, содержащей вызов системной функции:

ioctl(fd,command,arg);

где fd - дескриптор файла, возвращаемый предварительно вызванной функцией
open, command - действие (команда), которое необходимо выполнить драйверу,
arg - параметр команды (может быть указателем на структуру). Команды специ-
фичны для различных драйверов; следовательно, каждый драйвер интерпретирует
команды в соответствии со своими внутренними спецификациями, от команды, в
свою очередь, зависит формат структуры данных, описываемой передаваемым па-
раметром. Драйверы могут считывать структуру данных arg из пространства за-
дачи в соответствии с предопределенным форматом или записывать установки ус-
тройства в пространство задачи по адресу указанной структуры. Например, на-
личие интерфейса, предоставляемого функцией ioctl, дает возможность пользо-
вателям устанавливать для терминала скорость передачи информации в бодах,
перематывать магнитную ленту, и, наконец, выполнять сетевые операции, зада-
вая номера виртуальных каналов и сетевые адреса.


    10.1.2.6 Другие функции, имеющие отношение к файловой системе



Такие функции работы с файловой системой, как stat и chmod, выполняются
одинаково, как для обычных файлов, так и для устройств; они манипулируют с
индексом, не обращаясь к драйверу. Даже системная функция lseek работает для
устройств. Например, если процесс подводит головку на лентопротяжном устрой-
стве к указанному адресу смещения в байтах с помощью функции lseek, ядро
корректирует смещение в таблице файлов но не выполняет никаких действий,
специфичных для данного типа драйвера. Когда позднее процесс выполняет чте-
ние (read) или запись (write), ядро пересылает адрес смещения из таблицы
файлов в адресное пространство задачи, подобно тому, как это имеет место при
работе с файлами обычного типа, и устройство физически перемещает головку к
соответствующему смещению, указанному в пространстве задачи. Этот случай ил-
люстрируется на примере в разделе 10.3.


300

Периферийные Соединительная Вектор
устройства панель прерывания
+------------------+
| - |
tty00 -------------+ | - |
tty01 .... | | - |
... | +--+ +------------------+
tty07 -------------+-+ |------------| ttyintr 0 |
tty08 -------------+ +--+ +------------------+
tty09 .... +-+ |------------| ttyintr 1 |
... +--------+ +--+ +------------------+
tty15 ----+ +--------+ |------------| consintr |
консоль ------+ +--+ +------------------+
принтер00 -------------+-+ |------------| printintr 0 |
.... | +--+ +------------------+
принтер03 -------------+ | | | - |
| | | - |
+--+ +------------------+

Рисунок 10.6. Прерывания от устройств



    10.1.3 Программы обработки прерываний



Как уже говорилось выше (раздел 6.4.1), возникновение прерывания побуж-
дает ядро запускать программу обработки прерываний, в основе алгоритма кото-
рой лежит соотношение между устройством, вызвавшим прерывание, и смещением в
таблице векторов прерываний. Ядро запускает программу обработки прерываний
для данного типа устройства, передавая ей номер устройства или другие пара-
метры для того, чтобы идентифицировать единицу устройства, вызвавшую преры-
вание. Например, в таблице векторов прерываний на Рисунке 10.6 показаны две
точки входа для обработки прерываний от терминалов ("ttyintr"), каждая из
которых используется для обработки прерываний, поступивших от 8 терминалов.
Если устройство tty09 прервало работу системы, система вызывает программу
обработки прерывания, ассоциированную с местом аппаратного подключения уст-
ройства. Поскольку с одной записью в таблице векторов прерываний может быть
связано множество физических устройств, драйвер должен уметь распознавать
устройство, вызвавшее прерывание. На рисунке записи в таблице векторов пре-
рываний, соответствующие прерываниям от терминалов, имеют метки 0 и 1, чтобы
система различала их между собой при вызове программы обработки прерываний,
используя к примеру этот номер в качестве передаваемого программе параметра.
Программа обработки прерываний использует этот номер и другую информацию,
переданную механизмом прерывания, для того, чтобы удостовериться, что именно
устройство tty09, а не tty12, прервало работу системы. Этот пример в упро-
щенном виде показывает то, что имеет место в реальных системах, где на самом
деле существует несколько уровней контроллеров и соответствующих программ
обработки прерываний, но он иллюстрирует общие принципы.
Если подвести итог, можно сказать, что номер устройства, используемый
программой обработки прерываний, идентифицирует единицу аппаратуры, а млад-
ший номер в файле устройства идентифицирует устройство для ядра. Драйвер ус-
тройства устанавливает соответствие между младшим номером устройства и номе-
ром единицы аппаратуры.


    10.2 ДИСКОВЫЕ ДРАЙВЕРЫ



Так сложилось исторически, что дисковые устройства в системах UNIX раз-
бивались на разделы, содержащие различные файловые системы, что означало

301

"деление [дискового] пакета на несколько управляемых по-своему частей" (см.
[System V 84b]). Например, если на диске располагаются четыре файловые сис-
темы, администратор может оставить одну из них несмонтированной, одну смон-
тировать только для чтения, а две других только для записи. Несмотря на то,
что все файловые системы сосуществуют на одном физическом устройстве, поль-
зователи не могут ни обращаться к файлам немонтированной файловой системы,
используя методы доступа, описанные в главах 4 и 5, ни записывать файлы в
файловые системы, смонтированные только для чтения. Более того, так как каж-
дый раздел (и, следовательно, файловая система) занимает на диске смежные
дорожки и цилиндры, скопировать всю файловую систему легче, чем в том слу-
чае, если бы раздел занимал участки, разбросанные по всему дисковому тому.
Дисковый драйвер транслирует адрес файловой системы, состоящий из логи-
ческого номера устройства и номера блока, в точный номер дискового сектора.
Драйвер получает адрес одним из следующих путей: либо стратегическая проце-
дура использует буфер из буферного пула, заголовок которого содержит номера
устройства и блока, либо процедуры чтения и записи передают логический
(младший) номер устройства в качестве параметра; они преобразуют адрес сме-
щения в байтах, хранящийся в пространстве задачи, в адрес соответствующего
блока. Дисковый драйвер использует номер устройства для идентификации физи-
ческого устройства и указания используемого раздела, обращаясь при этом к
внутренним таблицам для поиска сектора, отмечающего начало раздела на диске.
Наконец, он добавляет номер блока в файловой системе к номеру блока, с кото-
рого начинается каждый сектор, чтобы идентифицировать сектор, используемый
для ввода-вывода.

+---------------------------------------------+
| Раздел Начальный блок Длина в блоках |
| |
| Размер блока = 512 байт |
| |
| 0 0 64000 |
| 1 64000 944000 |
| 2 168000 840000 |
| 3 336000 672000 |
| 4 504000 504000 |
| 5 672000 336000 |
| 6 840000 168000 |
| 7 0 1008000 |
+---------------------------------------------+

Рисунок 10.7. Разделы на диске RP07


Исторически сложилось так, что размеры дисковых разделов устанавливаются
в зависимости от типа диска. Например, диск DEC RP07 разбит на разделы, ха-
рактеристика которых приведена на Рисунке 10.7. Предположим, что файлы
"/dev/dsk0", "/dev/dsk1", "/dev/dsk2" и "/dev/dsk3" соответствуют разделам
диска RP07, имеющим номера от 0 до 3, и имеют аналогичные младшие номера.
Пусть размер логического блока в файловой системе совпадает с размером дис-
кового блока. Если ядро пытается обратиться к блоку с номером 940 в файловой
системе, хранящейся в "/dev/dsk3", дисковый драйвер переадресует запрос к
блоку с номером 336940 (раздел 3 начинается с блока, имеющего номер 336000;
336000 + 940 = 336940) на диске.
Размеры разделов на диске варьируются и администраторы располагают фай-
ловые системы в разделах соответствующего размера: большие файловые системы
попадают в разделы большего размера и т. д. Разделы на диске могут перекры-
ваться. Например, разделы 0 и 1 на диске RP07 не пересекаются, но вместе они
занимают блоки с номерами от 0 до 1008000, то есть весь диск. Раздел 7 так
же занимает весь диск. Перекрытие разделов не имеет значения, поскольку фай-

302

ловые системы, хранящиеся в разделах, размещаются таким образом, что между
ними нет пересечений. Иметь один раздел, включающий в себя все дисковое
пространство, выгодно, поскольку весь том можно быстро скопировать.
Использование разделов фиксированного состава и размера ограничивает
гибкость дисковой конфигурации. Информацию о разделах в закодированном виде
не следует включать в дисковый драйвер, но нужно поместить в таблицу содер-
жимого дискового тома. Однако, найти общее место на всех дисках для размеще-
ния таблицы содержимого дискового тома и сохранить тем самым совместимость с
предыдущими версиями системы довольно трудно. В существующих реализациях
версии V предполагается, что блок начальной загрузки первой из файловых сис-
тем на диске занимает первый сектор тома, хотя по логике это, казалось бы,
самое подходящее место для таблицы содержимого тома. И все же дисковый драй-
вер должен иметь закодированную информацию о месте расположения таблицы со-
держимого тома для каждого диска, не препятствуя существованию дисковых раз-
делов переменного размера.
В связи с тем, что для системы UNIX является типичным высокий уровень
дискового трафика, драйвер диска должен максимизировать передачу данных с
тем, чтобы обеспечить наилучшую производительность всей системы. Новейшие
дисковые контроллеры осуществляют планирование выполнения заданий, требующих
обращения к диску, позиционируют головку диска и обеспечивают передачу дан-
ных между диском и центральным процессором; иначе это приходится делать дис-
ковому драйверу.
Сервисные программы могут непосредственно обращаться к диску в обход
стандартного метода доступа к файловой системе, рассмотренного в главах 4 и
5, как пользуясь блочным интерфейсом, так и не прибегая к структурированию
данных. Непосредственно работают с диском две важные программы - mkfs и
fsck. Программа mkfs форматирует раздел диска для файловой системы UNIX,
создавая при этом суперблок, список индексов, список свободных дисковых бло-
ков с указателями и корневой каталог новой файловой системы. Программа fsck
проверяет целостность существующей файловой системы и исправляет ошибки, как
показано в главе 5.
Рассмотрим программу, приведенную на Рисунке 10.8, в применении к файлам
"/dev/dsk15" и "/dev/rdsk15", и предположим, что команда ls выдала следующую
информацию:
ls -1 /dev/dsk15 /dev/rdsk15

br-------- 2 root root 0,21 Feb 12 15:40 /dev/dsk15
crw-rw---- 2 root root 7,21 Mar 7 09:29 /dev/rdsk15

Отсюда видно, что файл "/dev/dsk15" соответствует устройству блочного
типа, владельцем которого является пользователь под именем "root", и только
пользователь "root" может читать с него непосредственно. Его старший номер -
0, младший - 21. Файл "/dev/rdsk15" соответствует устройству посимвольного
ввода-вывода, владельцем которого является пользователь "root", однако права
доступа к которому на запись и чтение есть как у владельца, так и у группы.
Его старший номер - 7, младший - 21. Процесс, открывающий файлы, получает
доступ к устройству через таблицу клю-
чей устройств ввода-вывода блоками и таблицу ключей устройств посимвольного
ввода-вывода, соответственно, а младший номер устройства 21 информирует
драйвер о том, к какому разделу диска производится обращение, например, дис-
ковод 2, раздел 1. Поскольку младшие номера у файлов совпадают, они ссылают-
ся на один и тот же раздел диска, если предположить, что это одно устройство
(***). Таким образом, процесс, выполняющий программу, открывает один и тот

---------------------------------------
(***) Не существует иного способа установить, что символьный и блочный драй-
веры ссылаются на одно и то же устройство, кроме просмотра таблиц сис-
темной конфигурации и текста программ драйвера.


303

же драйвер дважды (используя различные интерфейсы), позиционирует головку к
смещению с адресом 8192 и считывает данные с этого места. Результаты выпол-
нения операций чтения должны быть идентичными при условии, что работает
только одна файловая система.

+------------------------------------------------------------+
| #include "fcntl.h" |
| main() |
| { |
| char buf1[4096], buf2[4096] |
| int fd1, fd2, i; |
| |
| if (((fd1 = open("/dev/dsk5/", O_RDONLY)) == -1) || |
| ((fd2 = open("/dev/rdsk5", O_RDONLY)) == -1))|
| { |
| printf("ошибка при открытии\n"); |
| exit(); |
| } |
| |
| lseek(fd1, 8192L, 0); |
| lseek(fd2, 8192L, 0); |
| |
| if ((read(fd1, buf1, sizeof(buf1)) == -1) || |
| (read(fd2, buf2, sizeof(buf2)) == -1)) |
| { |
| printf("ошибка при чтении\n"); |
| exit(); |
| } |
| |
| for (i = 0; i < sizeof(buf1); i++) |
| if (buf1[i] != buf2[i]) |
| { |
| printf("различие в смещении %d\n", i); |
| exit(); |
| } |
| printf("данные совпадают\n"); |
| } |
+------------------------------------------------------------+

Рисунок 10.8. Чтение данных с диска с использованием блочного
интерфейса и без структурирования данных


Программы, осуществляющие чтение и запись на диск непосредственно, пред-
ставляют опасность, поскольку манипулируют с чувствительной информацией,
рискуя нарушить системную защиту. Администраторам следует защищать интерфей-
сы ввода-вывода путем установки прав доступа к файлам дисковых устройств.
Например, дисковые файлы "/dev/dsk15" и "/dev/rdsk15" должны принадлежать
пользователю с именем "root", и права доступа к ним должны быть определены
таким образом, чтобы пользователю "root" было разрешено чтение, а всем ос-
тальным пользователям и чтение, и запись должны быть запрещены.
Программы, осуществляющие чтение и запись на диск непосредственно, могут
также нарушить целостность данных в файловой системе. Алгоритмы файловой
системы, рассмотренные в главах 3, 4 и 5, координируют выполнение операций
ввода-вывода, связанных с диском, тем самым поддерживая целостность информа-
ционных структур на диске, в том числе списка свободных дисковых блоков и
указателей из индексов на информационные блоки прямой и косвенной адресации.
Процессы, обращающиеся к диску непосредственно, обходят эти алгоритмы. Пусть
даже их программы написаны с большой осторожностью, проблема целостности все

304

равно не исчезнет, если они выполняются параллельно с работой другой файло-
вой системы. По этой причине программа fsck не должна выполняться при нали-