В Unix-подобных операционных системах, стандартные ввода, вывода и ошибок потоков определены файловых дескрипторов 0
, 1
, 2
. В Linux они видны в proc
файловой системе в /proc/[pid]/fs/{0,1,2}
. Эти файлы на самом деле являются символическими ссылками на псевдотерминальное устройство в /dev/pts
каталоге.
Псевдотерминал (PTY) - это пара виртуальных устройств, главный псевдотерминал (PTM) и подчиненный псевдотерминал (PTS) (совместно именуемые псевдотерминальной парой ), которые обеспечивают канал IPC, что-то вроде двунаправленной трубы между программой, которая ожидает быть подключенным к терминальному устройству и программе драйвера, которая использует псевдотерминал для отправки ввода и получения ввода от предыдущей программы.
Ключевым моментом является то, что псевдотерминальный подчиненный выглядит как обычный терминал, например, он может переключаться между неканоническим и каноническим режимом (по умолчанию), в котором он интерпретирует определенные входные символы, например генерирует SIGINT
сигнал, когда символ прерывания (обычно генерируется) нажатием Ctrl+ Cна клавиатуре) записывается в мастер псевдотерминала или вызывает read()
возврат следующего, 0
когда встречается символ конца файла (обычно генерируемый Ctrl+ D). Другими операциями, поддерживаемыми терминалами, является включение или выключение эха, настройка группы процессов переднего плана и т. Д.
Псевдотерминалы имеют ряд применений:
Они позволяют таким программам ssh
управлять терминально-ориентированными программами на другом хосте, подключенном через сеть. Ориентированная на терминал программа может быть любой программой, которая обычно запускается в сеансе интерактивного терминала. Стандартный ввод, вывод и ошибка такой программы не могут быть подключены напрямую к сокету, так как сокеты не поддерживают вышеупомянутую функциональность, связанную с терминалом.
Они позволяют таким программам expect
управлять интерактивной терминально-ориентированной программой из сценария.
Они используются эмуляторами терминала, например, xterm
для обеспечения функциональности терминала.
Они используются такими программами, как screen
мультиплексирование одного физического терминала между несколькими процессами.
Они используются такими программами, как script
запись всех входных и выходных данных во время сеанса оболочки.
PTY-файлы в стиле Unix98 , используемые в Linux, настраиваются следующим образом:
Программа драйвера открывает главный мультиплексор псевдотерминала dev/ptmx
, на котором она принимает файловый дескриптор для PTM, и в /dev/pts
каталоге создается устройство PTS . Каждый дескриптор файла, полученный при открытии, /dev/ptmx
является независимым PTM со своим собственным PTS.
Драйвер программы вызывает fork()
для создания дочернего процесса, который, в свою очередь, выполняет следующие шаги:
Ребенок вызывает, setsid()
чтобы начать новый сеанс, из которых ребенок является лидером сеанса. Это также приводит к тому, что ребенок теряет свой контролирующий терминал .
Ребенок переходит к открытию устройства PTS, которое соответствует PTM, созданному программой драйвера. Поскольку дочерний объект является лидером сеанса, но не имеет управляющего терминала, PTS становится управляющим терминалом дочернего объекта.
Дочерний объект использует dup()
для дублирования файловый дескриптор для ведомого устройства на нем стандартный ввод, вывод и ошибку.
Наконец, ребенок вызывает exec()
запуск ориентированной на терминал программы, которая должна быть подключена к псевдотерминальному устройству.
В этот момент все, что программа драйвера записывает в PTM, отображается как входная информация для ориентированной на терминал программы в PTS, и наоборот.
При работе в каноническом режиме вход в PTS буферизуется построчно. Другими словами, как и в случае с обычными терминалами, программа, считывающая данные из PTS, получает строку ввода только тогда, когда символ PTL записывается в PTM. Когда буферная емкость исчерпана, дальнейшие write()
вызовы блокируются до тех пор, пока не будут использованы некоторые входные данные.
В ядре Linux системные вызовы open()
, связанные с файлами read()
, write()
stat()
и т. Д. Реализованы на уровне виртуальной файловой системы (VFS), который обеспечивает единый интерфейс файловой системы для программ пользовательского пространства. VFS позволяет различным реализациям файловой системы сосуществовать в ядре. Когда пользовательские программы вызывают вышеупомянутые системные вызовы, VFS перенаправляет вызов соответствующей реализации файловой системы.
Устройства PTS ниже /dev/pts
управляются реализацией devpts
файловой системы, определенной в /fs/devpts/inode.c
, в то время как драйвер TTY, обеспечивающий устройство в стиле Unix98, ptmx
определен в drivers/tty/pty.c
.
Буферизация между устройствами TTY и дисциплинами линий TTY , такими как псевдотерминалы, обеспечивается структурой буфера, поддерживаемой для каждого устройства tty, определенной вinclude/linux/tty.h
До версии ядра 3.7 буфер был перекидным :
#define TTY_FLIPBUF_SIZE 512
struct tty_flip_buffer {
struct tq_struct tqueue;
struct semaphore pty_sem;
char *char_buf_ptr;
unsigned char *flag_buf_ptr;
int count;
int buf_num;
unsigned char char_buf[2*TTY_FLIPBUF_SIZE];
char flag_buf[2*TTY_FLIPBUF_SIZE];
unsigned char slop[4];
};
Структура содержит хранилище, разделенное на два буфера одинакового размера. Буферы были пронумерованы 0
(первая половина char_buf/flag_buf
) и 1
(вторая половина). Драйвер сохранил данные в буфере, указанном buf_num
. Другой буфер может быть сброшен в дисциплину строки.
Буфер был «перевернут» переключением buf_num
между 0
и 1
. Когда buf_num
изменено, char_buf_ptr
и flag_buf_ptr
было установлено в начало буфера, идентифицированного buf_num
, и count
было установлено в 0
.
Начиная с версии ядра 3.7, буферы переключения TTY были заменены объектами, выделенными через kmalloc()
организованные в кольцах . В обычной ситуации для последовательного порта, управляемого IRQ, на типичных скоростях их поведение почти такое же, как и в случае старого откидного буфера; Два буфера в итоге распределяются, и ядро циклически переключается между ними, как и раньше Однако, когда есть задержки или скорость увеличивается, новая реализация буфера работает лучше, так как буферный пул может немного увеличиться.