你可以用 pty 做什么?

Bru*_*ams 1 c linux tty pty

阅读了包括http://www.linusakesson.net/programming/tty/在内的各种资源后, 我仍然对伪终端的结构和使用感到困惑和好奇在 linux 终端(bash 不是 tty)中,我们有三个流:

  • 标准输入
  • 标准输出
  • 标准错误

每个都有一个文件描述符。这些文件描述符映射到流 FILE*。例如,我们可以使用 fileno() 和 fdopen() 在它们之间进行切换。

我可以用 pty 做什么?

我通常认为 stdout、stdin 和 stderr 与进程相关联。我认为说有一个与 pty 相关联的 stdout、stdin 和 stderr 是不正确的。那么给定一个 pty(对于像 bash 这样的进程而不是它的 pid),你如何获得它们并将它们连接到 bash 意义上的终端?

单个文件描述符(对于 pty)如何映射到三个流?(显然它没有)它可以以某种方式映射到进程吗?

在使用 openpty() 时,我认为要连接主终端和从终端上的进程,您需要为 stdin、stdout 和 stderr 设置单独的管道,并有一个选择循环连接它们并在它们之间传输数据。

但是,您是否可以假设 stdin 和 stdout 已连接(通过 tty)并仅设置一个 stderr 管道以避免合并 stdout 和 stderr?

如果我们有一个 master 和 slave 进程,“额外”的 tty 实际上为我们做什么?


以下是我认为我知道并且相关的一些事情:

所述openpty()函数返回一对表示的伪终端的主设备和从设备的端部的文件描述符。

每个文件描述符指向字符设备 - 见https://man7.org/linux/man-pages/man7/pty.7.html

现在 Unix 中的设备只是特殊文件,因此文件描述符是有意义的。

bash 终端具有三个流,但 tty 只是一个通道(stdout 和 stderr 之间没有区别)。虚拟终端 (tty) 也有一些“魔法”(例如,线路规则)。

如果我要问 FD 将 pty 映射到哪个文件,它将是设备文件 /dev/ptyX 或其他任何文件。

openpty() 创建的主/从通道的两端都有一个“终端”。

它实际传输什么数据?

我通常将文件描述符视为表示文件的内核结构表的索引。很明显,其中一些文件是流(FILE* 的内核版本),而其中一些是特殊的。

与终端 (tty) 关联的主要用户空间结构是使用 tcgetattr 从 FD 获得的 termios。这允许您为终端设置参数,但它不会识别任何流或过程或线路规则。

对于您认为是文件的 FILE* 或 fd,有一组非常清晰且易于使用的 API 定义了您可以做什么。

我模糊地知道我可以用字符设备做什么(例如https://unix.stackexchange.com/questions/37829/how-do-character-device-or-character-special-files-work)。

但是你可以用 pty 做什么?

给定一个流,我们可以使用 isatty() 确定它是否具有关联的 tty,并使用 ttyname() 识别 tty。所以在内部某处有一些东西知道 tty 和流是相关联的。isatty(STDERR_FILENO) 也是真的,所以它不仅仅是标准输入和标准输出。


最初引起我兴趣的是一个程序,它应该是一个守护进程,但却创建了一个 ncurses UI。我一直在考虑如何让流程按需打开其 ncurses 用户界面(在 pty 中)。screen 程序可以让你做到这一点,但它在幕后做了什么。

描述此用例的一个问题是 -将终端附加到作为守护进程运行的进程(运行 ncurses UI)


看待这个问题的另一种方式是从面向对象的角度来看。

有哪些函数将“pty”作为参数。它们是做什么用的?如果我有一个 pty 对象。它会有哪些方法?

如果您只列出这些函数,手册页中会缺少一些关键信息。什么是前置条件、后置条件和不变量。

小智 5

文件描述符是每个进程的标识符,指的是内核内部的文件描述。文件描述包括常规文件的位置,以及它是为读取、写入、两者打开还是仅用于路径操作。这些在fcntl() 手册页中称为文件状态标志。每个文件描述符都有自己的描述符标志(目前只有一个,close-on-exec,O_CLOEXEC/FD_CLOEXEC)。

FILE 流是标准的 C 库抽象。如果我们忽略 GNU fopencookie() 扩展,则所有 C 流都与一个文件描述符相关联,并且只有一个 C 流可以与给定的文件描述符相关联。(当然,你可以复制一个文件描述符,并使用 fdopen() 将一个 FILE 流与新的重复文件描述符相关联。但是,C 库不知道这两个单独的流现在共享底层文件位置,这可以当描述符引用文件时会导致问题。当描述符引用设备时,比如终端或伪终端,并且原始描述符以兼容模式(读/写/两者)打开,则没有问题。)

伪终端基本上是一个双向管道,中间有内核 termios 层。在伪终端中运行的典型进程具有引用相同文件描述的所有三个标准描述符:伪终端的从端。

termios 层不只是对从端读写的数据做微小的处理:它还可以引起信号的升高。每个伪终端还与一个大小(行数和列数,以字符为单位)相关联。当伪终端的主端改变大小时,前台进程组(下面解释)中的每个进程都会收到一个 SIGWINCH 信号。如果主机关闭伪终端,则所有将其作为控制终端的进程都将收到一个 SIGHUP 信号。(这也是为什么/如何在有命令或程序在其中运行时关闭终端窗口通常会终止该命令或程序。)

每个进程最多可以有一个控制终端。每个终端最多可以与一个前台进程组关联(通过tcgetpgrp() 和 tcsetpgrp() 管理)。会话的概念(getid()setsid()、前台进程组和后台进程组)都涉及到一个终端(或伪终端)。

当一个进程将所有三个标准 C FILE 流都连接到终端或伪终端时,标准输入从主机接收数据(通过 termios 层),标准输出和标准错误都将数据发送到主机(通过 termios 层)。所以,标准输入、输出和错误并没有直接相连,只是连接到同一个终端/伪终端从端。


假设您是一名 GTK+3 C 程序员,并且您想创建自己的终端窗口应用程序,例如 xterm 或 gnome-terminal。

应用程序将为每个窗口创建一个伪终端。每当它接收到键盘事件时,它就会将相应的键序列(1for 1:for:等)写入主伪终端描述符。它将从主伪终端描述符中读取的所有内容都将在窗口中呈现。对于slave端,应用程序fork一个子进程,为所有三个标准流打开slave端,或者/bin/login -f $(id -un)以root权限执行等效的操作,或者为当前用户本身做等效的操作。本质上,为用户完成了某些内务管理,最后在用户的主目录中为用户打开了一个登录 shell。

大多数终端仿真器支持ANSI 转义码和对这些的 xterm 扩展。终端信息数据库和 ncurses 应用程序依赖于设置为当前终端类型的 TERM 环境变量。(xterm-256color 可能是 Linux 中最常见的,但也支持其他的。终端模拟器应该在从端启动进程时设置正确的 TERM 环境变量;本身没有自动检测。)

您需要伪终端的所有事情都涉及类似终端的环境。它可能面向人类用户(如在上述 GUI 终端应用程序中),也可能面向在终端中工作并代表用户执行操作的应用程序,如 nano、vim、emacs、links 或 lynx。

screen工作方式是,即使用户未连接到该屏幕进程,它也会维护应用程序正在运行的伪终端。当用户想要分离(即,断开连接但让进程继续运行)时,屏幕应用程序(它在其下运行进程的伪终端的主端)与人类正在使用的终端/伪终端分离,但继续运行。要恢复,screen -r命令(和已经运行 screen 时的键盘快捷键)将用户终端或伪终端重新连接到 screen 应用程序。

本质上,使用此类终端多路复用器,您将拥有连接到多路复用器应用程序的真实用户终端或伪终端(可以是像 xterm 或 gnome-terminal 这样的终端模拟器,甚至是像远程 SSH 连接到这台机器的非终端进程) ,它在连接到伪终端(多路复用器应用程序的主控)中继按键和输出,外壳或其他应用程序在该伪终端下运行。