TTY-абстракция
Подсистема 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.[1]
В первом варианте необходимо открыть файл /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 означает, что процесс был приостановлен, а затем пробудился.
- SIGTTIN
- Когда процесс, выполняющийся в фоновом режиме, пытается читать из TTY, TTY посылает этот сигнал всему заданию. Обычно это приостанавливает задание, до тех пор пока пользователь не переключится на него и не сможет ввести ожидаемые данные.
- SIGTTOU
- Аналогичен предыдущему, но вызывается, когда фоновый процесс пытается писать в TTY. Данный сигнал от данного TTY можно отключить.
- SIGWINCH
- TTY посылает сигнал SIGWINCH активному заданию при изменении размера терминала.
Пример
Рассмотрим следующий пример. Пусть пользователь редактирует текст в консольном текстовом редакторе. Курсор примерно посередине экрана, и редактор как раз занят выполнением задачи, требующей много процессорного времени (например, поиском и заменой слов в большом файле). В этот момент пользователь нажимает ^Z
.
Если дисциплина (протокол линии связи) была настроена на перехват этого символа, пользователю не придётся ждать, пока редактор закончит выполнение текущего задания, потому что слой дисциплины мгновенно пошлёт сигнал SIGTSTP активной задаче (то есть активной группе процессов). Причём в эту группу входит не только сам редактор, но и все его дочерние процессы.
Пусть редактор настроил ручную обработку сигнала SIGTSTP. Тогда ядро вызывает обработчик прерывания (внутри процесса текстового редактора). Этот обработчик передвигает курсор на последнюю строку на экране, путём записи определённой последовательности управляющих символов в TTY. Так как редактор является активным процессом, эти символы незамедлительно передаются и обрабатываются. Затем редактор посылает сам себе (и своей группе процессов) сигнал SIGSTOP и засыпает.
Тот факт, что текстовый редактор заснул, передаётся ведущему процессу сессии при помощи сигнала SIGCHLD (вместе с идентификаторами уснувших процессов). Когда засыпают все процессы активной задачи, ведущий процесс сессии запоминает текущие настройки TTY, и провозглашает себя активной задачей данного TTY при помощи системного вызова ioctl
. После этого он печатает на экране уведомление для пользователя, что текущая задача была приостановлена.
Если сейчас вызвать команду ps
, она покажет, что текстовый редактор приостановлен (буква «T»). При попытке пробудить его — например, встроенной в оболочку командой bg
, или же командой kill
послав ему сигнал SIGCONT, — редактор запустит обработчик сигнала SIGCONT. Этот обработчик попытается перерисовать интерфейс путём записи последовательности управляющих символов в TTY. Однако теперь редактор — фоновый процесс, поэтому вместо отрисовки интерфейса TTY пошлёт редактору сигнал SIGTTOU, и тот опять уснёт. Ведущий процесс сессии узнает об этом при помощи сигнала SIGCHLD, и опять выведет на экран уведомление для пользователя.
Если же вместо этого вызвать команду fg
оболочка восстановит сохранённые ранее настройки TTY, вновь сделает редактор активной задачей, и пошлёт ему (и его группе процессов) сигнал SIGCONT. После этого редактор сможет нормально отрисовать свой интерфейс, и работа продолжится.
Настройка TTY
Узнать TTY, который управляет данной программной оболочкой, можно при помощи утилиты tty
.
Открытый TTY можно настроить при помощи ioctl
. Однако, данный интерфейс не является переносимым, поэтому рекомендуется использовать вместо него POSIX-совместимые обёртки (см. man 3 termios
).
TTY можно также настроить прямо из консоли, используя утилиту stty
, которая основана на упомянутом выше API termios
:
$ stty -a
speed 38400 baud; rows 73; columns 238; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
stty -a
выводит все настройки TTY. Конкретный TTY можно выбрать при помощи флага -F
.
speed
показывает скорость UART-порта. Псевдотерминалы игнорируют это значение.
rows
и columns
показывают размер терминала в символах. По сути, это просто две числовые переменные внутри TTY-драйвера, которые можно свободно читать и изменять. При их изменении активной задаче будет послан сигнал SIGWINCH.
line
показывает номер активной дисциплины. Все имеющиеся в системе дисциплины перечислены в /proc/tty/ldiscs
.
Далее перечисляются спецсимволы, а в конце — выбранные в настоящий момент опции. Тире означает, что опция отключена.
Примеры
Если открыть окно Xterm, запомнить его TTY (вызвав команду tty
) и размер (вызвав команду stty -a
), запустить полноэкранное консольное приложение (типа vim), а потом в другом окне Xterm набрать stty -F X rows Y
, где X — имя TTY первого окна, а Y — половина его высоты, то vim в первом окне тут же получит сигнал SIGWINCH, и перерисует свой интерфейс, используя лишь половину предоставленного ему окна.
Если в окне Xterm набрать stty intr o
, то теперь сигнал SIGINT будет генерироваться при вводе символа «o». При этом нажатие ^C
ни к чему не приведёт.
Иногда в UNIX-системе не работает кнопка backspace. Это происходит потому, что эмулятор терминала посылает в TTY не тот ASCII-код, которому в этом TTY назначена функция erase
. Чтобы решить эту проблему, нужно набрать stty erase ^H
или stty erase ^?
. Первая команда установит символ стирания на ASCII-код 8, вторая — на 127. На приложения, работающие в сыром режиме, эти настройки не влияют.
Если в окне Xterm набрать stty -icanon
, это отключит канонический режим. Если после этого попытаться, например, запустить программу cat, все сочетания клавиш, отвечающие за редактирование текста, такие, как ^U
или даже backspace, не будут работать. Кроме того, cat
будет получать (и, соответственно, выводить) данные не строчками, как раньше, а отдельными символами.
Если в окне Xterm набрать stty -echo
, это отключит вывод на экран набираемых данных. Вызов после этого программы cat
продемонстрирует, что набираемые на клавиатуре данные больше не выводятся на экран (то есть придётся набирать текст «вслепую»). Однако, после нажатия клавиши Ввод (Enter), ядро передаст последнюю напечатанную строчку программе cat
, и она уже выведет её на экран.
Если в окне Xterm набрать stty -tostop
, это позволит процессам, работающим в фоновом режиме, выводить данные на экран, вместо того чтобы быть заблокированными. Например, команда (sleep 5; echo hello, world) &
покажет приглашение оболочки, однако через 5 секунд в консоль будет выведена строчка «hello, world»
. Если в это время работать с терминалом (например, набирать какой-то текст), то эта строчка вклинится прямо в этот набираемый текст. Если же набрать stty tostop
, то запуск команды (sleep 5; echo hello, world) &
приведёт к блокировке этого процесса сигналом SIGTTOU, потому что через 5 секунд он попытается вывести на экран текст, находясь при этом в фоновом режиме. Обычно оболочка в таких случаях выводит на экран предупреждающую надпись (либо сразу же, либо при выводе очередного приглашения).
Команда stty sane
возвращает настройки TTY к «вменяемым» параметрам.
Больше информации можно найти в системе info
(info libc, «Job Control»).
Примечания
- man 7 pty (15 сентября 2017).
Источник
- Linus Åkesson. The TTY demystified (англ.). www.linusakesson.net (25 июля 2008).