The design of the unix operating system by Maurice J

Вид материалаРеферат
Подобный материал:
1   ...   39   40   41   42   43   44   45   46   ...   55

│ │

│ main() │

│ { │

│ register int i; │

│ │

│ for (i = 0; i < 18; i++) │

│ { │

│ switch (fork()) │

│ { │

│ case -1: /* ошибка */ │

│ printf("операция fork не выполнена из-за ошибки\n");│

│ exit(); │

│ │

│ default: /* родительский процесс */ │

│ break; │

│ │

│ case 0: /* порожденный процесс */ │

│ for (;;) │

│ { │

│ read(0,input,256); /* чтение строки */ │

│ printf("%d чтение %s\n",i,input); │

│ } │

│ } │

│ } │

│ } │

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


Рисунок 10.16. Конкуренция за данные, вводимые с терминала


На Рисунке 10.16 приведена программа, в которой родительский

процесс порождает несколько процессов, осуществляющих чтение из

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

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

медленно для того, чтобы удовлетворить все процессы, ведущие чте-

ние, поэтому процессы большую часть времени находятся в приоста-

новленном состоянии в соответствии с алгоритмом terminal_read,

ожидая ввода данных. Когда пользователь вводит строку данных,

программа обработки прерываний от терминала возобновляет выполне-

ние всех процессов, ведущих чтение; поскольку они были приоста-

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

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

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

строку данных; успешно созданный процесс печатает значение пере-

менной i в момент его создания. Все другие процессы в конце кон-

цов будут запущены, но вполне возможно, что они не обнаружат

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

выполнение снова будет приостановлено. Вся процедура повторяется

для каждой введенной строки; нельзя дать гарантию, что ни один из

процессов не захватит все введенные данные.

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

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

образом. С другой стороны, ядро обязано позволять процессам од-

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

мандным процессором shell процессы, читающие из стандартного вво-

да, никогда не будут работать, поскольку shell тоже обращается к

стандартному вводу. Короче говоря, процессы должны синхронизиро-

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

Когда пользователь вводит символ "конец файла" (Ctrl-d в

ASCII), строковый интерфейс передает функции read введенную стро-

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

ные (код возврата 0) функции read, если в символьном списке

встретился только символ "конец файла"; вызывающий процесс сам

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

вать данные с терминала. Если еще раз обратиться к примерам прог-

рамм по shell'у, приведенным в главе 7, можно отметить, что цикл

работы shell'а завершается, когда пользователь нажимает :

функция read возвращает 0 и производится выход из shell'а.

В этом разделе рассмотрена работа терминалов ввода-вывода,

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

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

лектуальные терминалы подготавливают свой вводной поток на внеш-

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

работы. Структура драйверов для таких терминалов походит на

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

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

возможностей внешних устройств.


10.3.3 Терминальный драйвер в режиме без обработки символов


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

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

вок с помощью системной функции ioctl. Сходным образом они уста-

навливают необходимость эхо-сопровождения ввода данных с термина-

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

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

ливают выводной поток символов. В информационной структуре терми-

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

[SVID 85], стр.281), и строковый интерфейс получает параметры

функции ioctl и устанавливает или считывает значения соответству-

ющих полей структуры данных. Когда процесс устанавливает значения

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

ющих терминал. Установки терминала не сбрасываются автоматически

при выходе из процесса, сделавшего изменения в установках.

Процессы могут также перевести терминал в режим без обработки

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

соответствии с тем, как пользователь ввел их: обработка вводного

потока полностью отсутствует. Однако, ядро должно знать, когда

выполнить вызванную пользователем системную функцию read, пос-

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

символ. Оно выполняет функцию read после того, как с терминала

будет введено минимальное число символов или по прохождении фик-

сированного промежутка времени от момента получения с терминала

любого набора символов. В последнем случае ядро хронометрирует

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

налов (глава 8). Оба критерия (минимальное число символов и фик-

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

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

прерываний строкового интерфейса возобновляет выполнение всех

приостановленных процессов. Драйвер пересылает все символы из

списка для хранения неструктурированных вводных данных в канони-

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

же самому алгоритму, что и в случае работы в каноническом режиме.

Режим без обработки символов особенно важен в экранно-ориентиро-

ванных приложениях, таких как экранный редактор vi, многие из ко-

манд которого не заканчиваются символом возврата каретки. Напри-

мер, команда dw удаляет слово в текущей позиции курсора.

На Рисунке 10.17 приведена программа, использующая функцию

ioctl для сохранения текущих установок терминала для файла с

дескриптором 0, что соответствует значению дескриптора файла

стандартного ввода. Функция ioctl с командой TCGETA приказывает


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

│ #include

│ #include

│ struct termio savetty; │

│ main() │

│ { │

│ extern sigcatch(); │

│ struct termio newtty; │

│ int nrd; │

│ char buf[32]; │

│ signal(SIGINT,sigcatch); │

│ if (ioctl(0,TCGETA,&savetty) == -1) │

│ { │

│ printf("ioctl завершилась неудачно: нет терминала\n"); │

│ exit(); │

│ } │

│ newtty = savetty; │

│ newtty.c_lflag &= ~ICANON;/* выход из канонического режима */│

│ newtty.c_lflag &= ~ECHO; /* отключение эхо-сопровождения*/ │

│ newtty.c_cc[VMIN] = 5; /* минимум 5 символов */ │

│ newtty.c_cc[VTIME] = 100; /* интервал 10 секунд */ │

│ if (ioctl(0,TCSETAF,&newtty) == -1) │

│ { │

│ printf("не могу перевести тер-л в режим без обработки\n");│

│ exit(); │

│ } │

│ for(;;) │

│ { │

│ nrd = read(0,buf,sizeof(buf)); │

│ buf[nrd] = 0; │

│ printf("чтение %d символов '%s'\n",nrd,buf); │

│ } │

│ } │

│ sigcatch() │

│ { │

│ ioctl(0,TCSETAF,&savetty); │

│ exit(); │


│ } │

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


Рисунок 10.17. Режим без обработки - чтение 5-символьных блоков


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

savetty в адресном пространстве задачи. Эта команда часто исполь-

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

или нет, поскольку она ничего не изменяет в системе: если она за-

вершается неудачно, процессы предполагают, что файл не является

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

того, чтобы перевести терминал в режим без обработки: он отключа-

ет эхо-сопровождение ввода символов и готовится к выполнению опе-

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

минимум, или по прохождении 10 секунд с момента ввода первой пор-

ции символов. Когда процесс получает сигнал о прерывании, он

сбрасывает первоначальные параметры терминала и завершается.


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

│ #include

│ │

│ main() │


│ { │

│ register int i,n; │

│ int fd; │

│ char buf[256]; │

│ │

│ /* открытие терминала только для чтения с опцией "no delay" */ │

│ if((fd = open("/dev/tty",O_RDONLY│O_NDELAY)) == -1) │

│ exit(); │

│ │

│ n = 1; │

│ for(;;) /* всегда */ │

│ { │

│ for(i = 0; i < n; i++) │

│ ; │

│ │

│ if(read(fd,buf,sizeof(buf)) > 0) │

│ { │

│ printf("чтение с номера %d\n",n); │

│ n--; │

│ } │

│ else │

│ /* ничего не прочитано; возврат вследствие "no delay" */ │

│ n++; │

│ } │

│ } │

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


Рисунок 10.18. Опрос терминала


10.3.4 Опрос терминала


Иногда удобно производить опрос устройства, то есть считывать

с него данные, если они есть, или продолжать выполнять обычную

работу - в противном случае. Программа на Рисунке 10.18 иллюстри-

рует этот случай: после открытия терминала с параметром "no

delay" (без задержки) процессы, ведущие чтение с него, не приос-

тановят свое выполнение в случае отсутствия данных, а вернут уп-

равление немедленно (см. алгоритм terminal_read, Рисунок 10.15).

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

ройств: он может открыть каждое устройство с параметром "no

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

каждого. Однако, этот метод растрачивает вычислительные мощности

системы.

В системе BSD есть системная функция select, позволяющая про-

изводить опрос устройства. Синтаксис вызова этой функции:

select(nfds,rfds,wfds,efds,timeout)

где nfds - количество выбираемых дескрипторов файлов, а rfds,

wfds и efds указывают на двоичные маски, которыми "выбирают"

дескрипторы открытых файлов. То есть, бит 1 << fd (сдвиг на 1

разряд влево значения дескриптора файла) соответствует установке

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

файла. Параметр timeout (тайм-аут) указывает, на какое время сле-

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

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

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

двоичных масках, какие дескрипторы были выбраны. Например, если

пользователь пожелал приостановиться до момента получения данных

по дескрипторам 0, 1 или 2, параметр rfds укажет на двоичную мас-

ку 7; когда select возвратит управление, двоичная маска будет за-

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

товые данные. Двоичная маска wfds выполняет похожую функцию в от-

ношении записи дескрипторов, а двоичная маска efds указывает на

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

дескрипторами, что бывает полезно при работе в сети.


10.3.5 Назначение операторского терминала


Операторский терминал - это терминал, с которого пользователь

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

пользователем с терминала. Когда процесс открывает терминал,

драйвер терминала открывает строковый интерфейс. Если процесс

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

функции setpgrp и если процесс не связан с одним из операторских

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

раторским. Он сохраняет старший и младший номера устройства для

файла терминала в адресном пространстве, выделенном процессу, а

номер группы процессов, связанной с открываемым процессом, в

структуре данных терминального драйвера. Открываемый процесс ста-

новится управляющим процессом, обычно входным (начальным) команд-

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

Операторский терминал играет важную роль в обработке сигна-

лов. Когда пользователь нажимает клавиши "delete" (удаления),

"break" (прерывания), стирания или выхода, программа обработки

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

ветствующий сигнал всем процессам в группе. Подобно этому, когда

пользователь "зависает", программа обработки прерываний от терми-

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

интерфейс посылает соответствующий сигнал всем процессам в груп-

пе. Таким образом, все процессы, запущенные с конкретного терми-

нала, получают сигнал о "зависании"; реакцией по умолчанию для

большинства процессов будет выход из программы по получении сиг-

нала; это похоже на то, как при завершении работы пользователя с

терминалом из системы удаляются побочные процессы. После посылки

сигнала о "зависании" программа обработки прерываний от терминала

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

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

ле.


10.3.6 Драйвер косвенного терминала


Зачастую процессам необходимо прочитать ил записать данные

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

вывод могут быть переназначены в другие файлы. Например, shell

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

несмотря на то, что его стандартный файл вывода и стандартный

файл ошибок, возможно, переназначены в другое место. В версиях

системы UNIX поддерживается "косвенный" доступ к терминалу через

файл устройства "/dev/tty", в котором для каждого процесса опре-

делен управляющий (операторский) терминал. Пользователи, прошед-

шие регистрацию на отдельных терминалах, могут обращаться к файлу

"/dev/tty", но они получат доступ к разным терминалам.

Существует два основных способа поиска ядром операторского

терминала по имени файла "/dev/tty". Во-первых, ядро может специ-

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

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

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

минала получает старший и младший номера операторского терминала

из адресного пространства, выделенного процессу, и запускает

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

ройств посимвольного ввода-вывода. Второй способ, обычно исполь-

зуемый для поиска операторского терминала по имени "/dev/tty",

связан с проверкой соответствия старшего номера устройства номеру

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

типом данного драйвера. В случае совпадения номеров освобождается

индекс файла "/dev/tty", выделяется индекс операторскому термина-

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

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

дура open, принадлежащая терминальному драйверу. Дескриптор фай-

ла, возвращенный после открытия файла "/dev/tty", указывает не-

посредственно на операторский терминал и его драйвер.


10.3.7 Вход в систему


Как показано в главе 7, процесс начальной загрузки, имеющий

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

"/etc/inittab" инструкций о том, что нужно делать, если загружае-

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

зовательская". В многопользовательском режиме самой первой обя-

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

пользователям возможности регистрироваться в системе с терминалов

(Рисунок 10.19). Он порождает процессы, именуемые getty-процесса-

ми (от "get tty" - получить терминал), и следит за тем, какой из

процессов открывает какой терминал; каждый getty-процесс устанав-

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

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

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

шина не получит аппаратную связь с терминалом. Когда функция open

возвращает управление, getty-процесс исполняет программу login

(регистрации в системе), которая требует от пользователей, чтобы

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

роля. Если пользователь зарегистрировался успешно, программа

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

приступает к работе. Этот вызов shell'а именуется "login shell"

(регистрационный shell, регистрационный интерпретатор команд).

Процесс, связанный с shell'ом, имеет тот же идентификатор, что и

начальный getty-процесс, поэтому login shell является процессом,

возглавляющим группу процессов. Если пользователь не смог успешно

зарегистрироваться, программа регистрации завершается через опре-

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

нию, а процесс начальной загрузки порождает для этой линии следу-

ющий getty-процесс. Процесс начальной загрузки делает паузу до

получения сигнала об окончании порожденного ранее процесса. После

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

ние процесс регистрационным shell'ом и если это так, порождает

еще один getty-процесс, открывающий терминал, вместо прекративше-

го существование.


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

│ алгоритм login /* процедура регистрации */ │

│ { │

│ исполняется getty-процесс: │

│ установить группу процессов (вызов функции setpgrp); │

│ открыть терминальную линию; /* приостанов до завершения│

│ открытия */ │

│ если (открытие завершилось успешно) │

│ { │

│ исполнить программу регистрации: │

│ запросить имя пользователя; │

│ отключить эхо-сопровождение, запросить пароль; │

│ если (регистрация прошла успешно) │

│ /* найден соответствующий пароль в /etc/passwd */ │

│ { │

│ перевести терминал в канонический режим (ioctl);│

│ исполнить shell; │

│ } │

│ в противном случае │

│ считать количество попыток регистрации, пытаться│

│ зарегистрироваться снова до достижения опреде- │

│ ленной точки; │

│ } │

│ } │

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


Рисунок 10.19. Алгоритм регистрации


10.4 ПОТОКИ


Схема реализации драйверов устройств, хотя и отвечает зало-

женным требованиям, страдает некоторыми недостатками, которые с

годами стали заметнее. Разные драйверы имеют тенденцию дублиро-

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

вые протоколы и которые обычно включают в себя секцию управления

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

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

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