учебники, программирование, основы, введение в,

 

Общий терминальный интерфейс

Основные понятия и объекты
В стандарте POSIX-2001 терминал или терминальное устройство определяется как символьный специальный файл, удовлетворяющий спецификациям общего терминального интерфейса.
Наряду с физическими устройствами в стандарте рассматриваются псевдотерминалы - сущности, поддерживающие интерфейс, идентичный терминальному. Псевдотерминал состоит из двух "устройств": главного и подчиненного. Подчиненное "устройство" предоставляет процессам терминальный интерфейс, не опирающийся на прямую аппаратную поддержку. Данные, которые записываются на главное устройство, становятся входными для подчиненного и наоборот.
Обычно терминальное устройство работает в полнодуплексном режиме, когда ввод и вывод могут совмещаться во времени.
С каждым терминальным устройством ассоциирована очередь ввода, куда система помещает входные данные до того, как их прочитают прикладные процессы. На размер этой очереди (в байтах) может быть наложено ограничение {MAX_INPUT}. Поддерживается также очередь вывода, где хранятся записанные прикладными процессами, но еще не выведенные на терминал символы.
Ввод может происходить в каноническом и неканоническом режимах.   Канонический режим означает построчную буферизацию ввода системой, т. е. запрос на чтение из прикладной программы будет удовлетворен лишь после того, как с клавиатуры поступит символ перевода строки или конца файла, а прочитает программа заведомо не больше одной строки, независимо от того, сколько байт она запросила. На размер строки может быть наложено ограничение {MAX_CANON}. Канонический режим подразумевает также естественную обработку системой символов забоя и уничтожения строки.
(Отметим, что в каноническом режиме приложение не обязано сразу прочитать всю буферизованную строку. Можно запросить любое количество байт (даже один), и данные не будут потеряны.)
В неканоническом режиме входные данные не подвергаются препроцессированию системой, а обработка запроса на чтение зависит от двух параметров - MIN и TIME. Запрос на чтение не будет удовлетворен, пока не поступит по крайней мере MIN байт или не истечет время задержки TIME (время задается в десятых долях секунды). Нулевое значение TIME трактуется как бесконечная задержка.
Более точно, если MIN > 0, TIME трактуется как задержка между поступлениями байт; следовательно, отсчет времени начинается после прихода очередного байта. Если MIN = 0, TIME означает общее время обслуживания запроса на чтение. Такой подход позволяет эффективно читать во время вспышек активности ввода и не препятствует побайтному вводу.
Помимо режима, канонического или нет, на передачу данных читающему процессу оказывает влияние флаг O_NONBLOCK, устанавливаемый функциями open() или fcntl(), а на обработку входных символов - режимы ввода и локальные режимы. Подобная обработка может включать, например, эхоотображение вводимых символов.
Подвергаются обработке системой (в соответствии с режимами вывода) и выводимые прикладной программой символы (в частности, они могут буферизоваться).
Ряд управляющих символов играет специальную роль при вводе и/или выводе. Кратко опишем эти функции, не уточняя их соответствия с нажатиями на клавиатуре, поскольку оно зависит от реализации. Отметим, что за некоторыми очевидными исключениями (например, перевод строки), специальные управляющие символы не передаются читающему процессу.
INTR
Генерирует сигнал прерывания (SIGINT), посылаемый всем процессам, для которых данный терминал является управляющим.
QUIT
Генерирует сигнал выхода.
ERASE
При каноническом режиме ввода устраняет предыдущий символ, но не далее начала строки.
KILL
При каноническом режиме ввода уничтожает всю строку.
EOF
При каноническом режиме ввода при получении этого символа все буферизованные байты передаются процессу, а сам символ EOF отбрасывается. Таким образом, если буферизованных байт нет, т. е. EOF встретился в начале строки, в процесс будет передано нуль байт, что и является стандартным обозначением конца файла.
NL
Стандартный разделитель строк (перевод строки) при каноническом режиме ввода. Его нельзя изменить.
EOL
Дополнительный разделитель строк, аналогичный NL, при каноническом режиме ввода. Обычно не используется.
SUSP
Генерирует сигнал остановки.
STOP
Специальный символ как при вводе, так и при выводе, распознаваемый в случае наличия флагов IXON (управление выводом) или IXOFF (управление вводом). Обычно используется для временной приостановки вывода, когда нужно прочитать текст на экране терминала.
START
Употребляется для возобновления вывода, приостановленного с помощью символа STOP.
CR
При каноническом режиме ввода и выполнении некоторых дополнительных условий - эквивалент перевода строки.
http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifЦентральную роль в управлении терминалами играет структура termios, определенная во включаемом файле <termios.h>. Она должна содержать по крайней мере следующие поля.
tcflag_t c_iflag;     /* Режимы ввода */
tcflag_t c_oflag;     /* Режимы вывода */
tcflag_t c_cflag;     /* Управляющие режимы */
tcflag_t c_lflag;     /* Локальные режимы */
cc_t     c_cc [NCCS]; /* Специальные управ-
ляющие символы */
Типы tcflag_t, cc_t и фигурирующий далее speed_t должны определяться реализацией посредством typedef как беззнаковые целые.
Обращение к элементам массива c_cc, хранящего специальные управляющие символы, которые могут быть изменены, выполняется с помощью индексов с именами, полученными вставкой буквы V перед названием символа: VEOF, VEOL, VERASE, VINTR, VKILL, VQUIT, VSTART, VSTOP, VSUSP. Кроме того, еще два индекса, VMIN и VTIME, используются для работы со значениями MIN и TIME и могут совпадать с VEOF и VEOL, соответственно (поскольку символы EOF и EOL нужны только в каноническом режиме, а значения MIN и TIME - только в неканоническом).
Поле c_iflag структуры termios описывает основные параметры терминального ввода.
BRKINT
При разрыве соединения (когда в течение времени, превышающего длительность передачи байта, поступают нулевые биты) генерировать сигнал прерывания и сбрасывать очереди ввода/вывода.
ICRNL
Преобразовывать возврат каретки в перевод строки.
IGNBRK
Игнорировать разрыв соединения.
IGNCR
Игнорировать возврат каретки.
IGNPAR
Игнорировать символы с ошибками четности.
INLCR
Преобразовывать перевод строки в возврат каретки.
INPCK
Разрешить контроль четности.
ISTRIP
Отбрасывать старший бит, обрезая байты до семи бит.
IXOFF
Разрешить старт/стопное управление вводом.
IXON
Разрешить старт/стопное управление выводом.
PARMRK
Отмечать ошибки четности.
Поле c_oflag определяет системную обработку вывода. К числу обязательных для поддержки стандарт POSIX-2001 относит только один флаг - OPOST> (постпроцессировать вывод). В расширение XSI входят флаги, определяющие характер постпроцессирования: ONLCR (преобразовывать перевод строки в пару - перевод строки, возврат каретки), OCRNL (преобразовывать возврат каретки в перевод строки), NLDLY (выбрать задержку для перевода строки) и т.п.
Поле управляющих режимов c_cflag описывает аппаратные характеристики линии и терминала: размер символа в битах (CSIZE: от CS5 - 5 бит до CS8 - 8 бит), число стоп-бит (CSTOPB: два стоп-бита), освобождение линии при закрытии последнего файлового дескриптора, ассоциированного с терминалом (HUPCL), контроль четности (PARENB: контроль включен; PARODD: проверка на нечетность) и т.п.
Стандарт осторожен в части представления скорости передачи. Оговаривается только, что скорость хранится в структуре termios как значение типа speed_t, но не утверждается, что она представлена как часть поля c_cflag (хотя в исторически сложившихся реализациях это так). Допустимые значения скорости задаются именованными константами: от B0 - нулевой, означающей разрыв соединения, до B38400 - 38400 бит/сек.
Поле локальных режимов c_lflag структуры termios используется для управления различными характеристиками терминала. В их число входят:
ECHO
Включить эхоотображение.
ECHOE
Отображать символ забоя как тройку (возврат на шаг, пробел, возврат на шаг).
ECHOK
Отображать символ уничтожения строки (по крайней мере путем выдачи перевода строки).
ECHONL
Включить эхоотображение перевода строки.
ICANON
Канонический режим ввода.
ISIG
Разрешить сигналы. Если установлен флаг ISIG, то каждый вводимый символ сравнивается со специальными управляющими символами INTR, QUIT и SUSP. В случае совпадения выполняется ассоциированная функция.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifСлужебные программы и функции для управления терминалами

Служебная программа
tty
позволяет узнать имя пользовательского терминала. Более точно, она выдает на стандартный вывод имя терминала, открытого в качестве стандартного ввода.
Если для примера перенаправить стандартный ввод, можно получить результат, показанный в.
tty > /tmp/tty.res
tty < /tmp/tty.res > /tmp/tty.res
Листинг 9.1. Пример использования служебной программы tty.
/dev/ttyS4
not a tty
Листинг 9.2. Возможный результат использования служебной программы tty.
Узнать, ассоциирован ли открытый файловый дескриптор с терминальным устройством, а также получить имя этого устройства можно с помощью функций isatty() и ttyname().
#include <unistd.h>
int isatty (int fildes);
char *ttyname (int fildes);
Листинг 9.3. Описание функций isatty() и ttyname().
Если с дескриптором ассоциирован терминал, функция isatty() возвращает единицу, а ttyname() - указатель на цепочку символов (располагающуюся, быть может, в перезаписываемой каждым вызовом статической области). В противном случае возвращаются, соответственно, нуль и пустой указатель.
Каждый терминал обладает рядом характеристик, которые можно опросить и/или изменить. Для этого служит утилита stty:
stty  [ -a | -g]
stty  характеристика ...
Будучи вызванной без опций и операндов, она выдает значения основных характеристик терминала, ассоциированного со стандартным вводом. Смысл опций таков:
-a
Выдать значение всех установленных характеристик.
-g
Выдать текущие установки в формате, который может быть использован в качестве аргумента другой команды stty.
В частности, выдача команды
stty -a
может выглядеть так, как показано в. Здесь присутствуют как стандартные, так и специфичные для ОС Linux характеристики (минус перед именем характеристики означает, разумеется, что соответствующий флаг не установлен). Например, для опрашиваемого терминала задан канонический режим ввода и постпроцессирование вывода, но отсутствует контроль четности. Можно видеть также, какие символы назначены на роли специальных управляющих.
Листинг 9.4. Возможный результат команды stty -a.
Чтобы переустановить какую-либо характеристику, следует указать ее имя и, если нужно, новое значение. Например, после выполнения команды
stty kill '^k'
для отмены строки придется нажимать CTRL+K. Вслед за выполнением команды
stty -echo
на экране перестанут отображаться символы, вводимые пользователем, что, правда, не повлияет на выдачу результатов работы команд. Подобный режим применяется для обеспечения секретности, например, во время ввода пароля. Для восстановления эхоотображения советуем воспользоваться командой
stty echo
Сохранение и восстановление характеристик терминала можно реализовать так, как показано в.
saved="$(stty -g)"
stty  новые_характеристики
. . .
stty $saved
Листинг 9.5. Пример сохранения и восстановления характеристик терминала.
http://localhost:3232/img/empty.gifНекоторые, хотя и весьма ограниченные, возможности управления терминалами предоставляет служебная программа tput:
tput  [-T тип_терминала] действие
Стандарт POSIX-2001 предусматривает всего три действия: clear (очистка экрана), init (инициализация терминала), reset (переустановка терминала). Действие осуществляется путем посылки на терминал соответствующей управляющей последовательности. Стандарт не специфицирует, как эта последовательность определяется, каковы все результаты ее посылки и чем инициализация отличается от переустановки.
Служебная программа tput дает хорошие примеры немобильности. Во-первых, она входит в необязательную часть стандарта POSIX-2001. Во-вторых, в общем случае управляющие действия с терминалом могут реализовываться не только путем посылки на него некоторой последовательности символов, но и путем обращения к системным вызовам, поэтому присваивание
ClearScreen=`tput clear`
может и не достичь цели (tput ничего не выдаст). В-третьих, стандартизованный перечень действий слишком ограничен, так что реально можно рекомендовать лишь включение строки вида
tput init
в разного рода инициализационные файлы. Тем не менее, показанный в фрагмент командного файла, выполняющегося при загрузке ОС Linux, демонстрирует поучительные способы борьбы с немобильностью.
if [ -x /usr/bin/tput ]; then
if [ "x`tput kbs`" != "x" ]; then
stty erase `tput kbs`
elif [ -x /usr/bin/wc ]; then
if [ "`tput kbs | wc -c `" -gt 0 ]; then
stty erase `tput kbs`
fi
fi
fi
Листинг 9.6. Пример совместного использования утилит stty и tput.
Здесь задается управляющая последовательность для выполнения функции забоя. Впрочем, несмотря на все проверки, немобильность все-таки осталась, поскольку действие kbs не входит в число стандартных.
На уровне функций опрос и изменение характеристик терминала разбиты на два семейства: tc*() и cf*() (см. которые в стандарте POSIX введены в качестве замены исторически сложившейся, универсальной, но чрезмерно эклектичной, не вписывающейся ни в какие синтаксические рамки функции ioctl().
Листинг 9.7. Описание функций семейства tc*().
Листинг 9.8. Описание функций семейства cf*().
Пара функций tcgetattr()/tcsetattr() позволяет стандартным образом опросить/изменить характеристики терминала, ассоциированного с открытым файловым дескриптором fildes. Значения характеристик помещаются в структуру, на которую указывает аргумент termios_p. Нюансами изменения ведает аргумент optional_actions. Если его значение равно TCSANOW, изменение выполняется немедленно. Значение TCSADRAIN предписывает сначала дождаться передачи на терминальное устройство всех записанных по дескриптору fildes данных; это полезно, если изменения затрагивают режимы вывода. Значение TCSAFLUSH дополнительно означает предварительный сброс введенных устройством, но не прочитанных прикладными процессами символов.
Отметим, что вызов tcsetattr() завершается успешно (и возвращает нулевое значение), если удалось осуществить хотя бы одно из запрошенных изменений характеристик терминала. Это сделано по соображениям мобильности, поскольку разные реализации могут поддерживать разные подмножества характеристик. После tcsetattr() целесообразно снова вызвать tcgetattr() и узнать реальное состояние терминального устройства.
Приложение, изменяющее характеристики терминала, обязано позаботиться об их последующем восстановлении (например, при завершении процесса). Завершение должно выполняться аккуратно, с обработкой сигналов.
Функция tcflow() позволяет приостановить или возобновить терминальный ввод/вывод. Если значение аргумента action равно TCOOFF, приостанавливается вывод; по значению TCOON он возобновляется. Значение TCIOFF вызывает передачу на терминал символа STOP, значение TCION - символа START, что оказывает соответствующее воздействие на ввод.
С помощью функции tcflush() можно сбросить очередь ввода (значение TCIFLUSH аргумента queue_selector), вывода (TCOFLUSH) или совместить их (значение TCIOFLUSH).
Функция tcdrain() действует мягче. Она блокирует вызывающий процесс, пока все данные, записанные по дескриптору fildes, не будут переданы на терминальное устройство, дожидаясь тем самым физического окончания вывода.
Функция tcsendbreak() носит технический характер, позволяя организовать разрыв соединения путем посылки на терминал нулевых бит в течение заданного интервала времени. Если значение аргумента duration равно нулю, посылка продолжается от четверти до половины секунды.
Поскольку стандарт POSIX-2001 не специфицирует способ представления в структуре termios данных о скорости передачи, для выборки/записи данной характеристики из/в termios потребовалось ввести семейство функций cf*(), что, конечно, хорошо, ибо это проявление (возможно, невольное) объектно-ориентированного подхода. Кроме того, в общем случае терминальное устройство может поддерживать для ввода и вывода разные скорости, поэтому определено четыре функции, названия которых говорят сами за себя. Разумеется, для реального опроса/изменения скорости обмена с терминалом нужно воспользоваться функциями tcgetattr()/tcsetattr(). Отметим также, что скорость должна задаваться не в виде целого числа, а с помощью определенных в заголовочном файле <termios.h> констант B0, ..., B38400.
http://localhost:3232/img/empty.gifПри работе с (псевдо)терминалами (а также с каналами и сокетами) полезна функция poll(), входящая в расширение XSI и позволяющая мультиплексировать ввод/вывод в пределах набора файловых дескрипторов.
#include <poll.h>
int poll (struct pollfd fds [],
nfds_t nfds, int timeout);
Листинг 9.9. Описание функции poll().
Для каждого элемента массива fds (с числом элементов nfds) функция проверяет, наступили ли заданные события; в частности, возможны ли чтение или запись без блокирования процесса.
Согласно стандарту, структура pollfd содержит по крайней мере следующие поля.
int   fd;      
/* Опрашиваемый файловый дескриптор */
short events;  
/* Флаги опрашиваемых событий */
short revents; 
/* Флаги произошедших событий */
Среди событий, обрабатываемых функцией poll(), выделим POLLIN (информирует о том, что можно без блокирования прочитать данные, не являющиеся высокоприоритетными), POLLRDNORM (можно прочитать обычные данные), POLLOUT (без блокирования можно записать обычные данные), POLLERR (выходной флаг - зафиксирована ошибка устройства или потока).
Разделение данных на обычные, приоритетные и высокоприоритетные зависит от реализации.
Если запрашиваемые события не наступают, poll() ждет по крайней мере timeout миллисекунд. Значение -1 задает бесконечную задержку.
В качестве примера описанных функций приведем упрощенную версию программы, запускающей командный интерпретатор на псевдотерминале. Программа использует библиотеку curses для высокоуровневой работы с терминалами, которую мы не описываем, однако понять смысл фигурирующих в ней функций довольно просто. Отметим лишь, что функция endwin() восстанавливает характеристики терминала.
Листинг 9.10. Пример программы, использующей псевдотерминалы.
В соответствии с определением, выводимые на псевдотерминал символы являются входными для порожденного процесса. Они читаются по дескриптору pty, а затем интерпретируются и выводятся на физический терминал. Обратим также внимание на манипуляции с флагом ISIG: сигналы разрешены только для псевдотерминала (но не для стандартного ввода).
В целях полноты изложения укажем, что в (частично) описанное выше семейство tc*() входят еще три функции, предназначенные для работы с управляющими терминалами.
#include <unistd.h>
pid_t tcgetpgrp (int fildes);

#include <unistd.h>
int tcsetpgrp (int fildes, pid_t pgid_id);

#include <termios.h>
pid_t tcgetsid (int fildes);
Листинг 9.11. Описание функций семейства tc*() для работы с управляющими терминалами.
Функция tcgetpgrp() позволяет узнать идентификатор группы процессов переднего плана, ассоциированной с терминалом, заданным открытым файловым дескриптором fildes. Для установки идентификатора группы служит функция tcsetpgrp(). Требуется, чтобы аргумент fildes соответствовал управляющему терминалу вызывающего процесса. Функция tcgetsid(), входящая в расширение XSI, по сути аналогична tcgetpgrp(). Она позволяет узнать идентификатор группы процессов лидера сеанса, для которого терминал, ассоциированный с файловым дескриптором fildes, является управляющим.
Для выполнения в известном смысле обратной операции - получения маршрутного имени управляющего терминала для вызывающего процесса - служит функция ctermid().
#include <stdio.h>
char *ctermid (char *s);
Листинг 9.12. Описание функции ctermid().
Если значение аргумента s отлично от NULL, маршрутное имя управляющего терминала записывается по этому адресу; предполагается, что массив s имеет длину не менее L_ctermid байт. В противном случае имя помещается в область статической памяти, которая перезаписывается каждым вызовом.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика