Подсистема TTY, или TTY-абстракция, — это одна из основ Unix или Unix-подобных операционных систем, в частности Linux. Данная система предназначена для использования одного терминала несколькими процессами, некоторых возможностей ввода (например, отправка сигналов специальными клавишами, удаление введённых символов).

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

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

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

Так как различных моделей телетайпов было множество, потребовался некий уровень программной совместимости, чтобы абстрагироваться от конкретной модели телетайпа. В UNIX и UNIX-подобных системах низкоуровневая работа с телетайпом — например, количество битов в пакете, скорость в бодах, контроль потока, контроль чётности, специальные коды для зачаточного форматирования страницы, и т. д., — возлагалась на ядро операционной системы. Такие возможности, как перемещение курсора, цветной текст и т. п., стали возможны лишь в конце 1970-х годов, с появлением видеотерминалов типа VT-100. Все эти расширенные функции оставались на попечении приложений.

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

Сценарий использования

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

Редактирование строк Подразумевает под собой возможность удаления напечатанных символов. В соответствии с философией UNIX, программы должны быть как можно проще, поэтому данная функциональность предоставляется драйвером ядра, а не программой, использующей телетайп. Операционная система предоставляет буфер для редактирования текста, а также некоторые простейшие команды редактирования — «удалить символ», «удалить слово», «удалить строку». Все эти функции реализованы в модуле дисциплины линии (line discipline). По умолчанию они включены; такой режим называется каноническим (canonical), или приготовленным (cooked). Программа при желании может отключить эти функции, переведя драйвер в сырой (raw) режим. (Большинство консольных интерактивных программ — текстовые редакторы, почтовые агенты, оболочки, а также все программы, использующие Curses или Readline, — работают в raw-режиме, и сами обрабатывают все команды редактирования). Упомянутый слой протокола также позволяет настраивать эхо (отображение набираемых символов на этом же самом терминале), автоматическую конвертацию признаков конца строки и возврата каретки, и т. п. Таким образом, слой протокола является примитивным разборщиком текста типа Sed, причём работающим в режиме ядра.

Смысл выделения описанной выше обработки в отдельный слой заключается в том, что дисциплину (то есть конкретный драйвер этого слоя) можно динамически менять. Например, вместо дисциплины терминала можно включить дисциплину передачи данных с пакетной коммутацией — ppp, IrDA, последовательную мышь и т. д.

Управление сеансами связи Как правило, пользователь хочет запустить одновременно несколько программ и взаимодействовать с ними по очереди. Если программа зависает, — пользователь наверняка захочет её. Процессы, работающие в фоновом режиме, должны блокироваться, как только они захотят вывести какой-либо текст на экран. Аналогичным образом набираемый пользователем текст должен передаваться только активной в данный момент программе. Операционная система реализует все эти функции при помощи драйвера TTY.

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

Система из драйвера UART-порта, дисциплины (протокола) и драйвера TTY называется устройством TTY, или просто TTY. Пользовательский процесс может изменять поведение любого TTY-устройства путём манипулирования соответствующим ему файлом в папке /dev. Естественно, для этого данный процесс должен обладать в этот файл. Поэтому, когда пользователь входит в систему и подключается к определённому TTY, этот пользователь должен стать владельцем файла, соответствующего этому TTY. Именно это и делает программа login. (Сама программа login запускается от имени суперпользователя).

Теперь рассмотрим случай, когда система работает на обычном современном персональном компьютере. Дисциплина и TTY-драйвер работают так же, как и раньше, но драйвера UART-порта уже нет, так как нет телетайпа, который бы через него подключался. Вместо него используется эмулятор видеотерминала — программа, которая имитирует видеотерминал (аналог телетайпа, но с видеоэкраном вместо бумажной ленты), и отображает содержимое этого терминала на экран. При этом эта программа, в отличие от консоли, уже работает в , а не ядра, что обеспечивает куда большую гибкость; например, можно выводить терминал в окне, как это делает Xterm.

Псевдотерминал

Для того, чтобы разрешить работу эмулятора терминала в пространстве пользователя и при этом не отказываться от всей вышеописанной подсистемы TTY, был изобретён так называемый псевдотерминал, или PTY. Псевдотерминал может быть запущен внутри другого псевдотерминала; так поступают, например, Screen или клиент Ssh.

Графический эмулятор терминала, такой как, например, xterm, первым делом создаёт новый псевдотерминал и дочерний процесс, который становится лидером новой сессии, делает ведомую часть (slave) псевдотерминала своим управляющим терминалом и запускает интерпретатор команд (чаще всего bash или sh). Ведущая часть (master) псевдотерминала используется эмулятором терминала для отображения данных, получаемых от ведомой части. Все процессы, запущенные из интерпретатора, включая и сам интерпретатор, осуществляют ввод (stdin) и вывод (stdout и stderr) через ведомую часть.

В Linux для создания псевдотерминала доступно два API (pty(7)): UNIX 98 (pts(4)) и BSD.

В первом варианте необходимо открыть файл /dev/ptmx (рекомендуется использовать int posix_openpt(int flags)), при этом возвращённый файловый дескриптор будет привязан к ведущей части, и в каталоге /dev/pts/ создастся новый файл ведомой части, именем которого будет положительное целое число. Каждое открытие данного файла создаёт новый псевдотерминал. Для того, чтобы узнать точный путь к ведомой части, существует функция char* ptsname(int fd). Перед открытием ведомой части необходимо вызвать grantpt и unlockpt.

В случае с BSD в каталоге /dev/ существует множество файлов вида ttyXY (ведомая часть) и ptyXY (ведущая часть).

Место TTY в модели процессов В данном примере при помощи команды ps l можно увидеть статус каждого процесса, причём в колонке WCHAN будет отображено событие, которого дожидается конкретный спящий процесс. $ ps l F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 0 500 5942 5928 15 0 12916 1460 wait Ss pts/14 0:00 -/bin/bash 0 500 12235 5942 15 0 21004 3572 wait S+ pts/14 0:01 vim index.php 0 500 12580 12235 15 0 8080 1440 wait S+ pts/14 0:00 /bin/bash -c (ps l) >/tmp/v727757/1 2>&1 0 500 12581 12580 15 0 4412 824 - R+ pts/14 0:00 ps l

Колонка STAT в выводе команды ps показывает состояние процесса, но она также может содержать несколько флагов: s — данный процесс — ведущий процесс сессии; + — данный процесс входит в группу процессов, с которыми в данный момент идёт работа пользователя (то есть foreground processes — в противоположность background processes, работающим в фоновом режиме).

Именно эти атрибуты и используются для управления заданиями. Задача драйвера TTY — отслеживать идентификатор активной группы процессов (который явно обновляется ведущим процессом сессии).

TTY и система сигналов К TTY имеют непосредственное отношение следующие сигналы:

SIGHUP Драйвер UART-порта посылает сигнал SIGHUP всей сессии, когда модем переходит в состояние «трубка повешена». Обычно это убивает все процессы сессии. Некоторые программы, такие, как Screen или Nohup, отделяются от своей сессии и своего TTY, поэтому их дочерние процессы не умирают при отключении модема.

SIGINT Сигнал SIGINT генерируется драйвером TTY, когда во входном потоке появляется особый символ ^C (ASCII-код этого символа равен 3). Драйвер посылает этот сигнал активному заданию. Программа, имеющая доступ к TTY, может изменить код этого спецсимвола, либо вообще отключить генерацию этого сигнала. Менеджер сессий отслеживает настройки TTY, установленные каждой из работающих задач, и применяет их, когда эти задачи переключаются.

SIGQUIT Аналогичен SIGINT, спецсимвол для генерации: ^\.

SIGPIPE Этот сигнал полезен в заданиях, потому что он позволяет в конструкции типа yes | head завершить процесс yes по завершении процесса head.

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

SIGSTOP Этот сигнал приостанавливает исполнение процесса, который его получает. Обработать его может только процесс init. Как правило, ядро не использует этот сигнал. Вместо него спецсимвол ^Z посылает сигнал SIGTSTP, который уже может быть отловлен приложением; как правило, приложение выполняет определённые действия, после чего само себя ставит на паузу — уже сигналом SIGSTOP.

SIGCONT Этот сигнал пробуждает усыплённый ранее процесс. Его посылает оболочка, когда пользователь вызывает команду fg. Так как этот сигнал нельзя обработать, неожиданный сигнал SIGCONT означает, что процесс был приостановлен, а затем пробудился.

SIGTSTP SIGTSTP аналогичен SIGINT и SIGQUIT. Спецсимвол ^Z (ASCII-код 26).

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

SIGTTOU Аналогичен предыдущему, но вызывается, когда фоновый процесс пытается писать в TTY. Данный сигнал от данного TTY можно отключить.

SIGWINCH TTY посылает сигнал SIGWINCH активному заданию при изменении размера терминала.

Пример