The design of the unix operating system by Maurice J
Вид материала | Реферат |
- Лекция 10. Файловые системы Unix, 116.79kb.
- Уровни рассмотрения, 314.07kb.
- Курс по операционным системам (на примере ос windows) Основан на учебном курсе Windows, 29.21kb.
- Выполнил ученик 11 «А» класса, 443.51kb.
- Ос лекция 1 (2-й семестр – временно), 101.4kb.
- Operating System, 7686.97kb.
- Unix-подобные операционные системы, характеристики, особенности, разновидности, 40.63kb.
- 1. ms sql server. Общие сведения, 66.03kb.
- Shanti ananda maurice, 89.84kb.
- Методические материалы, 3002.45kb.
щего использования. Например, символьные списки могли бы быть по-
лезными благодаря своим возможностям в буферизации, но они требу-
ют больших затрат ресурсов на посимвольную обработку. Попытки
обойти этот механизм, чтобы повысить производительность системы,
привели к нарушению модульности подсистемы управления вводом-вы-
водом. Отсутствие общности на уровне драйверов распространяется
вплоть до уровня команд пользователя, на котором несколько команд
могут выполнять общие логические функции, но различными средства-
ми. Еще один недостаток построения драйверов заключается в том,
что сетевые протоколы требуют использования средства, подобного
строковому интерфейсу, в котором каждая дисциплина реализует одну
из частей протокола и составные части соединяются гибким образом.
Однако, соединить традиционные строковые интерфейсы довольно
трудно.
Ричи недавно разработал схему, получившую название "потоки"
(streams), для повышения модульности и гибкости подсистемы управ-
ления вводом-выводом. Нижеследующее описание основывается на его
работе [Ritchie 84b], хотя реализация этой схемы в версии V слег-
ка отличается. Поток представляет собой полнодуплексную связь
между процессом и драйвером устройства. Он состоит из совокупнос-
ти линейно связанных между собой пар очередей, каждая из которых
(пара) включает одну очередь для ввода и другую - для вывода.
Когда процесс записывает данные в поток, ядро посылает данные в
очереди для вывода; когда драйвер устройства получает входные
данные, он пересылает их в очереди для ввода к процессу, произво-
дящему чтение. Очереди обмениваются сообщениями с соседними оче-
редями, используя четко определенный интерфейс. Каждая пара оче-
редей связана с одним из модулей ядра, таким как драйвер,
строковый интерфейс или протокол, и модули ядра работают с данны-
ми, прошедшими через соответствующие очереди.
Каждая очередь представляет собой структуру данных, состоящую
из следующих элементов:
* процедуры открытия, вызываемой во время выполнения системной
функции open
* процедуры закрытия, вызываемой во время выполнения системной
функции close
* процедуры "вывода", вызываемой для передачи сообщения в оче-
редь
* процедуры "обслуживания", вызываемой, когда очередь заплани-
рована к исполнению
* указателя на следующую очередь в потоке
* указателя на список сообщений, ожидающих обслуживания
* указателя на внутреннюю структуру данных, с помощью которой
поддерживается рабочее состояние очереди
* флагов, а также верхней и нижней отметок, используемых для
управления потоками данных, диспетчеризации и поддержания ра-
бочего состояния очереди.
Ядро выделяет пары очередей, соседствующие в памяти; следова-
тельно, очередь легко может отыскать своего партнера по паре.
-----------┐
│ Индекс │
------------------------+ файла │
│ │устройства│
v L-----------
-------------T-----------┐
Заголовок │ Очередь │ Очередь │
потока │ для вывода │ для ввода │
L------T-----+------------
│
│ │
v │
-------------T-----+-----┐
Драйвер │ Очередь │ Очередь │ пара очередей
│ для вывода │ для ввода │
L------------+------------
Рисунок 10.20. Поток после открытия
Устройство с потоковым драйвером является устройством посим-
вольного ввода-вывода; оно имеет в таблице ключей устройств
соответствующего типа специальное поле, которое указывает на
структуру инициализации потока, содержащую адреса процедур, а
также верхнюю и нижнюю отметки, упомянутые выше. Когда ядро вы-
полняет системную функцию open и обнаруживает, что файл устройс-
тва имеет тип "специальный символьный", оно проверяет наличие но-
вого поля в таблице ключей устройств посимвольного ввода-вывода.
Если в таблице отсутствует соответствующая точка входа, то драй-
вер не является потоковым, и ядро выполняет процедуру, обычную
для устройств посимвольного ввода-вывода. Однако, при первом же
открытии потокового драйвера ядро выделяет две пары очередей -
одну для заголовка потока и другую для драйвера. У всех открытых
потоков модуль заголовка имеет идентичную структуру: он содержит
общую процедуру "вывода" и общую процедуру "обслуживания" и имеет
интерфейс с модулями ядра более высокого уровня, выполняющими
функции read, write и ioctl. Ядро инициализирует структуру очере-
дей драйвера, назначая значения указателям каждой очереди и копи-
руя адреса процедур драйвера из структуры инициализации драйвера,
и запускает процедуру открытия. Процедура открытия драйвера вы-
полняет обычную инициализацию, но при этом сохраняет информацию,
необходимую для повторного обращения к ассоциированной с этой
процедурой очереди. Наконец, ядро отводит специальный указатель в
копии индекса в памяти для ссылки на заголовок потока (Рисунок
10.20). Когда еще один процесс открывает устройство, ядро обнару-
живает назначенный ранее поток с помощью этого указателя и запус-
кает процедуру открытия для всех модулей потока.
Модули поддерживают связь со своими соседями по потоку путем
передачи сообщений. Сообщение состоит из списка заголовков бло-
ков, содержащих информацию сообщения; каждый заголовок блока со-
держит ссылку на место расположения начала и конца информации
блока. Существует два типа сообщений - управляющее и информацион-
ное, которые определяются указателями типа в заголовке сообщения.
Управляющие сообщения могут быть результатом выполнения системной
функции ioctl или результатом особых условий, таких как зависание
терминала, а информационные сообщения могут возникать в результа-
те выполнения системной функции write или в результате поступле-
ния данных от устройства.
Сообщение 1 Сообщение 2 Сообщение 3
----------┐ ----------┐ ----------┐
│ Блок +--------->│ +-------->│ │
L----T----- L---------- L----T-----
│ │
v v
----------┐ ----------┐
│ │ │ │
L----T----- L----------
│
v
----------┐
│ │
L----------
Рисунок 10.21. Сообщения в потоках
Когда процесс производит запись в поток, ядро копирует данные
из адресного пространства задачи в блоки сообщения, которые выде-
ляются модулем заголовка потока. Модуль заголовка потока запуска-
ет процедуру "вывода" для модуля следующей очереди, которая обра-
батывает сообщение, незамедлительно передает его в следующую
очередь или ставит в эту же очередь для последующей обработки. В
последнем случае модуль связывает заголовки блоков сообщения в
список с указателями, формируя двунаправленный список (Рисунок
10.21). Затем он устанавливает в структуре данных очереди флаг,
показывая тем самым, что имеются данные для обработки, и планиру-
ет собственное обслуживание. Модуль включает очередь в список
очередей, требующих обслуживания и запускает механизм диспетчери-
зации; планировщик (диспетчер) вызывает процедуры обслуживания
для каждой очереди в списке. Ядро может планировать обслуживание
модулей по программному прерыванию, подобно тому, как оно вызыва-
ет функции в таблице ответных сигналов (см. главу 8); обработчик
программных прерываний вызывает индивидуальные процедуры обслужи-
вания.
-----------┐
│ Индекс │
------------------------+ файла │
│ │устройства│
v L-----------
-------------T-----------┐
Заголовок │ Очередь │ Очередь │
потока │ для вывода │ для ввода │
L------T-----+------------
│
│ │
v │
-------------T-----------┐
Строковый │ Очередь │ Очередь │
интерфейс │ для вывода │ для ввода │
L------T-----+------------
│
│ │
v │
-------------T-----+-----┐
Терминальный │ Очередь │ Очередь │
драйвер │ для вывода │ для ввода │
L------------+------------
Рисунок 10.22. Продвижение модуля к потоку
Процессы могут "продвигать" модули к открытому потоку, ис-
пользуя вызов системной функции ioctl. Ядро помещает выдвинутый
модуль сразу под заголовком потока и связывает указатели очереди
таким образом, чтобы сохранить двунаправленную структуру списка.
Модули, расположенные в потоке ниже, не беспокоятся о том, связа-
ны ли они с заголовком потока или же с выдвинутым модулем: интер-
фейсом выступает процедура "вывода" следующей очереди в потоке; а
следующая очередь принадлежит только что выдвинутому модулю. Нап-
ример, процесс может выдвинуть модуль строкового интерфейса в по-
ток терминального драйвера с целью обработки символов стирания и
удаления (Рисунок 10.22); модуль строкового интерфейса не имеет
тех же составляющих, что и строковые интерфейсы, рассмотренные в
разделе 10.3, но выполняет те же функции. Без модуля строкового
интерфейса терминальный драйвер не обработает вводные символы и
они поступят в заголовок потока в неизмененном виде. Сегмент
программы, открывающий терминал и выдвигающий строковый интер-
фейс, может выглядеть следующим образом:
fd = open("/dev/ttyxy",O_RDWR);
ioctl(fd,PUSH,TTYLD);
где PUSH - имя команды, а TTYLD - число, идентифицирующее модуль
строкового интерфейса. Не существует ограничения на количество
модулей, могущих быть выдвинутыми в поток. Процесс может выталки-
вать модули из потока в порядке поступления, "первым пришел -
первым вышел", используя еще один вызов системной функции ioctl
ioctl(fd,POP,0);
При том, что модуль строкового интерфейса выполняет обычные
функции по управлению терминалом, соответствующее ему устройство
может быть средством сетевой связи вместо того, чтобы обеспечи-
вать связь с одним-единственным терминалом. Модуль строкового ин-
терфейса работает одинаково, независимо от того, какого типа мо-
дуль расположен ниже него. Этот пример наглядно демонстрирует
повышение гибкости вследствие соединения модулей ядра.
10.4.1 Более детальное рассмотрение потоков
Пайк описывает реализацию мультиплексных виртуальных термина-
лов, использующую потоки (см. [Pike 84]). Пользователь видит нес-
колько виртуальных терминалов, каждый из которых занимает отдель-
ное окно на экране физического терминала. Хотя в статье Пайка
рассматривается схема для интеллектуальных графических термина-
лов, она работала бы и для терминалов ввода-вывода тоже; каждое
окно занимало бы целый экран и пользователь для переключения вир-
туальных окон набирал бы последовательность управляющих клавиш.
----------┐ ----------┐ ------------------┐
Уровень │ shell 1 │ │ shell 2 │ │ mpx │
пользователя L---------- L---------- L------------------
---------------T---------------T-----------T---T-------T----
Уровень ядра │ │ ---- │ │
│ │ │ │ │ ---- │ │ │ │
│ │ │ │ │ │ │ │ │ │
v │ v │ со- v │ v │ со- │ │
терми- --T+┐ терми- --T+┐ об---T+┐ --T+┐об- │ │
нальная │ │ │ нальная │ │ │ ще-│ │ │ │ │ │ще- │ │
линия LT+-- линия LT+-- нияLT+-- LT+--ния │ │
│ ------------+-------- │ │ │
│ │ │ ----------+-+--------- │ │ │ │
│ │ │ │ │ │ ------------- │ │ │
v │ v │ v │ v ------------- v │
--T+T-T+┐ --T+T-T+┐ --T+┐
псевдо- │ │ │ │ │ │ │ │ │ │ псевдо- │ │ │
терми- L++-+++-- L++-+++-- терми- L-+--
нальная │ │ │ │ нальная терми-
пара 1 │ L-- │ │ L-- │ пара 2 нальный
L------ L------ драйвер
Рисунок 10.23. Отображение виртуальных окон на экране физи-
ческого терминала
На Рисунке 10.23 показана схема расположения процессов и
модулей ядра. Пользователь вызывает процесс mpx, контролирующий
работу физического терминала. Mpx читает данные из линии физичес-
кого терминала и ждет объявления об управляющих событиях, таких
как создание нового окна, переключение управления на другое окно,
удаление окна и т.п.
-----------------------------------------------------------------┐
│ /* предположим, что дескрипторы файлов 0 и 1 уже относятся к │
│ физическому терминалу */ │
│ для(;;) /* цикл */ │
│ { │
│ выбрать(ввод); /* ждать ввода из какой-либо линии */ │
│ прочитать данные, введенные из линии; │
│ переключить(линию с вводимыми данными) │
│ { │
│ если выбран физический терминал: /* данные вводятся по ли- │
│ нии физического терми- │
│ нала */ │
│ если(считана управляющая команда) /* например, создание │
│ нового окна */ │
│ { │
│ открыть свободный псевдотерминал; │
│ пойти по ветви нового процесса: │
│ если(процесс родительский) │
│ { │
│ выдвинуть интерфейс сообщений в сторону mpx; │
│ продолжить; /* возврат в цикл "для" */ │
│ } │
│ /* процесс-потомок */ │
│ закрыть ненужные дескрипторы файлов; │
│ открыть другой псевдотерминал из пары, выбрать stdin, │
│ stdout, stderr; │
│ выдвинуть строковый интерфейс терминала; │
│ запустить shell; /* подобно виртуальному терминалу */│
│ } │
│ /* "обычные" данные, появившиеся через виртуальный терминал */ │
│ демультиплексировать считывание данных с физического тер-│
│ минала, снять заголовки и вести запись на соответствую- │
│ щий псевдотерминал; │
│ продолжить; /* возврат в цикл "для" */ │
│ │
│ если выбран логический терминал: /* виртуальный терминал │
│ связан с окном */ │
│ закодировать заголовок, указывающий назначение информации│
│ окна; │
│ переписать заголовок и информацию на физический терминал;│
│ продолжить; /* возврат в цикл "для" */ │
│ } │
│ } │
L-----------------------------------------------------------------
Рисунок 10.24. Псевдопрограмма мультиплексирования окон
Когда mpx получает уведомление о том, что пользователю нужно
создать новое окно, он создает процесс, управляющий новым окном,
и поддерживает связь с ним через псевдотерминал. Псевдотерминал -
это программное устройство, работающее по принципу пары: выходные
данные, направляемые к одной составляющей пары, посылаются на
вход другой составляющей; входные данные посылаются тому модулю
потока, который расположен выше по течению. Для того, чтобы отк-
рыть окно (Рисунок 10.24), mpx назначает псевдотерминальную пару
и открывает одну из составляющих пары, направляя поток к ней (от-
крытие драйвера служит гарантией того, что псевдотерминальная па-
ра не была выбрана раньше). Mpx ветвится и новый процесс открыва-
ет другую составляющую псевдотерминальной пары. Mpx выдвигает
модуль управления сообщениями в псевдотерминальный поток, чтобы
преобразовывать управляющие сообщения в информационные (об этом в
следующем параграфе), а порожденный процесс помещает в псевдотер-
минальный поток модуль строкового интерфейса перед запуском
shell'а. Этот shell теперь выполняется на виртуальном терминале;
для пользователя виртуальный терминал неотличим от физического.
Процесс mpx является мультиплексором, направляющим вывод дан-
ных с виртуальных терминалов на физический терминал и демультип-
лексирующим ввод данных с физического терминала на подходящий
виртуальный. Mpx ждет поступления данных по любой из линий, ис-
пользуя системную функцию select. Когда данные поступают от физи-
ческого терминала, mpx решает вопрос, являются ли поступившие
данные управляющим сообщением, извещающим о необходимости созда-
ния нового окна или удаления старого, или же это информационное
сообщение, которое необходимо разослать процессам, считывающим
информацию с виртуального терминала. В последнем случае данные
имеют заголовок, идентифицирующий тот виртуальный терминал, к ко-
торому они относятся; mpx стирает заголовок с сообщения и перепи-
сывает данные в соответствующий псевдотерминальный поток. Драйвер
псевдотерминала отправляет данные через строковый интерфейс тер-
минала процессам, осуществляющим чтение. Обратная процедура имеет
место, когда процесс ведет запись на виртуальный терминал; mpx
присоединяет заголовок к данным, информируя физический терминал,
для вывода в какое из окон предназначены эти данные.
Если процесс вызывает функцию ioctl с виртуального терминала,
строковый интерфейс терминала задает необходимые установки терми-
нала для его виртуальной линии; для каждого из виртуальных терми-
налов установки могут быть различными. Однако, на физический тер-
минал должна быть послана и кое-какая информация, зависящая от
типа устройства. Модуль управления сообщениями преобразует управ-
ляющие сообщения, генерируемые функцией ioctl, в информационные
сообщения, предназначенные для чтения и записи их процессом mpx,
и эти сообщения передаются на физическое устройство.
10.4.2 Анализ потоков
Ричи упоминает о том, что им была предпринята попытка созда-
ния потоков только с процедурами "вывода" или только с процедура-
ми обслуживания. Однако, процедура обслуживания необходима для
управления потоками данных, так как модули должны иногда ставить
данные в очередь, если соседние модули на время закрыты для прие-
ма данных. Процедура "вывода" так же необходима, поскольку данные
должны иногда доставляться в соседние модули незамедлительно.
Например, строковому интерфейсу терминала нужно вести эхо-сопро-
вождение ввода данных на терминале в темпе с процессом. Системная
функция write могла бы запускать процедуру "вывода" для следующей
очереди непосредственно, та, в свою очередь, вызывала бы процеду-
ру "вывода" для следующей очереди и так далее, не нуждаясь в ме-
ханизме диспетчеризации. Процесс приостановился бы в случае пере-
полнения очередей для вывода. Однако, со стороны ввода модули не
могут приостанавливаться, поскольку их выполнение вызывается
программой обработки прерываний, иначе был бы приостановлен со-
вершенно безобидный процесс. Связь между модулями не должна быть
симметричной в направлениях ввода и вывода, хотя это и делает
схему менее изящной.
Также было бы желательно реализовать каждый модуль в виде от-
дельного процесса, но использование большого количества модулей
привело бы к переполнению таблицы процессов. Модули наделяются
специальным механизмом диспетчеризации - программным прерыванием,
независимым от обычного планировщика процессов. По этой причине
модули не могут приостанавливать свое выполнение, так как они
приостанавливали бы тем самым произвольный процесс (тот, который
прерван). Модули должны хранить внутри себя информацию о своем
состоянии, что делает лежащие в их основе программы более гро-
моздкими, чем если бы приостановка выполнения была разрешена.
В реализации потоков можно выделить несколько отклонений или
несоответствий:
* Учет ресурсов процесса в потоках затрудняется, поскольку мо-
дулям необязательно выполняться в контексте процесса, исполь-
зующего поток. Ошибочно предполагать, что все процессы одина-
ково используют модули потоков, поскольку одним процессам
может потребоваться использование сложных сетевых протоколов,
тогда как другие могут использовать простые строковые интер-
фейсы.
* Пользователи имеют возможность переводить терминальный драй-
вер в режим без обработки, в котором функция read возвращает