The design of the unix operating system by Maurice J

Вид материалаРеферат
10.3 Терминальные драйверы
Подобный материал:
1   ...   37   38   39   40   41   42   43   44   ...   55
раздел занимал участки, разбросанные по всему дисковому тому.

Дисковый драйвер транслирует адрес файловой системы, состоя-

щий из логического номера устройства и номера блока, в точный но-

мер дискового сектора. Драйвер получает адрес одним из следующих

путей: либо стратегическая процедура использует буфер из буферно-

го пула, заголовок которого содержит номера устройства и блока,

либо процедуры чтения и записи передают логический (младший) но-

мер устройства в качестве параметра; они преобразуют адрес смеще-

ния в байтах, хранящийся в пространстве задачи, в адрес соответс-

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

идентификации физического устройства и указания используемого

раздела, обращаясь при этом к внутренним таблицам для поиска сек-

тора, отмечающего начало раздела на диске. Наконец, он добавляет

номер блока в файловой системе к номеру блока, с которого начина-

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

для ввода-вывода.


----------------------------------------------┐

│ Раздел Начальный блок Длина в блоках │

│ │

│ Размер блока = 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 │

L----------------------------------------------


Рисунок 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 так же зани-

мает весь диск. Перекрытие разделов не имеет значения, поскольку

файловые системы, хранящиеся в разделах, размещаются таким обра-

зом, что между ними нет пересечений. Иметь один раздел, включаю-

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

можно быстро скопировать.

Использование разделов фиксированного состава и размера огра-

ничивает гибкость дисковой конфигурации. Информацию о разделах в

закодированном виде не следует включать в дисковый драйвер, но

нужно поместить в таблицу содержимого дискового тома. Однако,

найти общее место на всех дисках для размещения таблицы содержи-

мого дискового тома и сохранить тем самым совместимость с преды-

дущими версиями системы довольно трудно. В существующих реализа-

циях версии 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. Процесс, от-

крывающий файлы, получает доступ к устройству через таблицу клю-


-------------------------------------------------------------┐

│ #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"); │

│ } │

L-------------------------------------------------------------


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

интерфейса и без структурирования данных


чей устройств ввода-вывода блоками и таблицу ключей устройств по-

символьного ввода-вывода, соответственно, а младший номер

устройства 21 информирует драйвер о том, к какому разделу диска

производится обращение, например, дисковод 2, раздел 1. Поскольку

младшие номера у файлов совпадают, они ссылаются на один и тот же

раздел диска, если предположить, что это одно устройство (***).

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

тот же драйвер дважды (используя различные интерфейсы), позицио-

нирует головку к смещению с адресом 8192 и считывает данные с


---------------------------------------

(***) Не существует иного способа установить, что символьный и

блочный драйверы ссылаются на одно и то же устройство, кро-

ме просмотра таблиц системной конфигурации и текста про-

грамм драйвера.


этого места. Результаты выполнения операций чтения должны быть

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

ма.

Программы, осуществляющие чтение и запись на диск непосредс-

твенно, представляют опасность, поскольку манипулируют с чувстви-

тельной информацией, рискуя нарушить системную защиту. Админист-

раторам следует защищать интерфейсы ввода-вывода путем установки

прав доступа к файлам дисковых устройств. Например, дисковые фай-

лы "/dev/dsk15" и "/dev/rdsk15" должны принадлежать пользователю

с именем "root", и права доступа к ним должны быть определены та-

ким образом, чтобы пользователю "root" было разрешено чтение, а

всем остальным пользователям и чтение, и запись должны быть зап-

рещены.


Программы, осуществляющие чтение и запись на диск непосредс-

твенно, могут также нарушить целостность данных в файловой систе-

ме. Алгоритмы файловой системы, рассмотренные в главах 3, 4 и 5,

координируют выполнение операций ввода-вывода, связанных с дис-

ком, тем самым поддерживая целостность информационных структур на

диске, в том числе списка свободных дисковых блоков и указателей

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

Процессы, обращающиеся к диску непосредственно, обходят эти алго-

ритмы. Пусть даже их программы написаны с большой осторожностью,

проблема целостности все равно не исчезнет, если они выполняются

параллельно с работой другой файловой системы. По этой причине

программа fsck не должна выполняться при наличии активной файло-

вой системы.

Два типа дискового интерфейса различаются между собой по

использованию буферного кеша. При работе с блочным интерфейсом

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

па, исключение составляет тот момент, когда после преобразования

адреса смещения логического байта в адрес смещения логического

блока (см. алгоритм bmap в главе 4) оно трактует адрес смещения

логического блока как физический номер блока в файловой системе.

Затем, используя буферный кеш, ядро обращается к данным, и, в ко-

нечном итоге, к стратегическому интерфейсу драйвера. Однако, при

обращении к диску через символьный интерфейс (без структурирова-

ния данных), ядро не превращает адрес смещения в адрес файла, а

передает его немедленно драйверу, используя для передачи рабочее

пространство задачи. Процедуры чтения и записи, входящие в состав

драйвера, преобразуют смещение в байтах в смещение в блоках и ко-

пируют данные непосредственно в адресное пространство задачи, ми-

нуя буферы ядра.

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

блочного типа, а второй процесс затем считывает с устройства сим-

вольного типа по тому же адресу, второй процесс может не считать

информацию, записанную первым процессом, так как информация может

еще находиться в буферном кеше, а не на диске. Тем не менее, если

второй процесс обратится к устройству блочного типа, он автомати-

чески попадет на новые данные, находящиеся в буферном кеше.


При использовании символьного интерфейса можно столкнуться со

странной ситуацией. Если процесс читает или пишет на устройство

посимвольного ввода-вывода порциями меньшего размера, чем, к при-

меру, блок, результаты будут зависеть от драйвера. Например, если

производить запись на ленту по 1 байту, каждый байт может попасть

в любой из ленточных блоков.

Преимущество использования символьного интерфейса состоит в

скорости, если не возникает необходимость в кешировании данных

для дальнейшей работы. Процессы, обращающиеся к устройствам ввода

-вывода блоками, передают информацию блоками, размер каждого из

которых ограничивается размером логического блока в данной файло-

вой системе. Например, если размер логического блока в файловой

системе 1 Кбайт, за одну операцию ввода-вывода может быть переда-

но не больше 1 Кбайта информации. При этом процессы, обращающиеся

к диску с помощью символьного интерфейса, могут передавать за од-

ну дисковую операцию множество дисковых блоков, в зависимости от

возможностей дискового контроллера. С функциональной точки зре-

ния, процесс получает тот же самый результат, но символьный ин-

терфейс может работать гораздо быстрее. Если воспользоваться при-

мером, приведенным на Рисунке 10.8, можно увидеть, что когда

процесс считывает 4096 байт, используя блочный интерфейс для фай-

ловой системы с размером блока 1 Кбайт, ядро производит четыре

внутренние итерации, на каждом шаге обращаясь к диску, прежде чем

вызванная системная функция возвращает управление, но когда про-

цесс использует символьный интерфейс, драйвер может закончить

чтение за одну дисковую операцию. Более того, использование блоч-

ного интерфейса вызывает дополнительное копирование данных между

адресным пространством задачи и буферами ядра, что отсутствует в

символьном интерфейсе.


10.3 ТЕРМИНАЛЬНЫЕ ДРАЙВЕРЫ


Терминальные драйверы выполняют ту же функцию, что и осталь-

ные драйверы: управление передачей данных от и на терминалы. Од-

нако, терминалы имеют одну особенность, связанную с тем, что они

обеспечивают интерфейс пользователя с системой. Обеспечивая инте-

рактивное использование системы UNIX, терминальные драйверы имеют

свой внутренний интерфейс с модулями, интерпретирующими ввод и

вывод строк. В каноническом режиме интерпретаторы строк преобра-

зуют неструктурированные последовательности данных, введенные с

клавиатуры, в каноническую форму (то есть в форму, соответствую-

щую тому, что пользователь имел в виду на самом деле) прежде, чем

послать эти данные принимающему процессу; строковый интерфейс

также преобразует неструктурированные последовательности выходных

данных, созданных процессом, в формат, необходимый пользователю.

В режиме без обработки строковый интерфейс передает данные между

процессами и терминалом без каких-либо преобразований.

Программисты, например, работают на клавиатуре терминала до-

вольно быстро, но с ошибками. На этот случай терминалы имеют кла-

вишу стирания ("erase"; клавиша может быть обозначена таким обра-

зом), чтобы пользователь имел возможность стирать часть введенной

строки и вводить коррективы. Терминалы пересылают машине всю вве-

денную последовательность, включая и символы стирания (*** *). В

каноническом режиме строковый интерфейс буферизует информацию в

строки (набор символов, заканчивающийся символом возврата каретки

(*****)) и процессы стирают символы у себя, прежде чем переслать

исправленную последовательность считывающему процессу.

В функции строкового интерфейса входят:

* построчный разбор введенных последовательностей;

* обработка символов стирания;

* обработка символов "удаления", отменяющих все остальные сим-

волы, введенные до того в текущей строке;

* отображение символов, полученных терминалом;

* расширение выходных данных, например, преобразование символов

табуляции в последовательности пробелов;

* сигнализирование процессам о зависании терминалов и прерыва-

нии строк или в ответ на нажатие пользователем клавиши удале-

ния;

* предоставление возможности не обрабатывать специальные

символы, такие как символы стирания, удаления и возврата ка-

ретки.


---------------------------------------

(****) В этом разделе рассматривается использование терминалов

ввода-вывода, которые передают все символы, введенные

пользователем, без обработки.

(*****) В данной главе используется общий термин "возврат карет-

ки" для обозначения символов возврата каретки и перевода

строки.


Функционирование без обработки подразумевает использование

асинхронного терминала, поскольку процессы могут считывать симво-

лы в том виде, в каком они были введены, вместо того, чтобы

ждать, когда пользователь нажмет клавишу ввода или возврата ка-

ретки.

Ричи отметил, что первые строковые интерфейсы, используемые

еще при разработке системы в начале 70-х годов, работали в соста-

ве программ командного процессора и редактора, но не в ядре (см.

[Ritchie 84], стр.1580). Однако, поскольку в их функциях нуждает-

ся множество программ, их место в составе ядра. Несмотря на то,

что строковый интерфейс выполняет такие функции, из которых логи-

чески вытекает его место между терминальным драйвером и остальной

частью ядра, ядро не запускает строковый интерфейс иначе, чем че-

рез терминальный драйвер. На Рисунке 10.9 показаны поток данных,

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

соответствующие ему управляющие воздействия, проходящие через

терминальный драйвер. Пользователи могут указать, какой строковый

интерфейс используется посредством вызова системной функции

ioctl, но реализовать схему, по которой одно устройство использо-

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

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

следующий модуль для обработки данных, довольно трудно.


Поток данных Поток управляющих

воздействий

------------------------┐ ------------------------┐

│ Процесс чтения/записи │ │ Процесс чтения/записи │

L------------------------ L------------------------

│ │

v │ v │

----------------------┐ ------------------------┐

вывод │ Строковый интерфейс│ ввод │ Терминальный драйвер │

L---------------------- L------------------------

│ │

v │ v │

------------------------┐ ----------------------┐

│ Терминальный драйвер │ │ Строковый интерфейс │

L------------------------ L----------------------



v │

------------------------┐

│ Драйвер ввода-вывода │

L------------------------



v │

--------------------------┐

│ Устройство ввода-вывода │

L--------------------------


Рисунок 10.9. Последовательность обращений и поток данных че-

рез строковый интерфейс


Указатель Смещение Смещение

на до до

следующий начала конца Массив символов

блок 0 1 2 3 4 5 6 7 8 9 14

----------T---------T---------TT-T-T-T-T-T-T-T-T-T-T-T-T-T-T-T---

│ 7 │ 14 ││g│a│r│b│a│g│e│││ │e│q│n│ │││ │...

----+-----+---------+---------++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---



v


Рисунок 10.10. Символьный блок


10.3.1 Символьные списки


Строковый интерфейс обрабатывает данные в символьных списках.

Символьный список (clist) представляет собой переменной длины

список символьных блоков с использованием указателей и с подсче-

том количества символов в списке. Символьный блок (cblock) содер-

жит указатель на следующий блок в списке, небольшой массив храни-

мой в символьном виде информации и адреса смещений, показывающие

место расположения внутри блока корректной информации (Рисунок

10.10). Смещение до начала показывает первую позицию расположения

корректной информации в массиве, смещение до конца показывает

первую позицию расположения некорректной информации.

Ядро обеспечивает ведение списка свободных символьных блоков

и выполняет над символьными списками и символьными блоками шесть

операций.

1. Ядро назначает драйверу символьный блок из списка свободных

символьных блоков.

2. Оно также возвращает символьный блок в список свободных сим-

вольных блоков.

3. Ядро может выбирать первый символ из символьного списка: оно

удаляет первый символ из первого символьного блока в списке и