Страница:
чии активной файловой системы.
Два типа дискового интерфейса различаются между собой по использованию
буферного кеша. При работе с блочным интерфейсом ядро пользуется тем же ал-
горитмом, что и для файлов обычного типа, исключение составляет тот момент,
когда после преобразования адреса смещения логического байта в адрес смеще-
ния логического блока (см. алгоритм bmap в главе 4) оно трактует адрес сме-
щения логического блока как физический номер блока в файловой системе. За-
тем, используя буферный кеш, ядро обращается к данным, и, в конечном итоге,
к стратегическому интерфейсу драйвера. Однако, при обращении к диску через
символьный интерфейс (без структурирования данных), ядро не превращает адрес
смещения в адрес файла, а передает его немедленно драйверу, используя для
передачи рабочее пространство задачи. Процедуры чтения и записи, входящие в
состав драйвера, преобразуют смещение в байтах в смещение в блоках и копиру-
ют данные непосредственно в адресное пространство задачи, минуя буферы ядра.
Таким образом, если один процесс записывает на устройство блочного типа,
а второй процесс затем считывает с устройства символьного типа по тому же
адресу, второй процесс может не считать информацию, записанную первым про-
цессом, так как информация может еще находиться в буферном кеше, а не на
диске. Тем не менее, если второй процесс обратится к устройству блочного ти-
па, он автоматически попадет на новые данные, находящиеся в буферном кеше.
При использовании символьного интерфейса можно столкнуться со странной
ситуацией. Если процесс читает или пишет на устройство посимвольного вво-
да-вывода порциями меньшего размера, чем, к примеру, блок, результаты будут
зависеть от драйвера. Например, если производить запись на ленту по 1 байту,
каждый байт может попасть в любой из ленточных блоков.
Преимущество использования символьного интерфейса состоит в скорости,
если не возникает необходимость в кешировании данных для дальнейшей работы.
Процессы, обращающиеся к устройствам ввода -вывода блоками, передают инфор-
мацию блоками, размер каждого из которых ограничивается размером логического
блока в данной файловой системе. Например, если размер логического блока в
файловой системе 1 Кбайт, за одну операцию ввода-вывода может быть передано
не больше 1 Кбайта информации. При этом процессы, обращающиеся к диску с по-
мощью символьного интерфейса, могут передавать за одну дисковую операцию
множество дисковых блоков, в зависимости от возможностей дискового контрол-
лера. С функциональной точки зрения, процесс получает тот же самый резуль-
тат, но символьный интерфейс может работать гораздо быстрее. Если воспользо-
ваться примером, приведенным на Рисунке 10.8, можно увидеть, что когда про-
цесс считывает 4096 байт, используя блочный интерфейс для файловой системы с
размером блока 1 Кбайт, ядро производит четыре внутренние итерации, на каж-
дом шаге обращаясь к диску, прежде чем вызванная системная функция возвраща-
ет управление, но когда процесс использует символьный интерфейс, драйвер мо-
жет закончить чтение за одну дисковую операцию. Более того, использование
блочного интерфейса вызывает дополнительное копирование данных между адрес-
ным пространством задачи и буферами ядра, что отсутствует в символьном ин-
терфейсе.
Терминальные драйверы выполняют ту же функцию, что и остальные драйверы:
управление передачей данных от и на терминалы. Однако, терминалы имеют одну
особенность, связанную с тем, что они обеспечивают интерфейс пользователя с
системой. Обеспечивая интерактивное использование системы UNIX, терминальные
драйверы имеют свой внутренний интерфейс с модулями, интерпретирующими ввод
и вывод строк. В каноническом режиме интерпретаторы строк преобразуют нест-
руктурированные последовательности данных, введенные с клавиатуры, в канони-
ческую форму (то есть в форму, соответствующую тому, что пользователь имел в
305
виду на самом деле) прежде, чем послать эти данные принимающему процессу;
строковый интерфейс также преобразует неструктурированные последовательности
выходных данных, созданных процессом, в формат, необходимый пользователю. В
режиме без обработки строковый интерфейс передает данные между процессами и
терминалом без каких-либо преобразований.
Программисты, например, работают на клавиатуре терминала довольно быст-
ро, но с ошибками. На этот случай терминалы имеют клавишу стирания ("erase";
клавиша может быть обозначена таким образом), чтобы пользователь имел воз-
можность стирать часть введенной строки и вводить коррективы. Терминалы пе-
ресылают машине всю введенную последовательность, включая и символы стирания
(*** *). В каноническом режиме строковый интерфейс буферизует информацию в
строки (набор символов, заканчивающийся символом возврата каретки (*****)) и
процессы стирают символы у себя, прежде чем переслать исправленную последо-
вательность считывающему процессу.
В функции строкового интерфейса входят:
* построчный разбор введенных последовательностей;
* обработка символов стирания;
* обработка символов "удаления", отменяющих все остальные символы, введен-
ные до того в текущей строке;
* отображение символов, полученных терминалом;
* расширение выходных данных, например, преобразование символов табуляции
в последовательности пробелов;
* сигнализирование процессам о зависании терминалов и прерывании строк или
в ответ на нажатие пользователем клавиши удаления;
* предоставление возможности не обрабатывать специальные символы, такие
как символы стирания, удаления и возврата каретки.
Функционирование без обработки подразумевает использование асинхронного
терминала, поскольку процессы могут считывать символы в том виде, в каком
они были введены, вместо того, чтобы ждать, когда пользователь нажмет клави-
шу ввода или возврата каретки.
Ричи отметил, что первые строковые интерфейсы, используемые еще при раз-
работке системы в начале 70-х годов, работали в составе программ командного
процессора и редактора, но не в ядре (см. [Ritchie 84], стр.1580). Однако,
поскольку в их функциях нуждается множество программ, их место в составе яд-
ра. Несмотря на то, что строковый интерфейс выполняет такие функции, из ко-
торых логически вытекает его место между терминальным драйвером и остальной
частью ядра, ядро не запускает строковый интерфейс иначе, чем через терми-
нальный драйвер. На Рисунке 10.9 показаны поток данных, проходящий через
терминальный драйвер и строковый интерфейс, и соответствующие ему управляю-
щие воздействия, проходящие через терминальный драйвер. Пользователи могут
указать, какой строковый интерфейс используется посредством вызова системной
функции ioctl, но реализовать схему, по которой одно устройство использовало
бы несколько строковых интерфейсов одновременно, при чем каждый интерфейсный
модуль, в свою очередь, успешно вызывал бы следующий модуль для обработки
данных, довольно трудно.
Строковый интерфейс обрабатывает данные в символьных списках. Символьный
список (clist) представляет собой переменной длины список символьных блоков
с использованием указателей и с подсчетом количества символов в списке. Сим-
---------------------------------------
(****) В этом разделе рассматривается использование терминалов ввода-вывода,
которые передают все символы, введенные пользователем, без обработки.
(*****) В данной главе используется общий термин "возврат каретки" для обоз-
начения символов возврата каретки и перевода строки.
306
Поток данных Поток управляющих
воздействий
+-----------------------+ +-----------------------+
| Процесс чтения/записи | | Процесс чтения/записи |
+-----------------------+ +-----------------------+
- | ^ - | ^
- v | - v |
- +---------------------+ - +-----------------------+
вывод | Строковый интерфейс| ввод | Терминальный драйвер |
- +---------------------+ - +-----------------------+
- | ^ - | ^
- v | - v |
+-----------------------+ +---------------------+
| Терминальный драйвер | | Строковый интерфейс |
+-----------------------+ +---------------------+
| ^
v |
+-----------------------+
| Драйвер ввода-вывода |
+-----------------------+
| ^
v |
+-------------------------+
| Устройство ввода-вывода |
+-------------------------+
Рисунок 10.9. Последовательность обращений и поток данных че-
рез строковый интерфейс
Указатель Смещение Смещение
на до до
следующий начала конца Массив символов
блок 0 1 2 3 4 5 6 7 8 9 14
----------+---------+---------++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---
| 7 | 14 ||g|a|r|b|a|g|e||| |e|q|n| ||| |...
----+-----+---------+---------++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---
|
v
Рисунок 10.10. Символьный блок
вольный блок (cblock) содержит указатель на следующий блок в списке, неболь-
шой массив хранимой в символьном виде информации и адреса смещений, показы-
вающие место расположения внутри блока корректной информации (Рисунок
10.10). Смещение до начала показывает первую позицию расположения корректной
информации в массиве, смещение до конца показывает первую позицию расположе-
ния некорректной информации.
Ядро обеспечивает ведение списка свободных символьных блоков и выполняет
над символьными списками и символьными блоками шесть операций.
1. Ядро назначает драйверу символьный блок из списка свободных символьных
блоков.
2. Оно также возвращает символьный блок в список свободных символьных бло-
ков.
3. Ядро может выбирать первый символ из символьного списка: оно удаляет
первый символ из первого символьного блока в списке и устанавливает зна-
чения счетчика символов в списке и указателей в блоке таким образом,
чтобы последующие операции не выбирали один и тот же символ. Если в ре-
307
зультате операции выбран последний символ блока, ядро помещает в список
свободных символьных блоков пустой блок и переустанавливает указатели в
символьном списке. Если в символьном списке отсутствуют символы, ядро
возвращает пустой символ.
4. Ядро может поместить символ в конец символьного списка путем поиска пос-
леднего символьного блока в списке, включения символа в него и переуста-
новки адресов смещений. Если символьный блок заполнен, ядро выделяет но-
вый символьный блок, включает его в конец символьного списка и помещает
символ в новый блок.
5. Ядро может удалять от начала списка группу символов по одному блоку за
одну операцию, что эквивалентно удалению всех символов в блоке за один
раз.
6. Ядро может поместить блок с символами в конец символьного списка.
Символьные списки позволяют создать несложный механизм буферизации, по-
лезный при небольшом объеме передаваемых данных, типичном для медленных уст-
ройств, таких как терминалы. Они дают возможность манипулировать с данными с
каждым символом в отдельности и с группой символьных блоков. Например, Рису-
нок 10.11 иллюстрирует удаление символов из символьного списка; ядро удаляет
по одному символу из первого блока в списке (Рисунок 10.11а-в) до тех пор,
пока в блоке не останется ни одного символа (Рисунок
символьный символьные
список блоки
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 27 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(а)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 1 8 | | | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 26 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(б)
308
+-------+ +---+---+---+---+---+---+---+---+
+----->| 2 8 | | | | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 25 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(в)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | * | | | | | t | b | l | |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 19 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символов | +--+ | 0 8 | | | | | t | r | o | f | f | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(г)
Рисунок 10.11. Удаление символов из символьного списка
символьный символьные
список блоки
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 22 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символа | +--+ | 0 8 | | * | | | | | t | b | l | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 6 | | | | | t | r | o | f | | |
+-------+ +---+---+---+---+---+---+---+---+
(а)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 23 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символа | +--+ | 0 8 | | * | | | | | t | b | l | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 7 | | | | | t | r | o | f | f | |
+-------+ +---+---+---+---+---+---+---+---+
309
(б)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 24 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символа | +--+ | 0 8 | | * | | | | | t | b | l | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | | | | t | r | o | f | f | |
+-------+ +---+---+---+---+---+---+---+---+
(в)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 25 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 1 | | - | | | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(г)
Рисунок 10.12. Включение символов в символьный список
10.11г); затем оно устанавливает указатель списка на следующий блок, который
становится первым блоком в списке. Подобно этому на Рисунке 10.12 показано,
как ядро включает символы в символьный список; при этом предполагается, что
в одном блоке помещается до 8 символов и что ядро размещает новый блок в
конце списка (Рисунок 10.12г).
Структуры данных, с которыми работают терминальные драйверы, связаны с
тремя символьными списками: списком для хранения данных, выводимых на терми-
нал, списком для хранения неструктурированных вводных данных, поступивших в
результате выполнения программы обработки прерывания от терминала, вызванно-
го попыткой пользователя ввести данные с клавиатуры, и списком для хранения
обработанных входных данных, поступивших в результате преобразования строко-
вым интерфейсом специальных символов (таких как символы стирания и удаления)
в неструктурированном списке.
Когда процесс ведет запись на терминал (Рисунок 10.13), терминальный
драйвер запускает строковый интерфейс. Строковый интерфейс в цикле считывает
символы из адресного пространства процесса и помещает их в символьный список
для хранения выводных данных до тех пор, пока поток данных не будет исчер-
пан. Строковый интерфейс обрабатывает выводимые символы, например, заменяя
символы табуляции на последовательности пробелов. Если количество символов в
списке для хранения выводных данных превысит верхнюю отметку, строковый ин-
терфейс вызывает процедуры драйвера, пересылающие данные из символьного
списка на терминал и после этого приостанавливающие выполнение процесса, ве-
310
+------------------------------------------------------------+
| алгоритм terminal_write |
| { |
| выполнить (пока из пространства задачи еще поступают |
| данные) |
| { |
| если (на терминал поступает информация) |
| { |
| приступить к выполнению операции записи данных |
| из списка, хранящего выводные данные; |
| приостановиться (до того момента, когда терми- |
| нал будет готов принять следующую порцию дан- |
| ных); |
| продолжить; /* возврат к началу цикла */ |
| } |
| скопировать данные в объеме символьного блока из |
| пространства задачи в список, хранящий выводные |
| данные: строковый интерфейс преобразует символы |
| табуляции и т.д.; |
| } |
| |
| приступить к выполнению операции записи данных из спис-|
| ка, хранящего выводные данные; |
| } |
+------------------------------------------------------------+
Рисунок 10.13. Алгоритм переписи данных на терминал
дущего запись. Когда объем информации в списке для хранения выводных данных
падает за нижнюю отметку, программа обработки прерываний возобновляет выпол-
нение всех процессов, приостановленных до того момента, когда терминал смо-
жет принять следующую порцию данных. Строковый интерфейс завершает цикл об-
работки, скопировав всю выводимую информацию из адресного пространства зада-
чи в соответствующий символьный список, и вызывает выполнение процедур драй-
вера, пересылающих данные на терминал, о которых уже было сказано выше.
Если на терминал ведут запись несколько процессов, они независимо друг
от друга следуют указанной процедуре. Выводимая информация может быть иска-
жена; то есть на терминале данные, записываемые процессами, могут пересе-
каться. Это может произойти из-за того, что процессы ведут запись на терми-
нал, используя несколько вызовов системной функции write. Ядро может перек-
лючать контекст, пока процесс выполняется в режиме задачи, между последова-
тельными вызовами функции write, и вновь запущенные процессы могут вести за-
пись на терминал, пока первый из процессов приостановлен. Выводимые данные
могут быть также искажены и на терминале, поскольку процесс может приостано-
виться на середине выполнения системной функции write, ожидая завершения вы-
вода на терминал из системы предыдущей порции данных. Ядро может запустить
другие процессы, которые вели запись на терминал до того, как первый процесс
был повторно запущен. По этой причине, ядро не гарантирует, что содержимое
буфера данных, выводимое в результате вызова системной функции write, поя-
вится на экране терминала в непрерывном виде.
Рассмотрим программу, приведенную на Рисунке 10.14. Родительский процесс
создает до 18 порожденных процессов; каждый из порожденных процессов записы-
вает строку (с помощью библиотечной функции sprintf) в массив output, кото-
рый включает сообщение и значение счетчика i в момент выполнения функции
fork, и затем входит в цикл пошаговой переписи строки в файл стандартного
вывода. Если стандартным выводом является терминал, терминальный драйвер ре-
гулирует поток поступающих данных. Выводимая строка имеет более 64 символов
311
+----------------------------------------------------------------+
| char form[]="это пример вывода строки из порожденного процесса"| |
| main() |
| { |
| char output[128]; |
| int i; |
| |
| for (i = 0; i < 18; i++) |
| { |
| switch (fork()) |
| { |
| case -1: /* ошибка --- превышено максимальное чис-|
| ло процессов */ |
| exit(); |
| |
| default: /* родительский процесс */ |
| break; |
| |
| case 0: /* порожденный процесс */ |
| /* формат вывода строки в переменной output */ |
| sprintf(output,"%%d\n%s%d\n",form,i,form,i); |
| for (;;) |
| write(1,output,sizeof(output)); |
| } |
| } |
| } |
+----------------------------------------------------------------+
Рисунок 10.14. Передача данных через стандартный вывод
в длину, то есть слишком велика для того, чтобы поместиться в символьном
блоке (длиной 64 байта) в версии V системы. Следовательно, терминальному
драйверу требуется более одного символьного блока для каждого вызова функции
write, иначе выводной поток может стать искаженным. Например, следующие
строки были частью выводного потока, полученного в результате выполнения
программы на машине AT&T 3B20:
this is a sample output string from child 1
this is a sample outthis is a sample output string from child 0
Чтение данных с терминала в каноническом режиме более сложная операция.
В вызове системной функции read указывается количество байт, которые процесс
хочет считать, но строковый интерфейс выполняет чтение по получении символа
перевода каретки, даже если количество символов не указано. Это удобно с
практической точки зрения, так как процесс не в состоянии предугадать,
сколько символов пользователь введет с клавиатуры, и, с другой стороны, не
имеет смысла ждать, когда пользователь введет большое число символов. Напри-
мер, пользователи вводят командные строки для командного процессора shell и
ожидают ответа shell'а на команду по получении символа возврата каретки. При
этом нет никакой разницы, являются ли введенные строки простыми командами,
такими как "date" или "who", или же это более сложные последовательности ко-
манд, подобные следующей:
pic file* | tbl | eqn | troff -mm -Taps | apsend
Терминальный драйвер и строковый интерфейс ничего не знают о синтаксисе
командного процессора shell, и это правильно, поскольку другие программы,
которые считывают информацию с терминалов (например, редакторы), имеют раз-
312
личный синтаксис команд. Поэтому строковый интерфейс выполняет чтение по по-
лучении символа возврата каретки.
На Рисунке 10.15 показан алгоритм чтения с терминала. Предположим, что
терминал работает в каноническом режиме; в разделе 10.3.3 будет рассмотрена
работа в режиме без обработки. Если в настоящий момент в любом из символьных
списков для хранения вводной информации отсутствуют данные, процесс, выпол-
+------------------------------------------------------------+
| алгоритм terminal_read |
| { |
| если (в каноническом символьном списке отсутствуют дан- |
| ные) |
| { |
| выполнить (пока в списке для неструктурированных |
| вводных данных отсутствует информация) |
| { |
| если (терминал открыт с параметром "no delay" |
| (без задержки)) |
| возвратить управление; |
| если (терминал в режиме без обработки с использо-|
| ванием таймера и таймер не активен) |
| предпринять действия к активизации таймера |
| (таблица ответных сигналов); |
| приостановиться (до поступления данных с термина-|
| ла); |
| } |
| |
| /* в списке для неструктурированных вводных данных |
| есть информация */ |
| если (терминал в режиме без обработки) |
| скопировать все данные из списка для неструктури-|
| рованных вводных данных в канонический список; |
| в противном случае /* терминал в каноническом ре- |
| жиме */ |
| { |
| выполнить (пока в списке для неструктурированных |
| вводных данных есть символы) |
| { |
| копировать по одному символу из списка для |
| неструктурированных вводных данных в кано- |
| нический список: |
| выполнить обработку символов стирания и уда-|
| ления; |
| если (символ - "возврат каретки" или "конец |
| файла") |
| прерваться; /* выход из цикла */ |
Два типа дискового интерфейса различаются между собой по использованию
буферного кеша. При работе с блочным интерфейсом ядро пользуется тем же ал-
горитмом, что и для файлов обычного типа, исключение составляет тот момент,
когда после преобразования адреса смещения логического байта в адрес смеще-
ния логического блока (см. алгоритм bmap в главе 4) оно трактует адрес сме-
щения логического блока как физический номер блока в файловой системе. За-
тем, используя буферный кеш, ядро обращается к данным, и, в конечном итоге,
к стратегическому интерфейсу драйвера. Однако, при обращении к диску через
символьный интерфейс (без структурирования данных), ядро не превращает адрес
смещения в адрес файла, а передает его немедленно драйверу, используя для
передачи рабочее пространство задачи. Процедуры чтения и записи, входящие в
состав драйвера, преобразуют смещение в байтах в смещение в блоках и копиру-
ют данные непосредственно в адресное пространство задачи, минуя буферы ядра.
Таким образом, если один процесс записывает на устройство блочного типа,
а второй процесс затем считывает с устройства символьного типа по тому же
адресу, второй процесс может не считать информацию, записанную первым про-
цессом, так как информация может еще находиться в буферном кеше, а не на
диске. Тем не менее, если второй процесс обратится к устройству блочного ти-
па, он автоматически попадет на новые данные, находящиеся в буферном кеше.
При использовании символьного интерфейса можно столкнуться со странной
ситуацией. Если процесс читает или пишет на устройство посимвольного вво-
да-вывода порциями меньшего размера, чем, к примеру, блок, результаты будут
зависеть от драйвера. Например, если производить запись на ленту по 1 байту,
каждый байт может попасть в любой из ленточных блоков.
Преимущество использования символьного интерфейса состоит в скорости,
если не возникает необходимость в кешировании данных для дальнейшей работы.
Процессы, обращающиеся к устройствам ввода -вывода блоками, передают инфор-
мацию блоками, размер каждого из которых ограничивается размером логического
блока в данной файловой системе. Например, если размер логического блока в
файловой системе 1 Кбайт, за одну операцию ввода-вывода может быть передано
не больше 1 Кбайта информации. При этом процессы, обращающиеся к диску с по-
мощью символьного интерфейса, могут передавать за одну дисковую операцию
множество дисковых блоков, в зависимости от возможностей дискового контрол-
лера. С функциональной точки зрения, процесс получает тот же самый резуль-
тат, но символьный интерфейс может работать гораздо быстрее. Если воспользо-
ваться примером, приведенным на Рисунке 10.8, можно увидеть, что когда про-
цесс считывает 4096 байт, используя блочный интерфейс для файловой системы с
размером блока 1 Кбайт, ядро производит четыре внутренние итерации, на каж-
дом шаге обращаясь к диску, прежде чем вызванная системная функция возвраща-
ет управление, но когда процесс использует символьный интерфейс, драйвер мо-
жет закончить чтение за одну дисковую операцию. Более того, использование
блочного интерфейса вызывает дополнительное копирование данных между адрес-
ным пространством задачи и буферами ядра, что отсутствует в символьном ин-
терфейсе.
Терминальные драйверы выполняют ту же функцию, что и остальные драйверы:
управление передачей данных от и на терминалы. Однако, терминалы имеют одну
особенность, связанную с тем, что они обеспечивают интерфейс пользователя с
системой. Обеспечивая интерактивное использование системы UNIX, терминальные
драйверы имеют свой внутренний интерфейс с модулями, интерпретирующими ввод
и вывод строк. В каноническом режиме интерпретаторы строк преобразуют нест-
руктурированные последовательности данных, введенные с клавиатуры, в канони-
ческую форму (то есть в форму, соответствующую тому, что пользователь имел в
305
виду на самом деле) прежде, чем послать эти данные принимающему процессу;
строковый интерфейс также преобразует неструктурированные последовательности
выходных данных, созданных процессом, в формат, необходимый пользователю. В
режиме без обработки строковый интерфейс передает данные между процессами и
терминалом без каких-либо преобразований.
Программисты, например, работают на клавиатуре терминала довольно быст-
ро, но с ошибками. На этот случай терминалы имеют клавишу стирания ("erase";
клавиша может быть обозначена таким образом), чтобы пользователь имел воз-
можность стирать часть введенной строки и вводить коррективы. Терминалы пе-
ресылают машине всю введенную последовательность, включая и символы стирания
(*** *). В каноническом режиме строковый интерфейс буферизует информацию в
строки (набор символов, заканчивающийся символом возврата каретки (*****)) и
процессы стирают символы у себя, прежде чем переслать исправленную последо-
вательность считывающему процессу.
В функции строкового интерфейса входят:
* построчный разбор введенных последовательностей;
* обработка символов стирания;
* обработка символов "удаления", отменяющих все остальные символы, введен-
ные до того в текущей строке;
* отображение символов, полученных терминалом;
* расширение выходных данных, например, преобразование символов табуляции
в последовательности пробелов;
* сигнализирование процессам о зависании терминалов и прерывании строк или
в ответ на нажатие пользователем клавиши удаления;
* предоставление возможности не обрабатывать специальные символы, такие
как символы стирания, удаления и возврата каретки.
Функционирование без обработки подразумевает использование асинхронного
терминала, поскольку процессы могут считывать символы в том виде, в каком
они были введены, вместо того, чтобы ждать, когда пользователь нажмет клави-
шу ввода или возврата каретки.
Ричи отметил, что первые строковые интерфейсы, используемые еще при раз-
работке системы в начале 70-х годов, работали в составе программ командного
процессора и редактора, но не в ядре (см. [Ritchie 84], стр.1580). Однако,
поскольку в их функциях нуждается множество программ, их место в составе яд-
ра. Несмотря на то, что строковый интерфейс выполняет такие функции, из ко-
торых логически вытекает его место между терминальным драйвером и остальной
частью ядра, ядро не запускает строковый интерфейс иначе, чем через терми-
нальный драйвер. На Рисунке 10.9 показаны поток данных, проходящий через
терминальный драйвер и строковый интерфейс, и соответствующие ему управляю-
щие воздействия, проходящие через терминальный драйвер. Пользователи могут
указать, какой строковый интерфейс используется посредством вызова системной
функции ioctl, но реализовать схему, по которой одно устройство использовало
бы несколько строковых интерфейсов одновременно, при чем каждый интерфейсный
модуль, в свою очередь, успешно вызывал бы следующий модуль для обработки
данных, довольно трудно.
Строковый интерфейс обрабатывает данные в символьных списках. Символьный
список (clist) представляет собой переменной длины список символьных блоков
с использованием указателей и с подсчетом количества символов в списке. Сим-
---------------------------------------
(****) В этом разделе рассматривается использование терминалов ввода-вывода,
которые передают все символы, введенные пользователем, без обработки.
(*****) В данной главе используется общий термин "возврат каретки" для обоз-
начения символов возврата каретки и перевода строки.
306
Поток данных Поток управляющих
воздействий
+-----------------------+ +-----------------------+
| Процесс чтения/записи | | Процесс чтения/записи |
+-----------------------+ +-----------------------+
- | ^ - | ^
- v | - v |
- +---------------------+ - +-----------------------+
вывод | Строковый интерфейс| ввод | Терминальный драйвер |
- +---------------------+ - +-----------------------+
- | ^ - | ^
- v | - v |
+-----------------------+ +---------------------+
| Терминальный драйвер | | Строковый интерфейс |
+-----------------------+ +---------------------+
| ^
v |
+-----------------------+
| Драйвер ввода-вывода |
+-----------------------+
| ^
v |
+-------------------------+
| Устройство ввода-вывода |
+-------------------------+
Рисунок 10.9. Последовательность обращений и поток данных че-
рез строковый интерфейс
Указатель Смещение Смещение
на до до
следующий начала конца Массив символов
блок 0 1 2 3 4 5 6 7 8 9 14
----------+---------+---------++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---
| 7 | 14 ||g|a|r|b|a|g|e||| |e|q|n| ||| |...
----+-----+---------+---------++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+---
|
v
Рисунок 10.10. Символьный блок
вольный блок (cblock) содержит указатель на следующий блок в списке, неболь-
шой массив хранимой в символьном виде информации и адреса смещений, показы-
вающие место расположения внутри блока корректной информации (Рисунок
10.10). Смещение до начала показывает первую позицию расположения корректной
информации в массиве, смещение до конца показывает первую позицию расположе-
ния некорректной информации.
Ядро обеспечивает ведение списка свободных символьных блоков и выполняет
над символьными списками и символьными блоками шесть операций.
1. Ядро назначает драйверу символьный блок из списка свободных символьных
блоков.
2. Оно также возвращает символьный блок в список свободных символьных бло-
ков.
3. Ядро может выбирать первый символ из символьного списка: оно удаляет
первый символ из первого символьного блока в списке и устанавливает зна-
чения счетчика символов в списке и указателей в блоке таким образом,
чтобы последующие операции не выбирали один и тот же символ. Если в ре-
307
зультате операции выбран последний символ блока, ядро помещает в список
свободных символьных блоков пустой блок и переустанавливает указатели в
символьном списке. Если в символьном списке отсутствуют символы, ядро
возвращает пустой символ.
4. Ядро может поместить символ в конец символьного списка путем поиска пос-
леднего символьного блока в списке, включения символа в него и переуста-
новки адресов смещений. Если символьный блок заполнен, ядро выделяет но-
вый символьный блок, включает его в конец символьного списка и помещает
символ в новый блок.
5. Ядро может удалять от начала списка группу символов по одному блоку за
одну операцию, что эквивалентно удалению всех символов в блоке за один
раз.
6. Ядро может поместить блок с символами в конец символьного списка.
Символьные списки позволяют создать несложный механизм буферизации, по-
лезный при небольшом объеме передаваемых данных, типичном для медленных уст-
ройств, таких как терминалы. Они дают возможность манипулировать с данными с
каждым символом в отдельности и с группой символьных блоков. Например, Рису-
нок 10.11 иллюстрирует удаление символов из символьного списка; ядро удаляет
по одному символу из первого блока в списке (Рисунок 10.11а-в) до тех пор,
пока в блоке не останется ни одного символа (Рисунок
символьный символьные
список блоки
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 27 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(а)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 1 8 | | | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 26 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(б)
308
+-------+ +---+---+---+---+---+---+---+---+
+----->| 2 8 | | | | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 25 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(в)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | * | | | | | t | b | l | |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 19 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символов | +--+ | 0 8 | | | | | t | r | o | f | f | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 3 | | - | m | m | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(г)
Рисунок 10.11. Удаление символов из символьного списка
символьный символьные
список блоки
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 22 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символа | +--+ | 0 8 | | * | | | | | t | b | l | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 6 | | | | | t | r | o | f | | |
+-------+ +---+---+---+---+---+---+---+---+
(а)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 23 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символа | +--+ | 0 8 | | * | | | | | t | b | l | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 7 | | | | | t | r | o | f | f | |
+-------+ +---+---+---+---+---+---+---+---+
309
(б)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
+----------+-+ | v
| 24 | +--+ +-------+ +---+---+---+---+---+---+---+---+
| символа | +--+ | 0 8 | | * | | | | | t | b | l | |
+----------+-+ | ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | | | | t | r | o | f | f | |
+-------+ +---+---+---+---+---+---+---+---+
(в)
+-------+ +---+---+---+---+---+---+---+---+
+----->| 0 8 | | p | i | c | | f | i | l | e |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----------+-+ | | 0 8 | | * | | | | | t | b | l | |
| 25 | +--+ ++------+ +---+---+---+---+---+---+---+---+
| символов | +--+ v
+----------+-+ | +-------+ +---+---+---+---+---+---+---+---+
| | 0 8 | | | | | t | r | o | f | f | |
| ++------+ +---+---+---+---+---+---+---+---+
| v
| +-------+ +---+---+---+---+---+---+---+---+
+----->| 0 1 | | - | | | | | | | |
+-------+ +---+---+---+---+---+---+---+---+
(г)
Рисунок 10.12. Включение символов в символьный список
10.11г); затем оно устанавливает указатель списка на следующий блок, который
становится первым блоком в списке. Подобно этому на Рисунке 10.12 показано,
как ядро включает символы в символьный список; при этом предполагается, что
в одном блоке помещается до 8 символов и что ядро размещает новый блок в
конце списка (Рисунок 10.12г).
Структуры данных, с которыми работают терминальные драйверы, связаны с
тремя символьными списками: списком для хранения данных, выводимых на терми-
нал, списком для хранения неструктурированных вводных данных, поступивших в
результате выполнения программы обработки прерывания от терминала, вызванно-
го попыткой пользователя ввести данные с клавиатуры, и списком для хранения
обработанных входных данных, поступивших в результате преобразования строко-
вым интерфейсом специальных символов (таких как символы стирания и удаления)
в неструктурированном списке.
Когда процесс ведет запись на терминал (Рисунок 10.13), терминальный
драйвер запускает строковый интерфейс. Строковый интерфейс в цикле считывает
символы из адресного пространства процесса и помещает их в символьный список
для хранения выводных данных до тех пор, пока поток данных не будет исчер-
пан. Строковый интерфейс обрабатывает выводимые символы, например, заменяя
символы табуляции на последовательности пробелов. Если количество символов в
списке для хранения выводных данных превысит верхнюю отметку, строковый ин-
терфейс вызывает процедуры драйвера, пересылающие данные из символьного
списка на терминал и после этого приостанавливающие выполнение процесса, ве-
310
+------------------------------------------------------------+
| алгоритм terminal_write |
| { |
| выполнить (пока из пространства задачи еще поступают |
| данные) |
| { |
| если (на терминал поступает информация) |
| { |
| приступить к выполнению операции записи данных |
| из списка, хранящего выводные данные; |
| приостановиться (до того момента, когда терми- |
| нал будет готов принять следующую порцию дан- |
| ных); |
| продолжить; /* возврат к началу цикла */ |
| } |
| скопировать данные в объеме символьного блока из |
| пространства задачи в список, хранящий выводные |
| данные: строковый интерфейс преобразует символы |
| табуляции и т.д.; |
| } |
| |
| приступить к выполнению операции записи данных из спис-|
| ка, хранящего выводные данные; |
| } |
+------------------------------------------------------------+
Рисунок 10.13. Алгоритм переписи данных на терминал
дущего запись. Когда объем информации в списке для хранения выводных данных
падает за нижнюю отметку, программа обработки прерываний возобновляет выпол-
нение всех процессов, приостановленных до того момента, когда терминал смо-
жет принять следующую порцию данных. Строковый интерфейс завершает цикл об-
работки, скопировав всю выводимую информацию из адресного пространства зада-
чи в соответствующий символьный список, и вызывает выполнение процедур драй-
вера, пересылающих данные на терминал, о которых уже было сказано выше.
Если на терминал ведут запись несколько процессов, они независимо друг
от друга следуют указанной процедуре. Выводимая информация может быть иска-
жена; то есть на терминале данные, записываемые процессами, могут пересе-
каться. Это может произойти из-за того, что процессы ведут запись на терми-
нал, используя несколько вызовов системной функции write. Ядро может перек-
лючать контекст, пока процесс выполняется в режиме задачи, между последова-
тельными вызовами функции write, и вновь запущенные процессы могут вести за-
пись на терминал, пока первый из процессов приостановлен. Выводимые данные
могут быть также искажены и на терминале, поскольку процесс может приостано-
виться на середине выполнения системной функции write, ожидая завершения вы-
вода на терминал из системы предыдущей порции данных. Ядро может запустить
другие процессы, которые вели запись на терминал до того, как первый процесс
был повторно запущен. По этой причине, ядро не гарантирует, что содержимое
буфера данных, выводимое в результате вызова системной функции write, поя-
вится на экране терминала в непрерывном виде.
Рассмотрим программу, приведенную на Рисунке 10.14. Родительский процесс
создает до 18 порожденных процессов; каждый из порожденных процессов записы-
вает строку (с помощью библиотечной функции sprintf) в массив output, кото-
рый включает сообщение и значение счетчика i в момент выполнения функции
fork, и затем входит в цикл пошаговой переписи строки в файл стандартного
вывода. Если стандартным выводом является терминал, терминальный драйвер ре-
гулирует поток поступающих данных. Выводимая строка имеет более 64 символов
311
+----------------------------------------------------------------+
| char form[]="это пример вывода строки из порожденного процесса"| |
| main() |
| { |
| char output[128]; |
| int i; |
| |
| for (i = 0; i < 18; i++) |
| { |
| switch (fork()) |
| { |
| case -1: /* ошибка --- превышено максимальное чис-|
| ло процессов */ |
| exit(); |
| |
| default: /* родительский процесс */ |
| break; |
| |
| case 0: /* порожденный процесс */ |
| /* формат вывода строки в переменной output */ |
| sprintf(output,"%%d\n%s%d\n",form,i,form,i); |
| for (;;) |
| write(1,output,sizeof(output)); |
| } |
| } |
| } |
+----------------------------------------------------------------+
Рисунок 10.14. Передача данных через стандартный вывод
в длину, то есть слишком велика для того, чтобы поместиться в символьном
блоке (длиной 64 байта) в версии V системы. Следовательно, терминальному
драйверу требуется более одного символьного блока для каждого вызова функции
write, иначе выводной поток может стать искаженным. Например, следующие
строки были частью выводного потока, полученного в результате выполнения
программы на машине AT&T 3B20:
this is a sample output string from child 1
this is a sample outthis is a sample output string from child 0
Чтение данных с терминала в каноническом режиме более сложная операция.
В вызове системной функции read указывается количество байт, которые процесс
хочет считать, но строковый интерфейс выполняет чтение по получении символа
перевода каретки, даже если количество символов не указано. Это удобно с
практической точки зрения, так как процесс не в состоянии предугадать,
сколько символов пользователь введет с клавиатуры, и, с другой стороны, не
имеет смысла ждать, когда пользователь введет большое число символов. Напри-
мер, пользователи вводят командные строки для командного процессора shell и
ожидают ответа shell'а на команду по получении символа возврата каретки. При
этом нет никакой разницы, являются ли введенные строки простыми командами,
такими как "date" или "who", или же это более сложные последовательности ко-
манд, подобные следующей:
pic file* | tbl | eqn | troff -mm -Taps | apsend
Терминальный драйвер и строковый интерфейс ничего не знают о синтаксисе
командного процессора shell, и это правильно, поскольку другие программы,
которые считывают информацию с терминалов (например, редакторы), имеют раз-
312
личный синтаксис команд. Поэтому строковый интерфейс выполняет чтение по по-
лучении символа возврата каретки.
На Рисунке 10.15 показан алгоритм чтения с терминала. Предположим, что
терминал работает в каноническом режиме; в разделе 10.3.3 будет рассмотрена
работа в режиме без обработки. Если в настоящий момент в любом из символьных
списков для хранения вводной информации отсутствуют данные, процесс, выпол-
+------------------------------------------------------------+
| алгоритм terminal_read |
| { |
| если (в каноническом символьном списке отсутствуют дан- |
| ные) |
| { |
| выполнить (пока в списке для неструктурированных |
| вводных данных отсутствует информация) |
| { |
| если (терминал открыт с параметром "no delay" |
| (без задержки)) |
| возвратить управление; |
| если (терминал в режиме без обработки с использо-|
| ванием таймера и таймер не активен) |
| предпринять действия к активизации таймера |
| (таблица ответных сигналов); |
| приостановиться (до поступления данных с термина-|
| ла); |
| } |
| |
| /* в списке для неструктурированных вводных данных |
| есть информация */ |
| если (терминал в режиме без обработки) |
| скопировать все данные из списка для неструктури-|
| рованных вводных данных в канонический список; |
| в противном случае /* терминал в каноническом ре- |
| жиме */ |
| { |
| выполнить (пока в списке для неструктурированных |
| вводных данных есть символы) |
| { |
| копировать по одному символу из списка для |
| неструктурированных вводных данных в кано- |
| нический список: |
| выполнить обработку символов стирания и уда-|
| ления; |
| если (символ - "возврат каретки" или "конец |
| файла") |
| прерваться; /* выход из цикла */ |