【问题标题】:What can you do with a pty?你可以用 pty 做什么?
【发布时间】:2021-03-18 08:53:11
【问题描述】:

阅读过各种资源,包括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 被合并?

如果我们有一个主从进程,那么“额外”的 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 UI(在 pty 中)。屏幕程序允许您执行此操作,但它在幕后做了什么。

描述此用例的问题是 - attach a terminal to a process running as a daemon (to run an ncurses UI)


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

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

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

【问题讨论】:

标签: c linux tty pty


【解决方案1】:

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

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

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

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

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

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


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

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

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

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

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

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

【讨论】:

  • 这是一个很好的答案。有些是我想多了。 stderr 根本无关紧要。我仍然不清楚一些细节。我不清楚哪些 API 适合与 pty 一起使用(我的第一个问题以粗体表示)。另请参阅我的新问题 - stackoverflow.com/questions/65282503/…
猜你喜欢
  • 1970-01-01
  • 2012-11-17
  • 2020-07-09
  • 2010-09-11
  • 1970-01-01
  • 2010-11-07
  • 2016-04-13
  • 2018-01-25
  • 1970-01-01
相关资源
最近更新 更多