tty命令,TTY,Controlling Terminal

-- TOC --

本文介绍tty命令,以及TTY终端和console控制台的历史,Controlling Terminal,理清这些概念!内容零散,但很有价值。

tty命令

tty命令用于显示链接到当前stdin的终端文件名(字符设备文件)。

tty - print the file name of the terminal connected to standard input

$ tty
/dev/pts/0  # pts/1...

如果你的Linux在文本界面下(用Ctrl+Alt+FN切换tty):

$ tty
/dev/tty1  # tty2, tty3...

pts表示pty的从设备(slave),pty是伪终端,pseudo-tty,模拟出来的终端。通过网络远程登录,或者在X Windows里开的终端模拟器,用tty命令看到的都是pts!

pts

使用终端模拟器登录到 Linux 时,使用的是伪终端(PTY,Pseudo TTY)。

伪终端分为主和从两个部分,与终端模拟器交互的部分是主(PTM,Pseudo TTY Master),与用户进程交互的部分是从(PTS,Pseudo TTY Master)。

伪终端被分为主(master)从(slave)两个部分。pty slave 用于模拟硬件文本终端设备,pty master 提供了一套终端模拟器进程控制 pty slave 的方法。

终端模拟器运行于用户空间,与 pty master 交互,pty master 再通过 LDISC 与 pty slave 交互,pty slave 再与用户进程交互。

pts_pty_ldisc.png

这个图解释很多关于TTY的疑惑...

例如登录到 Linux 图形界面后打开终端窗口执行 tty,可以得到 /dev/pts/0。或者使用 ssh 登录到远程服务器执行 tty,也会得到 /dev/pts/N。这里的 pts 指的是 pty slave。

/dev/tty

Linux系统中还有一个/dev/tty,代表当前tty设备(Controlling Terminal),在当前的终端中输入:sudo echo "hello" > /dev/tty,都会直接显示在当前的终端中。

Trick: echo命令后面也可以换成别的终端设备文件,实现TTY相互之间发消息。

tty命令显示出来的dev,与/dev/tty等价。

ssh在获取密码时,是open /dev/tty来获取控制终端,然后来输出password:和获得用户输入的密码(这样可以无视重定向,密码不回显

Linux系统中stdin,stdout和stderr与tty是不同的设备:

$ pwd
/dev
$ ll std* tty
lrwxrwxrwx 1 root root   15 May 13 18:20 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root   15 May 13 18:20 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root   15 May 13 18:20 stdout -> /proc/self/fd/1
crw-rw-rw- 1 root tty  5, 0 Jun 22 17:59 tty

因此,读取/dev/tty,不能接收来自重定向的数据流。

终端的回显

你在terminal上输入的每个字符,都会在屏幕上显示出来,这个就是回显。

虽然回显很人性化,但某些特殊的场合是不希望有回显,比如当你输入密码/口令的时候,ssh和sudo就是这样。因此,终端提供了某种机制,使得程序能够控制“回显”的启用/禁用。

对于大多数终端,可以用Ctrl-S禁用“回显”,然后用Ctrl-Q启用“回显”。如果你在禁用“回显”的情况下输入一些文本,当你重新启用“回显”的瞬间,这些文本会一起出现在屏幕上。

那么,为啥要用这两个快捷键来控制“回显”呢?这又要说到【电传打字机】了。

由于这玩意儿最早的输出是【打印纸】,其速率比较【慢】。一旦“对方发送字符的速率”高于“自己这边的打印速率”,就需要向对方发一个控制信号,让对方暂停发送;等到自己这边打印完了,再发送另一个控制字符,通知对方继续(上述这种玩法,通信领域行话称之为“流量控制/流控”)。当年用来表示“暂停发送”的控制字符,对应的就是Ctrl-S;用来“恢复发送”的控制字符,也正是Ctrl-Q

也可以通过程序控制回显,Python标准库中的getpass模块内,有实现控制terminal回显的代码。

TTY历史

TTY是TeleTYpe printer的简写,有人翻译为电传打印机,它是电报时代的产物。对,你没看错,在还没有计算机的时候,TTY就有了。

在计算机诞生之前(二战前),电报属于高科技的玩意儿——它能够瞬间把信息传送到另一个城市(甚至传送到大洋彼岸)。当年的电报线路,是以字符为单位发送信息。在线路两端使用电传打字机,就可以自动地把对方发过来的字符打印出来。

TTY在电报时代,连接的是纸带,在TTY上输入,在纸带上输出!

tty

后来将TTY连接到计算机上,从此作为计算机的输入设备!

再说说终端(Terminal)这个词。

“终端”一词,洋文称之为“Terminal”。有时候又被称作 TTY,而 TTY 这个简写就来自刚才介绍的电传打字机(teletype printer)。因为早期的大型机,其“终端”就是电传打字机。那时候的终端,也称作硬件终端。

为啥会有“终端”这个概念呢?你依然需要了解历史的变迁。

最早期的计算机(大型机)是单任务滴——也就是说,每次只能干一件事情。到了60年代,出现了一个革命性的飞跃——发明了多任务系统,当时叫做“time-sharing”(分时系统)。有了“分时系统”,就可以让多个人同时使用一台大型机。而为了让多个人同时操作这台大型机,就引入了终端的概念。每一台大型机安装多个终端,每个操作员都在各自的终端上进行操作,互不干扰。“终端”的好处不光是“多任务”,而且还可以让用户在远程进行操作。

terminal

如今,“终端”一词的含义已经扩大了——用来指:基于【文本】的输入输出机制。TTY和Terminal基本上就是同义词。

有了以上TTY和终端的历史知识,现在来八卦一下回车和换行的历史。

稍微懂点 IT 的同学,应该都听说过“回车/换行”,洋文分别称之为carriage returnline feed。在编程领域,这两个字符简称为 \r\n

为啥会有这么两个玩意儿捏?

因为在电传打字机时代,当打印完一行之后,需要用一个控制命令把“打印头”复位(移到打印纸的左边),然后再用另一个控制命令把“打印头”往下移动一行。很自然地,这俩动作就对应了两个控制字符(CR & LF),也就是所谓的“回车 & 换行”。如果你去留意一下 ASCII 字符表的开头部分,前面那32个字符都是控制字符,很多都源于遥远的电报时代。

console控制台

早期的计算机是单任务的,只有一个操作界面空间,这个界面包括了TTY,那就是控制台,console。(console当时包含的范围比TTY大)

console

后来计算机有了分时功能,可以多任务,多TTY或Terminal,因此console这个概念现在也有的时候用来指直接连接到电脑上的TTY。

假设你的Linux系统没安装图形界面(或者默认不启用图形界面),当系统启动完成之后,你会在屏幕上看到一个文本模式的登录提示。这个界面就是 virtual console 的界面。在默认情况下,Linux 内置了6 virtual console 用于命令行操作,使用Ctrl+Alt+Fn进行切换。

所谓virtual console,就是可以多个不同的console共享同一套键盘和显示器。

tty_kernel.png

Controlling Terminal

保存一些网络上看到的文字,原文连接已经无法访问了,感觉这些都是比较古老的东西...老而不死的......有了前面两张图,controlling terminal是什么,基本上一目了然...

One of the attributes of a process is its controlling terminal. Child processes created with fork inherit the controlling terminal from their parent process. In this way, all the processes in a session inherit the controlling terminal from the session leader. A session leader that has control of a terminal is called the controlling process of that terminal.

controlling terminal是进程的一个属性,也是真实存在于系统中的,通过fork创建的子进程会继承父进程的controlling terminal。

You generally do not need to worry about the exact mechanism used to allocate a controlling terminal to a session, since it is done for you by the system when you log in.

login后,系统自动分配controlling terminal。

在setsid函数接口的说明中:

The setsid function creates a new session. The calling process becomes the session leader, and is put in a new process group whose process group ID is the same as the process ID of that process. There are initially no other processes in the new process group, and no other process groups in the new session.

**This function also makes the calling process have no controlling terminal. **

setsid函数接口用来创建一个新的session,调用此接口的进程就是session leader,并且调用此接口后,进程没有controlling terminal。

A process can have at most one controlling terminal, which is the primary means for interacting with the operator in text mode.

Note: For graphical programs, the controlling terminal is typically not the primary means of interaction, but it still can be used for controlling the process, e.g. pausing or terminating it.

一个进程最多只能有一个controlling terminal,这个terminal是与用户在文本模式下交互的主要手段。图形界面的程序,也可以有controlling terminal。

A controlling terminal is assigned to a whole session, which consists of one or more process groups. Only one group can read and write from/to the terminal at one time; such group is called the foreground group; others are said to be at the background. The shell allows the user to switch between the groups.

When a process starts, it inherits the controlling terminal from its parent, as well as it inherits a process group (and therefore a session).

进程启动后继承controlling terminal,同时也继承process group,因此形成所谓的session。

Terminal and the shell: When a process is started from the shell, it is typically in the foreground group (but the user can explictly request otherwise), and its standard input, standard output and standard error output are attached to the controlling terminal (but the user can redirect these streams as needed).

A process can query its controlling terminal via the /dev/tty file.

A process itself can close and redirect its standard input and outputs, but it stays bound to its controlling terminal. If it needs to disconnect from the controlling terminal, it has to leave its session, which is accomplished with the setsid() call.

进程解除与controlling terminal的绑定,只能通过调用setsid接口离开session的方式。

If a process has no controlling terminal (i.e. it is in a session that has no controlling terminal), it can acquire one by open()-ing a terminal file without the O_NOCTTY flag.

进程如果没有controlling terminal,可以自己open一个。

看到这里,sshpass的原理似乎已经跃然纸上!

Certain input sequences from the controlling-terminal cause signals to be sent to all processes in the process-group for the controlling-terminal (see termio(7)). The controlling-terminal plays a special role in handling quit and interrupt signals (see ``Special characters'').

When a session-leader without a controlling-terminal opens a terminal-device-file and the flag O_NOCTTY is clear on open, that terminal becomes the controlling-terminal assigned to the session-leader if the terminal is not already assigned to some session (see open(2)). When any process other than a session-leader opens a terminal-device-file, or the flag O_NOCTTY is set on open, that terminal does not become the controlling-terminal assigned to the calling-process.

只有session leader打开的才是controlling terminal。

A controlling-terminal distinguishes one of the process-groups in the session assigned to it as the foreground process-group; all other process-groups in the session are background process-groups. By default, when the session-leader acquires a controlling-terminal, the process-group of the session-leader becomes the foreground process-group of the controlling-terminal. The foreground process-group plays a special role in handling signal-generating input characters (see Special characters above).

When a session-leader terminates, the current session relinquishes the controlling-terminal allowing a new session-leader to acquire it. Any further attempts to access the terminal by other processes in the old session may be denied and treated as if modem-disconnect was detected on the terminal.

本文链接:https://cs.pynote.net/sf/linux/shell/202111273/

-- EOF --

-- MORE --