ГЛАВА 7. Устройства и файловые системы

Терминальные устройства
Работа с клавиатурой
Файлы termcap
Дисковые устройства
Файловые системы
Загрузочный диск
Размерные параметры

c Быстрая очистка экрана
mntf Монтирование гибкого диска в системном дереве
mntlook Поиск всех монтированных файловых систем
umntsys Размонтирование всех файловых систем, кроме корневой
lrgf Создание файла максимального размера, допустимого в вашей системе

УСТРОЙСТВА И ФАЙЛОВЫЕ СИСТЕМЫ

ВВЕДЕНИЕ

Ниже уровня известной нам области файловых систем находится мир устройств и их драйверов. В данной главе мы исследуем некоторые методы, необходимые для работы с терминалами, дисками и непосредственно файловыми системами. Программное средство 'c' иллюстрирует доступ к терминалу на примере операции быстрой очистки экрана. Следующие три средства - mntf, mntlook и umntsys - имеют дело с монтированием и размонтированием файловых систем. Наконец, средство lrgf позволит вам проверить пределы емкости вашей файловой системы.

СИСТЕМА UNIX И АППАРАТУРА

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

Операционная система, особенно такая высокоразвитая, как UNIX, имеет множество утилит и характерных особенностей, но сейчас речь не об этом. Сердцем операционной системы (в данном случае UNIX) является ядро (kernel). Ядро управляет процессами и руководит выполняемой работой. Оно также является своего рода мостом между аппаратурой и внешним миром. В данной главе мы обратим внимание на основные взаимоотношения между ядром, процессами и аппаратурой.

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

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

К нашему счастью, UNIX был разработан так, чтобы облегчить управление данными и устройствами настолько, насколько это возможно. К нашему несчастью, имеется, по всей видимости, несократимый объем знаний, которыми мы должны овладеть обязательно. На рис. 7-1 показана общая структура операционной системы UNIX. Мы видим, что со стороны ядра обращение ко всем внешним периферийным устройствам выполняется как к файлам устройств. Каждый тип устройств имеет свой собственный драйвер и специфическую архитектуру, но обращение к каждому устройству выполняется одинаковыми методами. Мы увидим, как использовать различные способы доступа к устройствам и определим, какие способы наиболее эффективны.

Рисунок 7-1
Модель среды системы UNIX

UNIX обращается к периферийным устройствам через "специальные файлы". Имеется два типа специальных файлов: блочные и символьные. Оба типа имеют свое предназначение и особенности. Блочный (например, /dev/hd0) использует буферизацию и позволяет получить доступ к большим объемам данных на жестком диске. Символьный (например, /dev/tty00 или /dev/rfd0) не использует значительную буферизацию, а выполняет обмен с устройством по одному символу за обращение. Даже несмотря на особые свойства таких файлов, для них поддерживается все тот же механизм защиты, что и для всех других файлов в системе.

Первая область, которую мы рассмотрим - терминальные устройства и работа с ними. Представленные программы включают в себя средство под названием 'c' для быстрой очистки экрана, а также пример программы, которая считывает значения нажатых клавиш и выполняет опрос нажатия одной клавиши. Мы также рассмотрим пример файла описания терминала (termcap), который обеспечивает доступные определения характеристик терминала.

Затем мы рассмотрим дисковые устройства - жесткие и гибкие диски.

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

В дополнение к работе с устройствами мы рассмотрим файловые системы на жестком диске. Всем нам известно, что система UNIX существенно ориентирована на диски, поэтому чем больше мы знаем о файловых системах, тем лучше для нас. Для более полного понимания разделов диска и файловых систем мы представим три программных средства. Средство lrgf проверяет граничные значения параметров файловой системы путем создания файла наибольшего возможного размера в вашей системе. Средство mntf обеспечивает удобный способ монтирования и размонтирования гибких дисков. Наконец, средство mntlook выполняет поиск немонтированных файловых систем, которые представляют собой потенциальную опасность.

ТЕРМИНАЛЬНЫЕ УСТРОЙСТВА

Драйверы терминальных устройств являются одними из самых сложных драйверов устройств. Причина этого заключается в том, что существует множество уровней программного обеспечения, которые поддерживают характеристики интерактивных терминалов. При работе терминала по последовательной линии связи необходима мощная поддержка для того, чтобы облегчить его использование. Различные установки, которые может иметь терминал, программируются командами stty(1) и ioctl(2). Команда termio(7) также описывает различные аспекты протокола работы терминала.

ПРОТОКОЛ ОПЕРАЦИЙ ВВОДА/ВЫВОДА ТЕРМИНАЛА

Протокол работы терминала представляет собой согласованный набор сигналов, позволяющих системе правильно интерпретировать вводимые с клавиатуры строки. Протокол необходим по четырем причинам. Первой является поддержка входной обработки специальных символов, таких как символы удаления и прекращения работы программы. Во-вторых, нам необходимо поддерживать обработку выводимой информации, например, вставку символов задержки или изменение последовательности возврат каретки/перевод строки. Третьей причиной является поддержка режимов необработанного и "канонического" ввода. Эти два режима позволяют пользовательским программам получать данные или по одному символу, или по одной строке. Последняя причина введения терминального протокола - желание сделать так, чтобы пользователь мог сам изменять параметры конфигурации терминала.

Содержимое терминальной подсистемы показано на рис. 7-2. Рисунок разбит на три части: слева - область пользователя, посредине - область ядра и справа - область устройства. Этот рисунок показывает, как передаются данные между терминалами и программами пользователя.

Рисунок 7-2.
Управление протоколом терминала

Когда какой-либо процесс читает символы с устройства, данные начинают двигаться от буфера драйвера устройства, который называется dbuf. Из этого буфера данные попадают в приемный буфер, управляемый ядром. Приемный буфер читается подпрограммой ядра с именем ttin(), и данные помещаются в структуру clist, называемую необработанной очередью. (Слово "необработанная" означает, что над символами пока что не производилось никакой обработки.) В то же время символы также помещаются в выходную очередь, что позволяет системе выполнять эхо-отображение символов по мере их ввода.

После этого подпрограмма canon() превращает необработанную очередь в каноническую. ("Каноническая" означает применение правил, которые в данный момент установлены системой для выполнения специальных функций над строкой текста, например для обработки символа удаления.) Такая обработка позволяет преобразовать данные перед тем, как их получит процесс пользователя. Последней подпрограммой является ttread(), которая читает символы из канонического буфера в буфер пользовательского процесса в области данных процесса.

Когда символы записываются из процесса пользователя на терминал, они проделывают почти такой же маршрут в обратном направлении: от области процесса к области драйвера устройства. Основное отличие в подпрограмме записи заключается в том, что эти данные проходят на один буфер меньше. От процесса пользователя символы передаются в выходную очередь ядра подпрограммой ttwrite(), а затем в буфер передачи с помощью подпрограммы ttout(). Из буфера передачи они пересылаются непосредственно в приемный буфер драйвера устройства с помощью подпрограммы dzxint().

ОПРЕДЕЛЕНИЕ ВВОДИМЫХ СИМВОЛОВ

Бывает так, что мы хотим увидеть, какие символы вводятся с клавиатуры. Мы могли бы для этого написать программу, но UNIX предоставляет встроенную возможность для этой цели.

Это команда od - восьмеричный дамп (octal dump). Такое название осталось еще с тех давних времен, когда восьмеричное исчисление широко применялось при отладке. К счастью, результат работы команды od можно получить в символьном, шестнадцатиричном или десятичном виде. Фокус использования команды od для проверки входных и выходных значений заключается в том, что od читает стандартное устройство ввода по умолчанию, если не указан файл. Например, вызов

  $ od -cx
  test string
  ^d
  ^d

даст такой результат:

 |
 | 0000000 6574 7473 7320 7274 6e69 0a67
 |    t e s t  s t r i n g 

 | 0000014
 | $
 |

Здесь вызов команды od делается без указания имени файла в командной строке и с применением стандартного вывода в качестве выводного устройства. Мы используем опцию -cx для того, чтобы байты интерпретировались как символы ASCII, а соответствующие 16-битовые слова отображались в шестнадцатиричном виде. По мере того, как вы набираете символы, они отображаются на экране, а команда od сохраняет их в своем буфере. В конце строки нажмите возврат каретки, затем CTRL-D. Ввод CTRL-D завершает чтение символов командой od и выдает распечатку, в которой сверху будут шестнадцатиричные значения, а снизу символы в коде ASCII.

Обратите внимание, что два символа, выводимые для каждого шестнадцатиричного слова, располагаются в обратном порядке по сравнению с двумя байтами, образующими это слово. Например, слово 6574 интерпретируется как два символа, t и e, где 65 - код ASCII для символа e, а 74 - ASCII-код для символа t. Для того чтобы выйти из команды od, введите еще один CTRL-D для прекращения блочного чтения. Если вы хотите еще проверять символы, продолжайте их вводить. Команда od работает несколько загадочно. Если вы введете достаточное количество символов, она выдаст на экран информацию по нажатию только лишь возврата каретки. Но если вы ввели всего несколько символов, требуется нажатие как возврата каретки, ТАК И CTRL-D для получения результата на экране.

Теперь мы можем сделать один фокус - изменить канонический способ обработки при чтении символов командой od. Это позволит нам увидеть эффект от различных установок протокола работы. Для этого проверьте текущие установки вашего терминала. В версии System V используйте команду "stty -a", а в версии Berkeley вам нужно применить команду "stty everything". System V выдает гораздо больше параметров, чем Berkeley. (Наиболее популярные версии UNIX'а разработаны и поддерживаются следующими фирмами: System V - фирмой AT&T Bell Laboratories, которая в настоящее время называется Unix System Laboratories; BSD (Berkeley Software Distribution) - Калифорнийским университетом в Беркли; XENIX - фирмой Microsoft.- Прим. перев.) Ниже приводится пример из XENIX:

 |
 | speed 9600 baud; line = 0;
 | intr = DEL; quit = ^|; erase = ^h;
 | kill = ^u; eof = ^d; eol = ^`
 | parenb -parodd cs7 -cstobp hupcl cread -clocal
 | -ignbrk brkint ignpar -parmrk -inpck istrip -inlcr -igncr icrnl -iuclc
 | ixon ixany -ixoff
 | isig icanon -xcase echo echoe echok -echonl -noflsh
 | opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel ff1
 |

После того, как мы почешем голову, мы увидим, что текущий флаг для канонической обработки установлен в состояние "icanon". То есть, мы можем видеть, что он включен, поскольку нет префикса в виде знака минус (хотя это не является строгим правилом).

Что происходит при канонической обработке? Символ возврата на шаг назад (backspace) является одним из важных вопросов. Когда вы вводите символ CTRL-H, он поступает в необработанную очередь как литеральный символ CTRL-H. Когда программа canon() читает CTRL-H, она понимает это так: "Изменить CTRL-H на символ возврата на шаг назад, записать пробел на место символа, затем сделать еще шаг назад." При эхо -отображении вы получаете удаление символа на экране. Когда каноническая обработка отключена, вы посылаете CTRL-H как обычные символы. Вот пример того, как это выглядит:

 |
 | $ stty -icanon   Отключение канонической обработки
 | $ od -cx
 | test string^h^h^h^h^h^hcase
 | ^d...
 |
 | 0000000 6574 7473 7320 7274 6e69 0867 0808 0808
 | t e s t  s t r i n g     
 | 0000020 6308 7361 0a65 0a04 0a0a 0a0a 0a0a 0a0a
 |  c a s e 
 004 
 
 
 
 
 
 
 
 

 |

После слова "string" вы видите группу чисел 08, которые в ASCII-коде обозначают CTRL-H. Эти числа 08 показывают вам, что литеральные символы CTRL-H действительно присутствуют в их "необработанной" форме. Поскольку CTRL-H не является больше специальным символом, команда od рассматривает его подобно любому другому символу. Здесь возникает новая проблема: поскольку специальные символы не распознаются, мы потеряли возможность завершить блочное чтение вводом символа конца файла (EOF). Когда вводится CTRL-D, он понимается просто как еще один символ. Поэтому мы должны заполнить буфер команды od, чтобы заставить ее выполнить дамп. В нашем примере CTRL-D - это символ 004 после символов case .

Кстати, в системе Berkeley используются установки "обработанная" ("cooked") и "необработанная" ("raw") для stty, которые по существу служат для тех же целей, что "canon" и "-canon" в System V.

ДИНАМИЧЕСКОЕ ПЕРЕОПРЕДЕЛЕНИЕ СИМВОЛОВ ПРЕРЫВАНИЯ

Обратите внимание, что в предыдущей распечатке команды stty -a присутствуют определения символов "intr" и "quit". Это две функции, которые прерывают работу ваших работающих процессов. Строки intr и quit представляют собой особые функции, а не просто нажатие клавиши. Эти функции можно назначить на любую клавишу клавиатуры при помощи команды stty.

Если мы изменили значение "intr" на другой символ, то этот новый символ прервет ваши процессы. Вы даже можете установить в качестве клавиши прерывания обычный символ. Вот как это можно сделать:

  $ stty intr x
  $ this is a junk stringx
  $

Когда вы вводите символ x в конце строки, то вся введенная строка обрывается и вы получаете новый символ приглашения. Для того чтобы вернуться к обычному режиму работы, введите в качестве символа прерывания старый символ. Если старым символом был "delete", вы даете такую команду:

  $ stty intr DEL

Что же в этом хорошего? Это показывает, насколько гибко работает команда stty с терминалом, и может быть использовано в качестве личной меры безопасности, чтобы сбить с толку человека, который захотел бы произвести какой-либо беспорядок с вашего терминала. Когда вам нужно на минуту отойти от терминала, измените ключ прерывания на какой-либо другой и запустите командный файл вроде такого:

  while :
  do
  :
  done

Этот бесконечный цикл будет выполняться постоянно. Если кто-нибудь попытается прервать этот процесс, используя клавишу DEL, ничего не произойдет. Когда вы вернетесь, нажмите новую клавишу прерывания. Она прервет цикл, и вы получите символ приглашения.

Очень важным является также символ "eof". Он соответствует концу файла (end of file), и обычно им является CTRL-D. Это объясняет, почему вы выходите из системы UNIX с помощью CTRL-D. Когда вы посылаете CTRL-D вашему регистрационному shell, вы тем самым говорите ему: "Shell, это метка конца файла для данного сеанса работы". Поскольку терминал рассматривается как файл, CTRL-D закрывает файл, а shell, который работает как цикл вида "читать команды с клавиатуры, пока не встретится EOF", завершается, тем самым посылая сигнал программе init. Программа init порождает команду getty по данной терминальной линии, которая выводит на экран приглашение "login:" для ожидания команды от следующего пользователя. Если вы измените символ конца файла, вы больше не сможете выйти из shell по CTRL-D. Он будет выводиться на экран точно так же, как любой другой символ. Вот пример таких действий:

  $ stty eof x
  $ x
  login:

Теперь признаком конца файла является обычный символ x. Когда вы вводите символ x, это равносильно вводу CTRL-D, и вы выходите из системы. Очевидно, такие манипуляции нежелательны, однако это позволяет показать ту большую степень гибкости, которую использует UNIX при присвоении различных функций разным символам.

ФАЙЛЫ ТЕРМИНАЛЬНЫХ УСТРОЙСТВ

Физически адресация терминалов производится посредством файлов устройств в каталоге /dev. Когда вы регистрируетесь в системе, вам присваивается определенный номер терминала, например tty01. Этот номер терминала в действительности является файлом /dev/tty01. Если вы вызовете команду tty UNIX, она выведет полное маршрутное имя файла того терминального устройства, за которым вы работаете.

Файлы терминальных устройств выглядят так же, как обычные файлы, за исключением того, что команда "ls -l" показывает, как называются старший и младший номера устройства, которые не являются частью обычных файлов. Старший номер является индексом в таблице cdevsw[], которая содержит адрес драйвера устройства, используемого ядром для данного типа устройства. Младший номер идентифицирует конкретное физическое устройство. Эти номера появляются в последовательном порядке для всех устройств, использующих один и тот же драйвер. Так выглядит типичный список файлов устройств в системе XENIX:

 |
 | crw--w--w- 1 russ tricks 0, 0 Jun 22 02:34 /dev/console
 | crw--w--w- 1 russ tricks 0, 1 Jun 22 00:41 /dev/tty02
 | crw--w--w- 1 root tricks 0, 2 Jun 21 17:56 /dev/tty03
 | crw--w--w- 1 root tricks 0, 3 Jun 21 05:47 /dev/tty04
 | crw-rw-rw- 1 root root 0, 4 Feb 18 17:09 /dev/tty05
 | crw-rw-rw- 1 root root 0, 5 Feb 18 17:09 /dev/tty06
 | crw-rw-rw- 2 root root 5, 0 Jun 21 20:23 /dev/tty11
 | crw--w--w- 2 root tricks 5, 8 Jun 22 02:20 /dev/tty12
 | crw-rw-rw- 2 root root 5,128 Feb 18 17:09 /dev/tty13
 | crw-rw-rw- 2 root root 5,136 Feb 18 17:09 /dev/tty14
 |

По символу 'c' в первом столбце мы видим, что это символьные устройства, а биты прав доступа показывают, кто имеет доступ к этим файлам. Первый столбец чисел (0 или 5) является старшим номером. Младшие номера в следующем столбце обычно следуют в последовательном порядке, но не всегда (как видно в данном примере).

В дополнение к использованию абсолютного номера вашего терминала, одно из устройств используется в качестве "логического", или "родового" адреса вашего терминала. Оно использует другой драйвер устройства, называется /dev/tty и применяется в случаях, когда стандартный ввод и стандартный вывод переадресовываются в другие файлы, а прикладной программе необходимо читать с клавиатуры или писать на экран. При помощи доступа к файлу /dev/tty образуется связь с самим терминалом. Выбор использования устройства с именем tty вместо устройства tty01 главным образом зависит от того, какая стоит задача. Если вам необходимо иметь независимую от типа терминала программу, используйте /dev/tty.

ПРАВА ДОСТУПА К ТЕРМИНАЛУ

Поскольку терминальное устройство является файлом, оно имеет режимы прав доступа точно так же, как и все другие файлы. Эти режимы представляют собой защиту доступа к вашему терминалу. Если все пользователи имеют право записи на ваш терминал (это обозначается как rw--w--w-), то они могут записать на ваш экран все, что угодно, и вы никогда не узнаете, кто это сделал. Если вы хотите предупредить это, то выполните команду "chmod 600 `tty`", где символы ударения и обозначение tty соответствуют маршрутному имени вашего терминального файла. Более простой в использовании является команда UNIX'а mesg. Команда "mesg n" запрещает запись извне на ваш терминал. Ваши собственные процессы по-прежнему имеют доступ на запись.

Права доступа к терминалу связаны также с проблемой безопасности, которую мы рассмотрим в главе 9. Пока что отметим, что всякий раз, когда вы открываете файл (то ли для чтения, то ли для записи), вам возвращается дескриптор файла. Затем вы можете использовать этот дескриптор файла в системном вызове ioctl. Получение этого дескриптора файла подобно получению ключа к терминальному интерфейсу определенного пользователя. Любые изменения, производимые с помощью ioctl с данным дескриптором файла, вызывают немедленный эффект, и нарушитель защиты может читать все, что записывается или считывается с вашего терминала или даже заставить ваш терминал выполнять команды, которые присваивают себе неразрешенные права! Пользователь, который работает с этим терминалом, может никогда не узнать, что же произошло или кто это сделал.

Другим примером подобного рода является команда write(1). Она используется для установки связи по линии в реальном режиме времени, или "болтовни". Она общается с терминалом путем выполнения записи в файл устройства. Измените биты прав доступа, выключив их командой "mesg n", и никто не сможет выполнить команду write с вашим терминалом. Таким способом вы можете "снять трубку телефона", когда вы хотите, чтобы вам не мешали. Вместе с тем кто-нибудь мог бы сделать такое:

  $ while :
  > do
  >   clear > /dev/tty00
  > done &

При этом создается фоновый бесконечный процесс (пока он не будет прекращен командой kill или выходом из системы), который посылает пользователю терминала tty00 символы очистки экрана. Как только этот пользователь что-нибудь набирает на экране, он тут же очищается. Большинство пользователей не могут даже понять, что происходит. Если это случится с вами, попытайтесь отключить права доступа к вашему терминалу. Если же тот, кто это делает, является суперпользователем (root), то никакие права доступа не смогут остановить его, поскольку суперпользователь не имеет ограничений по правам доступа к файлам. В этом случае возникает проблема для системного администратора!

Еще одна странная вещь наблюдается в случае, когда кто-либо посылает что-то на ваш терминал командой write, а вы отключаете права доступа. Команда write по-прежнему имеет доступ к вашему терминалу, пока она не закроет устройство. После закрытия она больше не имеет прав для открытия устройства. Представляется странным факт, что после получения доступа к терминалу последующее запрещение права доступа не оказывает никакого воздействия до тех пор, пока вы не прекратите работу с терминалом.

ОТМЕТКИ О ВРЕМЕНИ ДОСТУПА К ТЕРМИНАЛУ

Еще одним атрибутом терминалов, который вызван тем, что терминалы - это файлы, являются даты модификации, доступа и обновления. Каждый файл в системе имеет эти три даты, которые содержатся в его индексном дескрипторе (в секундах) в виде ДЛИННЫХ (long) чисел.

Эти даты могут содержать некоторую интересную информацию. Как было отмечено при описании командного файла activ в предыдущей главе, дата модификации представляет собой последний момент времени, когда пользователь что-то набирал на своей клавиатуре. Другие даты тоже кое-что означают, но они не так часто используются.

ОБРАБОТКА ТЕРМИНАЛОМ ВВОДИМОЙ ИНФОРМАЦИИ

Как уже обсуждалось ранее, по умолчанию драйвер терминала работает в каноническом режиме, т.е. в режиме построчной обработки. Когда вы вводите символы, драйвер ожидает, пока вы нажмете возврат каретки, после чего передает для обработки всю строку. Если вы работаете не в каноническом режиме, то каждый символ передается для обработки непосредственно после ввода. Наглядным примером такого режима работы является редактор vi. Вы нажимаете по одной клавише для движения курсора, удаления символов и т.д., поэтому редактор vi, очевидно, должен получать каждый символ сразу же, как только нажата клавиша.

Каким образом это делается в программе? Прием старый и часто используется в UNIX, хотя и не очень хорошо описан в документации. Такого рода информацию можно добыть путем просмотра большого количества текстов программ. Необходимо отметить, что этот прием лучше всего работает в программах на языке Си. Командные файлы, написанные на языке shell, могут использовать для этой цели команду stty, но результат будет не один и тот же. Следующий фрагмент программы на языке Си отключает каноническую обработку, затем читает символы и выводит их на экран.

  1 #include
  3 struct termio tsav, tchg;
  5 main (argc, argv)
  6 {
  7   int c;
  9   if (ioctl (0, TCGETA, &tsav) == -1) {
  10     perror("can't get original settings");
       невозможно получить исходные установки
  11     exit(1);
  12   }
  14   tchg = tsav;
  16   tchg.c_lflag &= ~(ICANON | ECHO);
  17   tchg.c_cc[VMIN] = 1;
  18   tchg.c_cc[VTIME] = 0;
  20   if (ioctl (0, TCSETA, &tchg) == -1) {
  21     perror("can't initiate new settings");
       невозможно задать новые установки
  22   }
  24   while (1)
  25   {
  26     c = getchar();
  28     if (c == 'x')
  29       break;
  31     putchar(c);
  32   }
  34   if (ioctl(0, TCSETA, &tsav) == -1) {
  35     perror("can't reset original settings");
       невозможно вернуть исходные установки
  36     exit(3);
  37   }
  38 }

У нас есть две "терминальные" структуры данных, одна из которых содержит исходные установки, а другая - установки, которые мы изменяем и записываем. Первый системный вызов ioctl получает информацию об установках терминала. Затем мы присваиваем эти значения изменяемой структуре (строка 14). Модификации терминального интерфейса мы выполняем в строках 16-18. Строка 16 отключает каноническую обработку и эхо-отображение символов. Строка 17 устанавливает, что минимальное количество нажатий на клавиши равно одному. Строка 18 определяет, что время ожидания для повторного чтения данных равно 0. По существу, это блочное чтение по одному символу.

Новые значения терминальных характеристик устанавливаются в строке 20. В этот момент режим работы терминала меняется. Цикл while читает, проверяет и выводит символы. Только при вводе символа x цикл завершается, терминал устанавливается в первоначальное состояние, и программа заканчивает работу.

Как мы уже заметили, операция чтения здесь является блочной. Это значит, что программа ожидает, пока вы введете символ. Если вы ничего не вводите, программа находится в бесконечном цикле ожидания. Каким образом мы изменяем режим чтения с блочного на посимвольный?

Этот вопрос эквивалентен такому вопросу: "Как опросить клавиатуру в UNIX?". Опрос является весьма важным приемом для некоторых применений. Опрос работает примерно так: "Посмотреть на клавиатуру. Если введен символ, получить его и каким-то образом обработать. В противном случае продолжать делать то, что вы делали. После истечения интервала времени, определенного программой, проверить клавиатуру снова." Таким образом, если пользователь не нажимает на клавиши, программа продолжает работу, а не ожидает, пока что-нибудь будет нажато на клавиатуре.

Для выполнения такой работы нам нужно несколько более подробно рассмотреть терминальный интерфейс. Как было отмечено ранее, терминал представляет собой файл. Это значит, что он должен обладать всеми обычными свойствами файлов - возможностью открытия, закрытия, чтения, записи и т.д. Мы также видели, что терминалы имеют протокол работы, который может быть изменен командой stty. Мы видели, что для получения одного символа с клавиатуры используется протокол работы. Теперь мы увидим, что для выполнения опроса вы должны использовать технику, которая относится к файлам, а не ioctl.

Секрет здесь в том, чтобы открыть терминальный файл, изменяя режим его открытия. Затем для получения одного символа используется тот же текст, что и в предыдущем случае - тем самым опрос достигнут. Вот текст программы:

  1 #include
  2 #include
  4 struct termio tsav, tchg;
  6 main (argc, argv)
  7 {
  8  int c;
  10  /* change the terminal based on file primitives */
   изменить режим терминала с помощью файловых примитивов
  11  close(0);
  12  if (open("/dev/tty",O_RDWR|O_NDELAY) == -1) {
  13   perror("can't open tty");
     невозможно открыть tty
  14   exit(1);
  15  }
  17  /* change the terminal based on line disciplines */
   изменить режим терминала с помощью протокола работы
  18  if (ioctl (0, TCGETA, &tsav) == -1) {
  19   perror("can't get original settings");
     невозможно получить исходные установки
  20   exit(2);
  21  }
  23  tchg = tsav;
  25  tchg.c_lflag &= ~(ICANON | ECHO);
  26  tchg.c_cc[VMIN] = 1;
  27  tchg.c_cc[VTIME] = 0;
  29  if (ioctl (0, TCSETA, &tchg) == -1) {
  30   perror(can't initiate new settings");
     невозможно задать новые установки
  31  }
  33  while (1)
  34  {
  35   putchar('.');
  36   c = getchar();
  38   if (c == 'x')
  39    break;
  41   putchar(c);
  42  }
  44  if (ioctl(0, TCSETA, &tsav) == -1) {
  45   perror("can't reset original settings");
     невозможно вернуть исходные установки
  46   exit(3);
  47  }
  48 }

Основное изменение производится в строках 11-15. Закрытие файла с нулевым дескриптором (который обозначает стандартное устройство ввода) закрывает стандартный ввод. Затем мы снова открываем файл /dev/tty. Значение дескриптора файла равно нулю, так что мы переназначили стандартный ввод на новое устройство. Фокус в том, что при открытии файла используется режим, называемый NODELAY. Это означает, что когда выполняется чтение по данному дескриптору файла (т.е. чтение стандартного ввода), вместо ожидания ввода выполняется просмотр, есть ли там что-нибудь, а затем работа продолжается.

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

ВОЗМОЖНОСТИ ТЕРМИНАЛОВ

Теперь, когда мы имеем понятие о характеристиках терминальных интерфейсов, давайте перейдем к возможностям терминалов. ВОЗМОЖНОСТИ это те функции, которые выполняет аппаратура терминала. Если мы знаем эту информацию, мы можем создать список возможных функций и использовать его, например, для работы редактора vi. Это осуществляется при помощи специального файла данных termcap (terminal capabilities - возможности терминала), который описывает возможности терминала.

Большинство из существующих типов терминалов уже занесены в файл termcap. Это файл /etc/termcap. Файл termcap и редактор vi происходят из системы Berkeley. Такая комбинация оказалась настолько эффективной, что была перенесена в System V. В более поздней System V Release 3 файл termcap уже не используется, его заменяет файл terminfo фирмы AT&T. Мы применяли файл terminfo совместно с командным файлом today в главе 5, но подробное обсуждение terminfo выходит за пределы нашей книги. В системе Berkeley файл termcap по-прежнему остается стандартом, и он заслуживает более детального рассмотрения.

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

В качестве примера мы приводим запись файла termcap для компьютера Apple II. Это описание распространено в различных формах, но наш пример относится к видеоплате Videx UltraTerm для Apple II+. Заметим, что возможности, предоставляемые файлом termcap, являются обычно подмножеством тех возможностей, которые фактически предоставляет аппаратура. В частности, видеоплата в компьютере Apple выполняет некоторые функции, которые не умеет делать файл termcap, например комбинации настроечных битов для изменения видеоатрибутов. Самое большее, что мы можем сделать с видеоатрибутами посредством файла termcap, это включить или выключить инверсное отображение.

С другой стороны, некоторые типы аппаратуры не обладают всеми возможностями, обеспечиваемыми файлом termcap. Например, одной из функций, которой недостает в Apple, является функция прокрутки ("scroll reverse"). Аппаратура не делает этого, поэтому и в termcap нет необходимости иметь описание этой функции. Вместо скроллинга (прокрутки) вниз, отображаемый на экране текст продолжает выводиться в верхней строке.

Для того, чтобы получить представление о том, как termcap соотносит общие характеристики терминала с конкретными возможностями, сравним терминалы Apple и vt52. Две соответствующие записи в termcap имеют много похожих функций, но совершенно разные коды для их выполнения. Приведем пример содержимого файла termcap:

  a2|aii|Apple II with UltraTerm :
  :bl=^G:
  :bs:
  :cd=^K:
  :ce=^]:
  :cl=^L:
  :cm=^^%r%+ %+ :
  :co#80:
  :cr=^M:
  :do=^J:
  :ho=^Y:
  :kb=^H:
  :kd=^J:
  :kl=^H:
  :kr=^\:
  :ku=^_:
  :le=^H:
  :li#24:
  :nd=^\:
  :nl=^J:
  :se=^O:
  :so=^N:
  :up=^_:

В табл. 7-1 представлен список функций файла termcap с сопоставлением терминалов Apple и vt52. Если какая-либо функция отсутствует у одного или другого терминала, это отмечается словом "нет".

Таблица 7-1
Терминальные возможности и их конкретные значения

Функция

Apple II

vt52

bl - звуковой сигнал (bell)

^G

^G

bs - возврат на шаг по коду ^H (can backspace with ^H)

да

да

cd - очистка до конца экрана (clear to end of display)

^K

EJ

ce - очистка до конца строки (clear to end of line)

^]

EK

cl - очистка всего экрана (clear entire screen)

^L

EHEJ

cm - движение курсора (cursor motion)

^^%r%+ %+

EY%+ %+

co - число позиций в строке (number of columns in a line)

#80

#80

cr - возврат каретки (carriage return)

^M

^M

do - сдвиг на строку вниз (down one line)

^J

^J

ho - курсор в начало экрана(без команды cm) (home cursor)

^Y

EH

kb - код клавиши backspace (sent by backspace key)

^H

^H

kd - код клавиши "стрелка вниз" (sent by down arrow key)

^J

EB

kl - код клавиши "стрелка влево" (sent by left arrow key)

^H

ED

kr - код клавиши "стрелка вправо" (sent by right arrow key)

^\

EC

ku - код клавиши "стрелка вверх" (sent by up arrow key)

^_

EA

le - курсор влево (cursor left)

^H

^H

li - число строк экрана (number of lines per screen)

#24

#24

nd - нестирающий пробел (nondestructive space)

^\

EC

nl - символ перевода строки (newline character)

^J

^J

pt - наличие аппаратной табуляции (has hardware tabs)

нет

да

se - обычный экран (end stand out mode (normal))

^O

нет

so - инверсный экран (begin stand out mode (inverse))

^N

нет

sr - прокрутка (scroll reverse)

нет

EI

ta - символ табуляции (tab)

^I

^I

up - сдвиг вверх на строку(up a line)

нет

^_

Самое интересное здесь, наверное, то, что терминалы vt52 и Apple имеют взаимно обратный порядок указания координат в команде движения курсора. Терминал vt52 воспринимает значения x и y в порядке YX, что является умолчанием для файла termcap. Apple воспринимает их в порядке XY, поэтому в записи файла termcap требуется поменять координаты местами, что указано обозначением %r в функции cm.

Файл termcap позволяет вам спрятать основную информацию о специфических характеристиках терминала (за исключением характеристик, которые могут отсутствовать у терминала, или специальных возможностей, которые не описаны в termcap). Это значит, что вы можете создавать терминально-независимые программы. При этом вам нет необходимости изменять все специфические обращения к терминалу, такие как ESC-последовательности (символы, указывающие терминалу, что передаваемые после них символы (символ) должны интерпретироваться как управляющие коды). Это символы (E) для терминала vt52 и (^) для Apple.

Наилучший пример - способ использования файла termcap редактором vi. Он начинает выполнять указанную ему функцию, например движение курсора, после чего ставит вопрос: "Какой код функции, которую мы хотим выполнить?". Затем он ищет соответствующую последовательность в той информации, которую предоставляет termcap.

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

Обновлено: 12.03.2015