每个伪终端 (PTY) 组件(软件、主端、从端)的职责是什么?

Pie*_*ean 77 kernel terminal-emulator terminal tty pty

我试图弄清楚tty如何工作的1(每个元素的工作流程和职责)。我已经阅读了几篇关于它的有趣文章,但仍然有一些模糊的地方。

这是我到目前为止的理解:

  • 仿真终端/dev/ptmx对伪终端的主控部分进行不同的系统调用。
  • 伪终端的master部分分配了一个文件/dev/pts/[0-N],对应过时的串口,并在其上“附加”了一个slave伪终端。
  • 从伪终端保存会话ID、前台作业、屏幕大小等信息。

以下是我的问题:

  1. ptmx除了分配从属部分之外还有什么用途吗?它是否提供某种“智能”,或者模拟终端(例如 xterm)具有像终端一样运行的所有智能?
  2. 为什么 xterm必须与 master 部分交互,因为它只转发 slave 部分的 stdout 和 stdin?为什么不能 直接读写pts文件
  3. 会话 ID 是否总是附加到一个 pts 文件,反之亦然?我可以输入 ps 命令并找到2 个 sessionId 的相同 /dev/pts/X吗?
  4. pts商店还有什么其他信息?Xterm 是自己更新所有字段,还是在其上ptm添加一些“智能”?

1. 我的理解基于Linus Akesson 揭秘TTYAndries BrouwerLinux Kernel帖子,以及这些网站上的其他几个问题

Sté*_*las 76

终端模拟器

主端替换连接到终端的线路(一对 TX/RX 线)。

终端显示它在其中一根电线上接收到的字符(其中一些是控制字符并使其执行诸如移动光标、更改颜色等操作),并在另一根电线上发送与您键入的键相对应的字符。

像 xterm 这样的终端仿真器没有什么不同,只是它们不是在线路上发送和接收字符,而是在主端读取和写入文件描述符上的字符。一旦他们产生了从属终端,并在其上启动了你的 shell,他们就不再碰它了。除了模拟这对线之外,xterm 还可以通过该文件描述符将一些线路规则属性更改为主端。例如,他们可以更新大小属性,以便向与从属 pty 交互的应用程序发送 SIGWINCH 以通知他们更改的大小。

除此之外,终端/终端模拟器中几乎没有智能

您写入终端设备(如 pty slave)的意思是显示在那里,您从中读取的内容就是您在那里输入的内容,因此终端模拟器读取或写入该设备没有任何意义. 他们是另一端的人。


tty 线路纪律

很多的情报tty线路纪律。线路规程是一个软件模块(驻留在驱动程序中,在内核中)被推到位于该设备和线路/电线(pty 的主端)之间的串行/pty 设备之上。

串行线的另一端可以有一个终端,也可以有一个鼠标或另一台用于联网的计算机。例如,您可以附加 SLIP 线路规则以在串行设备(或 pty 设备)之上获取网络接口,或者您可以拥有tty线路规则。tty 线路规则是至少在 Linux 上用于串行和 pty 设备的默认线路规则。在 Linux 上,您可以使用ldattach.

您可以通过发出stty raw -echo命令来查看禁用 tty 行规则的效果(请注意,bash 提示符或其他交互式应用程序,例如vi将终端设置为他们需要的确切模式,因此您希望使用类似这种cat体验的哑应用程序)。然后,写入从终端设备的所有内容都会立即发送到主端供 xterm 读取,并且 xterm 写入主端的每个字符都可以立即从从设备读取。

线路规程是实现终端设备内部线路编辑器的地方。例如用stty icanon echo(因为是默认设置),当你键入a,xterm中写入a到主机,那么该行的纪律相呼应(使得回a可通过读取xterm用于显示),但根本不提供任何用于读取从站侧. 然后,如果您键入退格键,xterm 会发送一个^?^H字符,线路规则(作为^?^H对应于erase线路规则设置)向主设备发送回 a ^Hspace^Hxterm擦除a您刚刚在其屏幕上输入了内容,但仍然没有将任何内容发送到从从端读取的应用程序,它只是更新其内部行编辑器缓冲区以删除a您之前输入的内容。

然后,当您按下 Enter 键时,xterm 发送^M(CR),线路规则将输入转换为 ^J (LF),并发送您迄今为止输入的内容,以便在从机端读取(正在读取的应用程序/dev/pts/x将收到什么您输入的 LF 包括 LF,但a由于您已删除它而未输入),而在主端,它会发送 CR 和 LF 以将光标移动到下一行和屏幕的开头。

线路纪律还负责在主端接收到字符时向终端的前台进程组发送SIGINT信号^C等。

许多交互式终端应用程序禁用该线路规程的大部分功能以自行实现它。但无论如何,请注意终端 ( xterm) 几乎没有参与其中(除了显示被告知要显示的内容)。

每个进程和每个终端设备只能有一个会话。一个会话可以有一个控制终端连接到它,但不是必须的(所有会话开始时没有终端,直到它们打开一个)。xterm,在它分叉以执行您的 shell 的过程中,通常会创建一个新会话(因此与您启动的终端分离xterm,如果有的话),打开/dev/pts/x它产生的新会话,通过将该终端设备附加到新会话。然后它将在该过程中执行您的 shell,因此您的 shell 将成为会话领导者。您的 shell 或该会话中的任何交互式 shell 通常会处理进程组和tcsetpgrp(), 以设置该终端的前台和后台作业。

至于具有 tty 规则(串行或 pty)的终端设备存储哪些信息,这通常是stty命令显示和修改的内容。所有学科配置:终端屏幕大小、本地、输入输出标志、特殊字符设置(如 ^C、^Z...)、输入和输出速度(与 ptys 无关)。对应于tcgetattr()/tcsetattr()功能,这在Linux映射到TCGETS/TCSETS读写控制,和TIOCGWINSZ/TIOCSWINSZ对于屏幕尺寸。您可能会争辩说,当前的前台进程组是存储在终端设备(tcsetpgrp()/ tcgetpgrp()TIOC{G,S}PGRPioctls)或当前输入或输出缓冲区中的另一个信息。

请注意,终端设备中存储的屏幕尺寸信息可能无法反映实际情况。终端仿真器通常会在调整其窗口大小时对其进行设置(通过主尺寸上的相同 ioctl),但如果应用程序调用从端的 ioctl 或未传输调整大小时(以防万一),它可能会不同步ssh 连接,这意味着sshdif产生的另一个 ptyssh忽略SIGWINCH例如)。一些终端也可以通过转义序列查询它们的大小,因此应用程序可以通过这种方式查询它,并用该信息更新线路规则。

有关更多详细信息,例如,您可以查看Debian上的termiostty_ioctl手册页。

与其他线路学科一起玩:

  1. 用伪终端模拟鼠标:

    socat pty,link=mouse fifo:fifo
    sudo inputattach -msc mouse # sets the MOUSE line discipline and specifies protocol
    xinput list # see the new mouse there
    exec 3<> fifo
    printf '\207\12\0' >&3 # moves the cursor 10 pixels to the right
    
    Run Code Online (Sandbox Code Playgroud)

    上面,pty 的主端通过 socat 终止到命名管道 ( fifo) 上。我们将该 fifo 连接到一个进程(外壳),该进程写入 0x87 0x0a 0x00,在鼠标系统协议中这意味着no button pressed, delta(x,y) = (10,0). 在这里,我们(shell)不是在模拟终端,而是在模拟鼠标,我们发送的 3 个字节不会被终端设备的应用程序读取(可能被转换)(mouse上面是socat某个/dev/pts/x设备的符号链接) ,但将被解释为鼠标输入事件。

  2. 创建 SLIP 接口:

    # on hostA
    socat tcp-listen:12345,reuseaddr pty,link=interface
    # after connection from hostB:
    sudo ldattach SLIP interface
    ifconfig -a # see the new interface there
    sudo ifconfig sl0 192.168.123.1/24
    
    # on hostB
    socat -v -x pty,link=interface tcp:hostA:12345
    sudo ldattach SLIP interface
    sudo ifconfig sl0 192.168.123.2/24
    ping 192.168.123.1 # see the packets on socat output
    
    Run Code Online (Sandbox Code Playgroud)

    上面,串行线被模拟socat为主机 A 和主机 B 之间的 TCP 套接字。SLIP 线路规程将通过该虚拟线路交换的那些字节解释为 SLIP 封装的 IP 数据包,以便在sl0接口上传送。


Pie*_*ean 37

编辑:自从有了这个答案,我在我的博客上写了一篇专门的文章,供那些对更多细节感兴趣的人使用。


经过大量阅读,这就是我的理解。

  • ptmx 除了分配从属部分之外还有什么用途吗?它是否提供某种“智能”,或者模拟终端(例如 xterm)具有像终端一样运行的所有智能?

    /dev/ptmx不分配从属部分:它分配“伪终端主部分”。/dev/ptmx 不是主伪终端:它是一个伪终端主多路复用器。它是用 Unix98 PTY 标准创建的,以避免在分配主伪终端(source)时出现竞争条件。

    伪终端的主部件 (ptm)未在文件系统上表示。它由文件描述符表示。

    所述从动部分(PTS)通过在文件表示/dev/pts/N,其中N是一个数字。

    pts 是通过grandpt, unlockpt, ptsname.( Source )的连续调用从 ptm 中获得的

    ptm 替换了专用于与设备通信的 AUR 驱动程序,以及 line 版本。所以它不以任何方式模拟终端,而是提供line edition的功能,并提供一种可视化和与 pts 通信的方式。(来源

    这是连接到硬件设备的 TTY 的图表 与 AUR 的 TTY 通信

    这是连接到 ptm 的 tty 的图 与 PTM 的 TTY 通信

    ptm 文件处理与 pts 不同的 Ioctl 参数(ISPTM、UNLKPT、TIOCREMOTE、TIOCSIGNAL)。

  • 为什么 xterm 必须与 master 部分交互,因为它只转发 slave 部分的 stdout 和 stdin?为什么不能直接读写pts文件?

    进程通过对虚拟文件执行的操作(读、写、ioctl ..)与设备交互。该文件本身不存在,驱动程序使用该文件在调用读取或写入方法时触发操作。(有关驱动程序的信息,请参阅附件)

    TTY 定义了一种与之交互的精确方式。无论实现何种类型的 TTY,处理从设备写入和读取的过程并期望相同的行为。

    • 进程使用read 函数从终端读取条目
    • 进程使用write 函数输出发送到终端

    pts 的行为类似于 TTY 驱动程序。它的读写方法用于实现 TTY 驱动程序行为。由于没有真正的设备来发送数据,所以创建了一个流对,ptm 实现了一个读取函数来读取 pts 发送到流的数据,以及一个写入函数将数据发送到将可用的流pts什么时候会读。

    请记住,代表设备的文件不是经典文件,如果xterm想查看已写入文件的内容,不能简单地调用 open 并读取它,因为这些函数在这里具有完全不同的行为。

  • 会话 ID 是否总是附加到一个 pts 文件,反之亦然?我可以键入一个 ps 命令并为同一个 /dev/pts/X 找到 2 个 sessionId 吗?

    我不这么认为,会话 ID 是由附加 pts 的第一个进程定义的(通常是 bash),我没有看到创建另一个会话并将其附加到相同 pts 的方法。也许像socat这样的工具可以做到这一点?

  • pts 还存储哪些其他信息?Xterm 是自己更新所有字段,还是 ptm 在其上添加一些“智能”?

    pts 存储有关与其通信的终端的 2 类信息:theTerminfo和 the Termcap。通常,许多终端仿真器都基于为它们管理 termcap 信息的库(例如,它将提供所有功能值来模拟 VTX100)。这种库的一个例子是libvte。编辑(请参阅 Stephane Chazelas 评论):终端功能不由 pts 存储。

附件

  • 这些都是不错的图片。你是用什么软件制作的? (7认同)
  • @Gilles 谢谢。我是用 [Inkscape](http://www.inkscape.org),一个开源矢量图形编辑器完成的。这可能不是制作此类图形的最有效方法,但如果您有兴趣,[我写了一篇文章](http://bit.ly/1udaYSz) 关于如何创建此类等轴测图。 (6认同)
  • 图像看起来很整洁,但由于严重倾斜而很难阅读。感谢您的周到回答。 (2认同)

Bor*_*kov 17

这是我前段时间制定的关于如何sshd工作的计划。它不涉及生产线纪律和其他东西的操作,但它添加了一个真实的例子,说明谁与什么交互:

在此处输入图片说明