Терминальный интерфейс POSIX — это обобщенная абстракция, включающая как интерфейс прикладного программирования для программ, так и набор поведенческих ожиданий для пользователей терминала , как определено стандартом POSIX и Single Unix Specification . Это историческое развитие терминальных интерфейсов BSD версии 4 и Seventh Edition Unix .
Множество устройств ввода-вывода рассматриваются как «терминалы» в системах Unix. [1] [2] К ним относятся:
В отличие от своих современников — мэйнфреймов и мини-компьютеров [ требуется ссылка ] — оригинальная система Unix была разработана исключительно для немых терминалов, и это остается таковым и по сей день. [6] Терминал — это символьно-ориентированное устройство, включающее потоки символов, получаемых с устройства и отправляемых на него. [6] [7] Хотя потоки символов структурированы, включая управляющие символы , управляющие коды и специальные символы, протокол ввода-вывода не структурирован так, как протокол ввода-вывода интеллектуальных терминалов . Спецификации формата полей отсутствуют. Блоковая передача целых экранов (форм ввода) входных данных отсутствует.
Напротив, мэйнфреймы и мини-компьютеры в закрытых архитектурах обычно используют блочно-ориентированные терминалы .
«Возможности» терминала включают в себя различные функции немого терминала, которые выходят за рамки того, что доступно чистому телетайпу, которые могут использовать программы. Они (в основном) включают в себя управляющие коды, которые могут быть отправлены или получены с терминала. Управляющие коды, отправленные на терминал, выполняют различные функции, которые терминал с ЭЛТ (или программный эмулятор терминала) может выполнять, но не может телетайп, такие как перемещение курсора терминала в позиции на экране, очистка и прокрутка всего или части экрана, включение и выключение подключенных устройств принтера, программируемые функциональные клавиши, изменение цветов и атрибутов дисплея (например, обратное видео ) и настройка строк заголовков дисплея. Управляющие коды, полученные с терминала, обозначают такие вещи, как функциональная клавиша , клавиша со стрелкой и другие специальные нажатия клавиш ( клавиша «Домой» , клавиша «Завершить» , клавиша «Справка » , клавиша «PgUp» , клавиша «PgDn» , клавиша «Вставить » , клавиша «Удалить » и т. д.). [8] [9]
Эти возможности закодированы в базах данных, которые настраиваются системным администратором и доступны из программ через библиотеку terminfo (которая заменяет старую библиотеку termcap ), на которой, в свою очередь, построены библиотеки, такие как библиотеки curses и ncurses . Прикладные программы используют возможности терминала для предоставления текстовых пользовательских интерфейсов с окнами, диалоговыми окнами, кнопками, метками, полями ввода, меню и т. д. [10] [11]
TERM
и др.Конкретный набор возможностей для терминала, который использует ввод и вывод (терминально-ориентированной) программы, получается из базы данных, а не жестко зашит в программы и библиотеки, и управляется TERM
переменной окружения (и, опционально для библиотек termcap и terminfo, переменными окружения TERMCAP
и TERMINFO
соответственно). [10] Эта переменная устанавливается любой программой монитора терминала, которая порождает программы, которые затем используют этот терминал для ввода и вывода, или иногда явно. Например:
TERM
переменную среды в соответствии с системной базой данных ( inittab или файлы конфигурации для программ ttymon или launchd ), определяя, какие локальные терминалы подключены к каким последовательным портам и какие типы терминалов предоставляются локальными виртуальными терминалами или локальной системной консолью.TERM
переменную окружения сразу после входа в систему на правильный тип. (Чаще всего тип терминала, установленный программой getty для коммутируемой линии, который системный администратор определил как наиболее часто используемый пользователями коммутируемого доступа с удаленными терминалами, совпадает с типом, используемым пользователем коммутируемого доступа, и этому пользователю нет необходимости переопределять тип терминала.)TERM
переменную среды на тот же тип терминала, что и клиент SSH. [12]TERM
переменную окружения для указания типа эмулируемого им терминала. Эмулируемые терминалы часто не полностью соответствуют реальному оборудованию терминала, а эмуляторы терминалов имеют имена типов, предназначенные для их использования. xterm
Например, программа xterm (по умолчанию) устанавливает тип терминала. [13] Программа GNU Screen устанавливает screen
тип терминала.Терминалы предоставляют средства управления заданиями. В интерактивном режиме пользователь на терминале может отправлять управляющие символы, которые приостанавливают текущую работу, возвращаясь к интерактивной оболочке управления заданиями, которая породила задание, и может запускать команды, которые помещают задания в «фоновый» режим или переключают другое фоновое задание на передний план (отменяя его при необходимости). [14] [15]
Строго говоря, в Unix-системах терминальное устройство включает в себя базовый драйвер устройства tty , отвечающий за физическое управление оборудованием устройства с помощью инструкций ввода-вывода и обработку запросов прерывания устройства для ввода и вывода символов, а также дисциплину линии . Дисциплина линии не зависит от фактического оборудования устройства, и та же дисциплина линии может использоваться для терминального концентратора , отвечающего за несколько управляющих терминалов, как и для псевдотерминала. Фактически, дисциплина линии (или, в случае BSD, AIX и других систем, дисциплины линии ) одинаковы для всех терминальных устройств. Именно дисциплина линии отвечает за локальное эхо, редактирование строк, обработку режимов ввода, обработку режимов вывода и отображение символов. Все эти вещи не зависят от фактического оборудования, работая так, как они делают в простых абстракциях, предоставляемых драйверами устройств tty: передают символ, получают символ, устанавливают различные состояния оборудования. [16] [17]
В системах Seventh Edition Unix , BSD и производных, включая macOS и Linux , каждое терминальное устройство может переключаться между несколькими дисциплинами линий. [18] В системе AT&T STREAMS дисциплины линий представляют собой модули STREAMS, которые могут быть помещены в стек ввода-вывода STREAMS и извлечены из него. [19]
Терминальный интерфейс POSIX создан на основе терминальных интерфейсов различных систем Unix.
Терминальный интерфейс, предоставляемый Unix 32V и Seventh Edition Unix, а также представленный BSD версии 4 как старый драйвер терминала , был простым, в значительной степени ориентированным на телетайпы в качестве терминалов. Ввод осуществлялся построчно, при этом драйвер терминала в операционной системе (а не сами терминалы) предоставлял простые возможности редактирования строк. Ядро поддерживало буфер, в котором происходило редактирование. Приложения, считывающие ввод с терминала, получали содержимое буфера только при returnнажатии клавиши на терминале для завершения редактирования строки. Клавиша, отправленная с терминала в систему, стирала («убивала») все текущее содержимое буфера редактирования и обычно отображалась как символ « @ », за которым следовала последовательность новой строки для перемещения позиции печати на новую пустую строку. Ключ , отправленный с терминала в систему, стирает последний символ в конце буфера редактирования и обычно отображается как символ « # », который пользователи должны распознавать как «стирание» предыдущего символа (телетайпы физически не способны стирать символы после того, как они были напечатаны на бумаге). [20] [21] [22] [23] [18]@#
С точки зрения программирования, терминальное устройство имело скорости передачи и приема данных , символы «стирания» и «уничтожения» (которые выполняли редактирование строк, как объяснялось), символы «прерывания» и «выхода» (генерирующие сигналы для всех процессов, для которых терминал был управляющим терминалом), символы «старта» и «стопа» (используемые для управления потоком модема ), символ «конца файла» (действующий как возврат каретки, за исключением того, что он отбрасывается из буфера системным read()
вызовом и, следовательно, потенциально приводит к возврату результата нулевой длины) и различные флаги базового режима , определяющие, эмулируется ли локальное эхо драйвером терминала ядра, включено ли управление потоком модема, длины различных задержек вывода, отображение для символа возврата каретки и три режима ввода. [24]
Три режима ввода были следующими:
В линейном режиме дисциплина линии выполняет все функции редактирования строк и распознает управляющие символы «прерывание» и «выход» и преобразует их в сигналы, отправляемые процессам. Прикладные программы, считывающие данные с терминала, получают целые строки после того, как редактирование строк было завершено пользователем, нажавшим клавишу возврата. [21] [25]
Режим cbreak — один из двух режимов «символ за раз». ( Стивен Р. Борн в шутку назвал его (Bourne 1983, стр. 288) «полуготовым» и, следовательно, «редким» режимом.) Дисциплина строки не выполняет редактирование строк, а управляющие последовательности для функций редактирования строк обрабатываются как обычный ввод символов. Прикладные программы, считывающие с терминала, получают символы немедленно, как только они становятся доступны в очереди ввода для чтения. Однако управляющие символы «прерывание» и «выход», а также символы управления потоком модема по-прежнему обрабатываются особым образом и удаляются из входного потока. [26] [27]
Программным интерфейсом для запроса и изменения всех этих режимов и управляющих символов был ioctl()
системный вызов . (Он заменил системные вызовы stty()
и gtty()
шестого издания Unix.) [29] [30] Хотя символы «erase» и «kill» можно было изменять из их значений по умолчанию и , в течение многих лет они были предустановленными значениями по умолчанию в драйверах терминальных устройств и во многих системах Unix, которые изменяли только настройки терминального устройства как часть процесса входа в систему, в сценариях входа в систему, которые запускались после того, как пользователь вводил имя пользователя и пароль, любые ошибки в приглашениях на вход в систему и пароль приходилось исправлять с помощью исторических символов клавиш редактирования, унаследованных от терминалов телетайпа. [23]#@
С появлением BSD Unix появился контроль заданий и новый драйвер терминала с расширенными возможностями. [18] Эти расширения включали дополнительные (опять же программно-изменяемые) специальные символы:
SIGTSTP
Программный интерфейс для запроса и изменения всех этих дополнительных режимов и управляющих символов по-прежнему был ioctl()
системным вызовом, который его создатели (Leffler et al. 1989, p. 262) описали как «довольно загроможденный интерфейс». Вся первоначальная функциональность седьмого издания Unix была сохранена, а новая функциональность была добавлена через дополнительные ioctl()
коды операций, в результате чего программный интерфейс явно вырос и представлял собой некоторое дублирование функциональности. [31]
System III представила новый программный интерфейс, который объединил отдельные операции Seventh Edition ioctl()
для получения и установки флагов и для получения и установки управляющих символов в вызовы, которые использовали termio
структуру для хранения как флагов, так и управляющих символов, и которые могли получать их в одной операции и устанавливать их в другой одной операции. Он также разделил некоторые флаги из интерфейса Seventh Edition на несколько отдельных флагов и добавил некоторые дополнительные возможности, хотя он не поддерживал управление заданиями или улучшения приготовленного режима 4BSD. [32] Например, он заменил режимы "cooked", "cbreak" и "raw" Seventh Edition на другие абстракции. Распознавание генерирующих сигналы символов не зависит от режима ввода, и есть только два режима ввода: канонический и неканонический. (Это позволяет использовать терминальный режим ввода, отсутствующий в Seventh Edition и BSD: канонический режим с отключенной генерацией сигнала.)
Последователи System III, включая System V , использовали тот же интерфейс.
Одной из основных проблем, которую стандарт POSIX решил своим определением общего терминального интерфейса, было изобилие программных интерфейсов. Хотя ко времени стандарта поведение терминалов было довольно единообразным от системы к системе, большинство Unix-систем переняли понятия дисциплин линий и возможности управления заданиями BSD, программный интерфейс к терминалам через ioctl()
системный вызов был беспорядочным. Различные Unix-системы предоставляли разные ioctl()
операции с разными (символическими) именами и разными флагами. Переносимый исходный код должен был содержать значительный объем условной компиляции для учета различий между программными платформами, хотя все они были теоретически Unix. [33]
Стандарт POSIX ioctl()
полностью заменяет систему набором библиотечных функций (которые, конечно, могут быть реализованы под прикрытием посредством ioctl()
операций, специфичных для платформы) со стандартизированными именами и параметрами. termio
Структура данных System V Unix использовалась в качестве шаблона для termios
структуры данных POSIX, поля которой в значительной степени не изменились, за исключением того, что теперь они использовали псевдонимы типов данных для указания полей, что позволяет разработчикам легко переносить их на несколько архитектур процессоров, а не явно требовать типы данных unsigned short
и char
языков программирования C и C++ (которые могут иметь неудобные размеры на некоторых архитектурах процессоров). [33] [34]
В POSIX также появилась поддержка управления заданиями, при этом termios
структура содержала символы приостановки и отложенной приостановки в дополнение к управляющим символам, поддерживаемым System III и System V. В нее не было добавлено никаких расширений режима подготовки из BSD, хотя SunOS 4.x, System V Release 4 , Solaris , HP-UX , AIX , более новые BSD, macOS и Linux реализовали их как расширения для termios
.
Каждый процесс в системе имеет либо один управляющий терминал , либо вообще не имеет управляющего терминала. Процесс наследует свой управляющий терминал от своего родителя, и единственными операциями над процессом являются получение управляющего терминала процессом, у которого нет управляющего терминала, и освобождение его процессом, у которого есть управляющий терминал. [33]
Не определен переносимый способ получения управляющего терминала, метод определяется реализацией. Стандарт определяет флаг O_NOCTTY
для open()
системного вызова , который является способом предотвращения того, что в противном случае является обычным способом получения управляющего терминала (процесс без управляющего терминала open()
sa файл устройства терминала, который уже не является управляющим терминалом для какого-либо другого процесса, без указания O_NOCTTY
флага [35] ), но оставляет его традиционную семантику необязательной.
Каждый процесс также является членом группы процессов. Каждое терминальное устройство записывает группу процессов, которая называется его группой процессов переднего плана . Группы процессов управляют доступом к терминалу и доставкой сигналов. Сигналы, генерируемые на терминале, отправляются всем процессам, которые являются членами группы процессов переднего плана терминала. read()
и write()
операции ввода-вывода на терминале процессом, который не является членом группы процессов переднего плана терминала, будут и могут опционально (соответственно) вызывать сигналы ( SIGTTIN
и SIGTTOU
соответственно), которые будут отправлены вызывающему процессу. Различные библиотечные функции изменения режима терминала ведут себя так же, как write()
, за исключением того, что они всегда генерируют сигналы, даже если эта функциональность отключена для write()
себя. [36] [37]
Структура данных, используемая всеми вызовами терминальной библиотеки, представляет собой termios
структуру, [38] определение которой на языках программирования C и C++ выглядит следующим образом: [34]
struct termios { tcflag_t c_iflag ; // Режимы ввода tcflag_t c_oflag ; // Режимы вывода tcflag_t c_cflag ; // Режимы управления tcflag_t c_lflag ; // Локальные режимы cc_t c_cc [ NCCS ] ; // Управляющие символы } ;
Порядок полей в termios
структуре не определен, и реализациям разрешено добавлять нестандартные поля. [34] Действительно, реализации должны добавлять нестандартные поля для записи входных и выходных скоростей передачи данных. Они записываются в структуру в форме, определяемой реализацией, и к ним осуществляется доступ через функции доступа, а не путем прямого манипулирования значениями полей, как в случае со стандартизированными полями структуры. [39]
Псевдонимы типов данных tcflag_t
и cc_t
, а также символическая константа NCCS
и символические константы для различных флагов режима, имен управляющих символов и скоростей передачи данных определяются в стандартном заголовке termios.h
. (Не следует путать его с заголовком с аналогичным названием termio.h
из System III и System V, который определяет похожую termio
структуру и множество похожих по названию символических констант. Этот интерфейс специфичен для System III и System V, и код, который его использует, не обязательно будет переносимым на другие системы.) [40]
Поля структуры следующие (вкратце, подробности см. в основной статье [ необходимы пояснения ] ):
c_iflag
c_oflag
c_cflag
c_lflag
SIGTTOU
системным write()
вызовом [39]Функции библиотеки следующие (кратко, подробности см. в основной статье [ необходимы пояснения ] ):
tcgetattr()
termios
структуру [43]tcsetattr()
termios
структуры, опционально ожидая слива очереди вывода и очищая очередь ввода [43]cfgetispeed()
termios
структуре [44]cfgetospeed()
termios
структуре [44]cfsetispeed()
termios
структуре [44]cfsetospeed()
termios
структуре [44]tcsendbreak()
tcdrain()
tcflush()
tcflow()
tcgetpgrp()
tcsetpgrp()
Элемент c_cc[]
массива termios
структуры данных определяет все (программно-изменяемые) специальные символы. Индексы в массиве являются символическими константами, по одному для каждого типа специального символа, как в таблице справа. (Две дальнейшие записи в массиве относятся к обработке ввода неканонического режима и обсуждаются ниже.) [43]
Непрограммно модифицируемые специальные символы — это перевод строки (ASCII LF
) и возврат каретки (ASCII CR
). [47]
Обработка ввода определяет поведение системного read()
вызова на терминальном устройстве, а также редактирование строки и характеристики генерации сигнала дисциплины строки. В отличие от случая с седьмым изданием Unix и BSD версии 4, и как в случае с System III и System V, редактирование строки работает в одном из двух режимов: каноническом режиме и неканоническом режиме. Основное различие между ними заключается в том, когда с точки зрения требований блокирования/неблокирования системного read()
вызова (указанных флагом O_NONBLOCK
в дескрипторе файла через open()
или fcntl()
) данные «доступны для чтения». [48]
В каноническом режиме данные накапливаются в буфере редактирования строк и не становятся «доступными для чтения» до тех пор, пока редактирование строк не будет прекращено пользователем (на терминале), отправив символ-разделитель строк . Символы-разделители строк являются специальными символами, и это конец файла , конец строки и перевод строки (ASCII LF
). Первые два устанавливаются программно, в то время как последний фиксирован. Последние два включены в буфер редактирования строк, в то время как первый — нет. [49]
Строго говоря, в буфере редактирования строк накапливается ноль или более строк, разделенных разделителями строк (которые могут быть или не быть отброшены, как только дело read()
дойдет до их чтения), и редактирование строк работает с частью буфера редактирования строк, которая следует за последним (если таковой имеется) разделителем строк в буфере. Так, например, символ «стирания» (каким бы он ни был запрограммирован) сотрет последний символ в буфере строк только до (но не включая) предыдущего разделителя строк. [49]
В неканоническом режиме данные накапливаются в буфере (который может быть или не быть буфером редактирования строки — некоторые реализации имеют отдельные очереди «обработанного ввода» и «сырого ввода») и становятся «доступными для чтения» в соответствии со значениями двух параметров управления вводом, c_cc[MIN]
и c_cc[TIME]
членов структуры termios
данных. Оба являются беззнаковыми величинами (потому что cc_t
требуется быть псевдонимом для беззнакового типа). Первый указывает минимальное количество символов, а второй указывает тайм-аут в десятых долях секунды. [50] Существует четыре возможности:
c_cc[TIME]
и c_cc[MIN]
оба равны нулюread()
немедленно возвращаются с любыми данными, находящимися в буфере (потенциально возвращая ноль, если доступных данных нет). [51]c_cc[TIME]
не равен нулю и c_cc[MIN]
равен нулюread()
вызова или при получении одного символа. Другими словами, read()
ожидает максимальное указанное общее время и может вернуть нулевые данные, а также возвращает любые данные сразу после их получения. [51]c_cc[TIME]
равно нулю и c_cc[MIN]
не равно нулюread()
ждет минимального количества данных (которое может быть больше, чем то, что вызывающий готов прочитать в системном вызове), не вернет нулевые данные и может ждать бесконечно. [51]c_cc[TIME]
и c_cc[MIN]
оба не равны нулюread()
ждет минимального количества данных (которое может быть больше, чем то, что вызывающий готов прочитать в системном вызове), не вернет нулевые данные, может ждать бесконечно, но не будет ждать дольше указанного тайм-аута, если в буфере есть хотя бы один символ для чтения. [51]Обработка вывода в значительной степени не изменилась по сравнению с корнями System III/System V. Флаги управления режимом вывода определяют различные параметры: