Литмир - Электронная Библиотека
Содержание  
A
A

алгоритм terminal_read

{

 if (в каноническом символьном списке отсутствуют данные) 
{

  do while (в списке для неструктурированных вводных данных отсутствует информация) 
{

   if (терминал открыт с параметром "no delay" (без задержки))
 return;

   if (терминал в режиме без обработки с использованием таймера и таймер не активен)

    предпринять действия к активизации таймера (таблица ответных сигналов);

   sleep (до поступления данных с терминала);

  }

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

  if (терминал в режиме без обработки)

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

  else { /* терминал в каноническом режиме */

   do while (в списке для неструктурированных вводных данных есть символы)
{

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

    if (символ — "возврат каретки" или "конец файла") break
; /* выход из цикла */

   }

  }

 }

 do while(в каноническом списке еще есть символы и не исчерпано количество символов, указанное в вызове функции read)

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

}

Рисунок 10.15. Алгоритм чтения с терминала

Обработка символов в направлении ввода и в направлении вывода асимметрична, что видно из наличия двух символьных списков для ввода и одного — для вывода. Строковый интерфейс выводит данные из пространства задачи, обрабатывает их и помещает их в список для хранения выводных данных. Для симметрии следовало бы иметь только один список для вводных данных. Однако, в таком случае потребовалось бы использование программы обработки прерываний для интерпретации символов стирания и удаления, что сделало бы процедуру более сложной и длительной и запретило бы возникновение других прерываний на все критическое время. Использование двух символьных списков для ввода подразумевает, что программа обработки прерываний может просто сбросить символы в список для неструктурированных вводных данных и возобновить выполнение процесса, осуществляющего чтение, который собственно и возьмет на себя работу по интерпретации вводных данных. При этом программа обработки прерываний немедленно помещает введенные символы в список для хранения выводных данных, так что пользователь испытывает лишь минимальную задержку при просмотре введенных символов на терминале.

char input[256];

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);

   }

  }

 }

}

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

На Рисунке 10.16 приведена программа, в которой родительский процесс порождает несколько процессов, осуществляющих чтение из файла стандартного ввода, конкурируя за получение данных, вводимых с терминала. Ввод с терминала обычно осуществляется слишком медленно для того, чтобы удовлетворить все процессы, ведущие чтение, поэтому процессы большую часть времени находятся в приостановленном состоянии в соответствии с алгоритмом terminal_read, ожидая ввода данных. Когда пользователь вводит строку данных, программа обработки прерываний от терминала возобновляет выполнение всех процессов, ведущих чтение; поскольку они были приостановлены с одним и тем же уровнем приоритета, они выбираются для запуска с одинаковым уровнем приоритета. Пользователь не в состоянии предугадать, какой из процессов выполняется и считывает строку данных; успешно созданный процесс печатает значение переменной i в момент его создания. Все другие процессы в конце концов будут запущены, но вполне возможно, что они не обнаружат введенной информации в списках для хранения вводных данных и их выполнение снова будет приостановлено. Вся процедура повторяется для каждой введенной строки; нельзя дать гарантию, что ни один из процессов не захватит все введенные данные.

Одновременному чтению с терминала несколькими процессами присуща неоднозначность, но ядро справляется с ситуацией наилучшим образом. С другой стороны, ядро обязано позволять процессам одновременно считывать данные с терминала, иначе порожденные командным процессором shell процессы, читающие из стандартного ввода, никогда не будут работать, поскольку shell тоже обращается к стандартному вводу. Короче говоря, процессы должны синхронизировать свои обращения к терминалу на пользовательском уровне.

Когда пользователь вводит символ "конец файла" (Ctrl-d в ASCII), строковый интерфейс передает функции read введенную строку до символа конца файла, но не включая его. Он не передает данные (код возврата 0) функции read, если в символьном списке встретился только символ "конец файла"; вызывающий процесс сам распознает, что обнаружен конец файла и больше не следует считывать данные с терминала. Если еще раз обратиться к примерам программ по shell'у, приведенным в главе 7, можно отметить, что цикл работы shell'а завершается, когда пользователь нажимает ‹Ctrl-d›: функция read возвращает 0 и производится выход из shell'а.

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

111
{"b":"96903","o":1}