Архитектура операционной системы UNIX - Морис Бах
Шрифт:
Интервал:
Закладка:
Одновременному чтению с терминала несколькими процессами присуща неоднозначность, но ядро справляется с ситуацией наилучшим образом. С другой стороны, ядро обязано позволять процессам одновременно считывать данные с терминала, иначе порожденные командным процессором shell процессы, читающие из стандартного ввода, никогда не будут работать, поскольку shell тоже обращается к стандартному вводу. Короче говоря, процессы должны синхронизировать свои обращения к терминалу на пользовательском уровне.
Когда пользователь вводит символ "конец файла" (Ctrl-d в ASCII), строковый интерфейс передает функции read введенную строку до символа конца файла, но не включая его. Он не передает данные (код возврата 0) функции read, если в символьном списке встретился только символ "конец файла"; вызывающий процесс сам распознает, что обнаружен конец файла и больше не следует считывать данные с терминала. Если еще раз обратиться к примерам программ по shell'у, приведенным в главе 7, можно отметить, что цикл работы shell'а завершается, когда пользователь нажимает ‹Ctrl-d›: функция read возвращает 0 и производится выход из shell'а.
В этом разделе рассмотрена работа терминалов ввода-вывода, которые передают данные на машину по одному символу за одну операцию, в точности как пользователь их вводит с клавиатуры. Интеллектуальные терминалы подготавливают свой вводной поток на внешнем устройстве, освобождая центральный процессор для другой работы. Структура драйверов для таких терминалов походит на структуру драйверов для терминалов ввода-вывода, несмотря на то, что функции строкового интерфейса различаются в зависимости от возможностей внешних устройств.
10.3.3 Терминальный драйвер в режиме без обработки символов
Пользователи устанавливают параметры терминала, такие как символы стирания и удаления, и извлекают значения текущих установок с помощью системной функции ioctl. Сходным образом они устанавливают необходимость эхо-сопровождения ввода данных с терминала, задают скорость передачи информации в бодах, заполняют очереди символов ввода и вывода или вручную запускают и останавливают выводной поток символов. В информационной структуре терминального драйвера хранятся различные управляющие установки (см. [SVID 85], стр.281), и строковый интерфейс получает параметры функции ioctl и устанавливает или считывает значения соответствующих полей структуры данных. Когда процесс устанавливает значения параметров терминала, он делает это для всех процессов, использующих терминал. Установки терминала не сбрасываются автоматически при выходе из процесса, сделавшего изменения в установках.
Процессы могут также перевести терминал в режим без обработки символов, в котором строковый интерфейс передает символы в точном соответствии с тем, как пользователь ввел их: обработка вводного потока полностью отсутствует. Однако, ядро должно знать, когда выполнить вызванную пользователем системную функцию read, поскольку символ возврата каретки трактуется как обычный введенный символ. Оно выполняет функцию read после того, как с терминала будет введено минимальное число символов или по прохождении фиксированного промежутка времени от момента получения с терминала любого набора символов. В последнем случае ядро хронометрирует ввод символов с терминала, помещая записи в таблицу ответных сигналов (глава 8). Оба критерия (минимальное число символов и фиксированный промежуток времени) задаются в вызове функции ioctl. Когда соответствующие критерии удовлетворены, программа обработки прерываний строкового интерфейса возобновляет выполнение всех приостановленных процессов. Драйвер пересылает все символы из списка для хранения неструктурированных вводных данных в канонический список и выполняет запрос процесса на чтение, следуя тому же самому алгоритму, что и в случае работы в каноническом режиме. Режим без обработки символов особенно важен в экранно-ориентированных приложениях, таких как экранный редактор vi, многие из команд которого не заканчиваются символом возврата каретки. Например, команда dw удаляет слово в текущей позиции курсора.
На Рисунке 10.17 приведена программа, использующая функцию ioctl для сохранения текущих установок терминала для файла с дескриптором 0, что соответствует значению дескриптора файла стандартного ввода. Функция ioctl с командой TCGETA приказывает драйверу извлечь установки и сохранить их в структуре с именем savetty в адресном пространстве задачи. Эта команда часто используется для того, чтобы определить, является ли файл терминалом или нет, поскольку она ничего не изменяет в системе: если она завершается неудачно, процессы предполагают, что файл не является терминалом. Здесь же, процесс вторично вызывает функцию ioctl для того, чтобы перевести терминал в режим без обработки: он отключает эхо-сопровождение ввода символов и готовится к выполнению операций чтения с терминала по получении с терминала 5 символов, как минимум, или по прохождении 10 секунд с момента ввода первой порции символов. Когда процесс получает сигнал о прерывании, он сбрасывает первоначальные параметры терминала и завершается.
#include ‹signal.h›
#include ‹termio.h›
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();
}
Рисунок 10.17. Режим без обработки — чтение 5-символьных блоков
10.3.4 Опрос терминала
Иногда удобно производить опрос устройства, то есть считывать с него данные, если они есть, или продолжать выполнять обычную работу — в противном случае. Программа на Рисунке 10.18 иллюстрирует этот случай: после открытия терминала с параметром "no delay" (без задержки) процессы, ведущие чтение с него, не приостановят свое выполнение в случае отсутствия данных, а вернут управление немедленно (см. алгоритм terminal_read, Рисунок 10.15). Этот метод работает также, если процесс следит за множеством устройств: он может открыть каждое устройство с параметром "no delay" и опросить всех из них, ожидая поступления информации с каждого. Однако, этот метод растрачивает вычислительные мощности системы.
#include ‹fcntl.h›
main() {
register int i, n;
int fd;
char buf[256];
/* открытие терминала только для чтения с опцией "no delay" */
if ((fd = open("/dev/tty", O_RDONLYO_NDELAY)) == -1) exit();
n = 1;
for(;;) { /* всегда */
for (i = 0; i ‹ n; i++);
if (read(fd, buf, sizeof(buf)) › 0) {
printf("чтение с номера %dn", n);
n--;
}
else n++; /* ничего не прочитано; возврат вследствие "no delay" */
}
}
Рисунок 10.18. Опрос терминала
В системе 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 указывает на существование исключительных условий, связанных с конкретными дескрипторами, что бывает полезно при работе в сети.