1 终端

1.1 名词

在详细讲述下面的知识点之前, 先简单了解下各个终端相关名词

  • CLI: 命令行, 图形界面普及之前使用最为广泛的用户界面, 用户通过键盘输入指令来告知计算机
  • Terminal: 一种用来让用户输入数据到计算, 并回显计算结果的机器, 一个IO交互设备
  • Console: 一种特殊的终端
  • Terminal Emulator: 终端模拟器, 利用程序模拟终端行为, 例如iterm2
  • TTY: 终端的统称, Terminal等价于 TTY 等价于文本输入,输出环境
  • Shell: 命令行解释器, 执行用户输入的命令并返回结果

1.2 哑终端和控制台

最早期的大型机和小型机时代, 计算机是庞大的并安置在特殊的房间中, 此时通过某些设备与计算机
进行交互, 这些设备就是最早期的终端.
这些早期的终端一般使用电传打字机(Teletype)的设备来连接计算机, 从而确保每个用户都能通过
终端登录并操作主机, 这是 UNIX 系统创建早期为了解决多用户登录而采取的一种较为廉价的方式.
因为当时计算机设备非常昂贵, 并且键盘和主机是集成在一起的, 没有独立的键盘, 所以使用ASR-33
发送信号给计算机, 并且将结果回传打印到纸袋(注意, 是纸袋哦)上.

ASR-33 电传打字机

这些终端被称为哑终端, 是一种相较于其他聪明的终端而言, 不执行删除, 清屏, 控制光标等等操作.

在早期的计算中, 管理员使用一种特殊的终端来管理主机, 其和计算机主机是一体的, 有着比普通
终端更大的权限, 一台计算机只能有一个控制台, 但是有多个终端, 此时终端和控制台还是两个不同
的物体.
下文就是一台console终端, 左边为console, 右边为terminal:

console和terminal

但是, 时代在进步, 随着个人计算机的普及, console和terminal的概念逐渐模糊. 在现在看来, 键盘
和显示器既可以认为是console, 也可以认为是普通终端.

  • 管理系统时: 键盘和显示器就是控制台
  • 浏览网页,编辑等时: 键盘和显示器就是终端

两者现在是一个统一的概念.

1.3 基于字符和图形终端

终端设备一直在不断的发展, 根据历史发展我们总结终端:

  1. 字符终端: 文本终端, 仅仅接收和显示文本信息的终端, 分为哑终端(dump)和智能终端(intelligent)
  2. 基于字符的图形终端: 不仅仅可以显示文本信息, 还可以显示图形和图像, 进行复杂的操作
  3. 运行窗口系统的图形终端: 现在我们使用的窗口系统

下面就是一个较为古老的字符终端设备:VT100, 其是第一批支持ANSI转移序列和光标控制的智能终端

DEC VT100

1.4 终端模拟器

那么, 我现在正在使用的iterm2, 以及网络上常见的各种终端应用是属于哪一类终端呢?
上面所述的终端都是硬件外设终端, 但是现在都被键盘显示器替代了, 那么开发者如何使用命令行
和计算机交互呢?
为了解决这些问题, 终端模拟器(Terminal Emulator), 一种模拟传统终端行为的程序被开发出来, 终端
模拟器在整个计算机交互中担当的老角色:

  • CLI 程序: 终端模拟器假装成传统的终端设备
  • 图形窗口: 终端模拟器假装成GUI程序

一个终端模拟器的工作流程如下, 实际上充当代理的角色:

  • 捕捉键盘输入
  • 将键盘输入发送给命令行程序
  • 获取命令行输出结果(STDOUT/STDERR)
  • 调用图形接口, 比如X11, 将输出回显到显示器上

1.5 虚拟控制台

当你登录 LINUX 图形界面系统之后, 可以按下组合键CTRL + ALT + F1~F6来切换到6 个类似原始终端的
全屏界面, 实际上这些也是终端模拟器的一种, 它被称为虚拟控制台.
如果你使用云服务器, 当你使用网络终端登录到远程服务器时, 你可以看到tty1~tty6六个父进程为init
的终端进程, 这些就是虚拟控制台进程.

  • 虚拟控制台: 有操作系统内核直接提供的终端界面
  • 终端窗口: 运行在图形界面上的终端界面

这两者都是终端模拟器, 他们的原理是类似的.

2 tty和pts

注意, 本章节的内容大多引用同一篇文章:Linux TTY/PTS概述因为作者实在写的太好了, 我这边就直接抄了好些内容.

2.1 tty演变和使用

1.首先, 在多任务的计算机出现之前, 人们就已经使用 Teletype来进行消息传递了, 有点类似点对点功能.

1
2
3
+----------+    +------+    Physical Line    +------+   +----------+
| teletype +----+ modem+---------------------+ modem+---+ teletype |
+----------+ +------+ A->B B->A +------+ +----------+

2.其次, 在多任务计算机出现之后, 键盘和显示器等现代高级终端出现之前, 将廉价简易的Teletype作为
计算机终端以支持多用户登录和操作(计算机大部分技术发展都是这种向后兼容, 基于前者发展).

1
2
3
4
5
6
7
8
9
10
11
+----------+   +-------+     Physical Line
| Terminal +<->+ Modem +<------------------+
+----------+ +-------+ |
| +----------+
| +-------+ +------+ | |
+---->+ Modem +<->+ UART +<->+ Computer |
| +-------+ +------+ | |
| +----------+
+----------+ +-------+ Physical Line |
| Terminal +<->+ Modem +<------------------+
+----------+ +-------+

注意, 这里 Modem 就是以前我们了解的(调制解调器), UART可以理解为将teletype的信号转换成计算
机能识别的信号的设备.

3.最后, 计算机如何支持Teletype设备? 他们怎样进行通信的呢? 如何查看当前计算支持的 TTY 设备?

计算机为了支持teletype, 设计了TTY 子系统, 以便能够外部中断设备进行通信, 其简单框架如下:

1
2
3
4
5
6
7
8
9
10
11
              +-----------------------------------------------+
| Kernel |
| +--------+ |
+--------+ | +--------+ +------------+ | | | +----------------+
| | | | UART | | Line | | TTY +<---------->+ User process A |
|Teletype+------->+ +<->+ +<->+ | | +----------------+
| | | | driver | | discipline | | driver +<---------->+ User process B |
+--------+ | +--------+ +------------+ | | | +----------------+
| +--------+ |
| |
+-----------------------------------------------+

这里省略了Modem等信息. Line discipline对输入和输出做处理, tty driver就是后面要讲解的
tty设备, 在文件中讲过unix的万物皆文件
概念, tty设备在 UNIX 环境下也是一个个独立的文件, 具体见ls -l /dev/tty*. 用户进程就是
通过tty设备和内核进行input/output信息的交换工作的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                     +----------------+
| TTY Driver |
| |
| +-------+ | +----------------+
+------------+ | | |<---------->| User process A |
| Terminal A |<--------->| ttyS0 | | +----------------+
+------------+ | | |<---------->| User process B |
| +-------+ | +----------------+
| |
| +-------+ | +----------------+
+------------+ | | |<---------->| User process C |
| Terminal B |<--------->| ttyS1 | | +----------------+
+------------+ | | |<---------->| User process D |
| +-------+ | +----------------+
| |
+----------------+

当外部中断设备请求连接时, 驱动会根据型号参数创建相应的tty设备, 之后中断设备就能通过
该设备文件ttyS0来运行应用, 比如启动用户进程 A 等等, ttyS0在这之间起到代理作用, 其信息交换
图如下:

1
2
3
4
5
6
+--------+   Input    +--------------------------+    R/W     +------+
| +----------->+ +<---------->+ bash |
|terminal| | pts/1 or ttys001 | +------+
| +<-----------+ +<---------->+ lsof |
+--------+ Output | Foreground process group | R/W +------+
+--------------------------+
  • Foreground process group: 记录当前前台进程组
  • Input(比如键盘)输入时, pts会获取当前前台进程组首会话, 并将输入放入该进程输入缓存
  • pts设备发现数据输入时, 会将相应数据回传Output(显示器)

在这中间, 可能一个tty会运行多个进程(比如多个后台进程), 此时如果有 IO 中断, 则需要进行一定的
阻塞. 这里的pts实际上为网络终端登录技术兴起后产生的, 其和tty的区别有如下几点:

  • 本地和远程: pts是远程网络登录, 比如sshd等, 而tty一般为计算机直连设备或者本机模拟设备
  • 远端: pts远程连接ptmx, tty的直接连接内核的终端模拟器, 但两者都负责维护会话和转发数据包.
  • 本地端: ptmx的另一端连接着用户空间应用程序(比如ssh), 内核终端模拟器的另一端连接着具体的硬件

关于进程组以及首会话, 见笔记进程会话
ptmx就是伪终端-pseudo terminal, 现在让我们看下系统中各个tty设备输出, 并且进行一定的测试来
验证上面的结论.

1
2
# 1. 查看macos下所有tty设备
ps -ef|grep ttys|grep -v grep|grep -v zsh

其输出如下:

1
2
3
4
5
6
7
501 67156 67154   0  9:32上午 ??         0:00.01 sshd: bamboounuse@ttys005
====================================1====================================
501 56192 278 0 二05下午 ttys000 0:00.10 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp bamboounuse
0 56193 56192 0 二05下午 ttys000 0:00.37 login -fp bamboounuse
====================================2====================================
0 67348 67346 0 9:33上午 ttys006 0:00.06 login -pf bamboounuse
====================================3====================================

关于login进程, 会在后面第三章的终端登录和网络登录中讲解. 上面三种不同类型的输出:

  • 第一种类型是远端xshell客户端通过ssh连接本机, 可以看出sshd进程
  • 第二种类型是本机item2打开的shell终端
  • 第三种类型是本机系统自带terminal打开的shell终端

现在我们获取这些ttys进程相关的设备, 以便进行IN/OU测试:

1
2
3
4
5
6
7
# 1. 获取进程
ps -ef|grep ttys005|grep -v grep
# 2. 查看进程关联的设备信息, 可以看到/dev/ptmx最后打开了/dev/ttys005设备.
# 如果是Linux, 则可以使用-c <program name>来更快的查看
sudo lsof -p <pid>
# 3. 查看终端设备打开的文件, 可以看到打开了文件描述符: 0, 1, 2, 10
lsof /dev/ttys005

在找到各个 tty 设备后, 现在开始进行输入输出验证:

1
2
3
4
5
6
# 1. 在ttys001上面进行上面终端 INPUT/OUTPUT 的验证工作.
echo "我是ttys001, 我现在向ttys005进行输入" >> /dev/ttys005
# 此时可以看到远端的另外一台机器xshell终端上打印上面的文字信息

# 2. 类似上面, 在另外给终端也可以看到类似输出
echo "我是ttys001, 我现在向ttys006 terminal原始终端输入" >> /dev/ttys006

现在让我们看下远端云服务器上的tty输出:

1
2
3
4
5
# 1. 获取当前tty, 可以看到是pts
tty

# 2. 查看ubuntu默认开启的6 个终端模拟器(见 1.4 节), 他们都使用getty程序启动
ps -ef|grep tty

另外, MacOS不同于ubuntu, 后者的tty001~tty006在开机之后就自动的创建, 当然, 如果没有使用
Ctrl + Alt + F1~F6的话, 实际上找不到对应login进程.

2.2 tty的创建逻辑

2.2.1 键盘直连

键盘,显示器和内核中的终端模拟器直连, 由模拟器根据输入来决定启动那个tty设备. 比如, 输入
ctrl+alt+f1时, 模拟器会根据输入转发给tty1的getty程序(见第三章说明), 开启login:操作
流程, 界面会自动切换到tty1终端登录上.

1
2
3
4
5
6
7
8
9
10
11
                  +-----------------------------------------+
| Kernel |
| +--------+ | +----------------+
+----------+ | +-------------------+ | tty1 |<---------->| User processes |
| Keyboard |--------->| | +--------+ | +----------------+
+----------+ | | Terminal Emulator |<->| tty2 |<---------->| User processes |
| Monitor |<---------| | +--------+ | +----------------+
+----------+ | +-------------------+ | tty3 |<---------->| User processes |
| +--------+ | +----------------+
| |
+-----------------------------------------+

2.2.2 图形界面的键盘直连

上面所述的直连方式时直接从图形界面切换到虚拟控制台(见 1.5节)的流程, 那么如果通过item2
终端模拟器的流程是怎样的呢?
大体流程类似, 不过引入了ptmx来作为终端模拟器.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+----------+       +------------+
| Keyboard |------>| |
+----------+ | Terminal |--------------------------+
| Monitor |<------| | fork |
+----------+ +------------+ |
| ↑ |
| | |
write | | read |
| | |
+-----|---|-------------------+ |
| | | | ↓
| ↓ | +-------+ | +-------+
| +--------+ | pts/0 |<---------->| shell |
| | | +-------+ | +-------+
| | ptmx |<->| pts/1 |<---------->| shell |
| | | +-------+ | +-------+
| +--------+ | pts/2 |<---------->| shell |
| +-------+ | +-------+
| Kernel |
+-----------------------------+

关于ptmx的通信流程,见下节网络登录 SSH 的讲解. 上面两个连接方式都是基于本地的连接, 都会自动的
创建login来进行用户认证工作, 后面的网络登录则依托于网络登录协议认证机制.

2.2.3 远程登录ssh

远端客户终端通过特殊的协议来和本服务器进行连接不仅仅是通过protocolClientprotocolServer,
实际上也是基于ptmx来完成终端模拟器工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+----------+       +------------+
| Keyboard |------>| |
+----------+ | Terminal |
| Monitor |<------| |
+----------+ +------------+
|
| ssh protocol
|

+------------+
| |
| ssh server |--------------------------+
| | fork |
+------------+ |
| ↑ |
| | |
write | | read |
| | |
+-----|---|-------------------+ |
| | | | ↓
| ↓ | +-------+ | +-------+
| +--------+ | pts/0 |<---------->| shell |
| | | +-------+ | +-------+
| | ptmx |<->| pts/1 |<---------->| shell |
| | | +-------+ | +-------+
| +--------+ | pts/2 |<---------->| shell |
| +-------+ | +-------+
| Kernel |
+-----------------------------+

现在我们将建立连接消息收发分开来讨论, 详细的讲解两个过程中消息的流动. 先看一下连接是
如何建立的.

  1. Terminal根据指定协议请求连接SSHD, 若验证通过则会创建一个新的session(前段进程组)
  2. sshd充当代理人角色, 调用 API, 请求ptmx创建一个pts并创建相应的文件描述符fd
  3. 最后只要将0, 1, 2, N1描述符和session关联在一起, 即完成连接过程, 当然还伴随这shell的绑定.

那么消息收发是怎样一个流程呢?

  1. 键盘输入通过协议到达远端服务器sshd, 找到客户端session对应的fd开始进行写入(read)操作
  2. ptmx在收到消息之后会将数据包转发到对应的pts上.
  3. pts在检查数据包之后, 联系前段进程组, 将数据发完首进程(leader), 最后交由shell
  4. shell在处理输入之后, 将回传结果写入pts(write), pts转发数据包到ptmx
  5. ptmx在找到pts对应的fd, 进行数据写入操作
  6. sshd会找到对应的session将数据通过协议回传给远端客户端

关于会话, 首进程等, 见文章会话.

2.2.4 tmux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
+----------+       +------------+
| Keyboard |------>| |
+----------+ | Terminal |
| Monitor |<------| |
+----------+ +------------+
|
| ssh protocol
|

+------------+
| |
| ssh server |--------------------------+
| | fork |
+------------+ |
| ↑ |
| | |
write | | read |
| | |
+-----|---|-------------------+ |
| ↓ | | ↓
| +--------+ +-------+ | +-------+ fork +-------------+
| | ptmx |<->| pts/0 |<---------->| shell |-------->| tmux client |
| +--------+ +-------+ | +-------+ +-------------+
| | | | ↑
| +--------+ +-------+ | +-------+ |
| | ptmx |<->| pts/2 |<---------->| shell | |
| +--------+ +-------+ | +-------+ |
| ↑ | Kernel | ↑ |
+-----|---|-------------------+ | |
| | | |
|w/r| +---------------------------+ |
| | | fork |
| ↓ | |
+-------------+ |
| | |
| tmux server |<--------------------------------------------+
| |
+-------------+

3 终端登录和网络登录

3.1 终端登录

在第二章我们讲解了本地登录和网络登录过程中终端模拟器或者虚拟控制台所起到的作用, 那么
这个章节就从进程的角度来简单的了解下这些过程中都启动了哪些应用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
+------+
| init |
+--+---+
|
|fork
| +---------------------------------+
+--v---+ |Get Fd, tty message, environment.+-------------+
| init | +---------------------------------+ |
+--+---+ | +-----------------+ |
| | |new login request| |
|exec | +----------------------+ |
| | | |
+--v--------+ | +----------------------------------------+ |
| getty| | | 1 chdir. | |
+--+---+ | | 2 chown. change the owner of terminal | |
| | | 3 change permission and set shell, env | |
|exec+-----+ | 4 run shell | |
| +------+----------------------------------------+ |
+--v---+ | |
| login| | |
+--+---+ | +-----------------------------------------+
| | | ptmx or tty device.|
| | | fd: 0, 1, 2 |
+--v------+ +--------------------+
| shell| |
+-----------------+

这里本地终端登录不仅仅包含虚拟控制台, 也包含现在带计算机中的各种终端模拟器, 后者
实际上在内核中也用到ptmx伪终端来连接终端设备和shell.

3.2 网络登录

网络登录不同于本地终端登录, 其使用 Client/Server 来进行验证和通信, 所有登录都经由内核的
网络驱动程序来进行, 事先并不知晓有多少这样登录, 通过协议设置某种公开的服务来等待网络
请求的到达.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 +------+
| init |
+--+---+
|
|fork
|
+--v---+
| init | running sshd service. Waitting client
+--+---+ connect.
|
|exec
|
+--v---+ call api.
| sshd | send session id
+--+---+--------------->+---------+
| | ptmx |
|fork +---------+
| |
+---v----+ |
| session| bind session and fd
+---+-----<--------------+
|
|exec
+--v---+
| shell|
+------+

4 参考链接

文章参考:

本博客其他相关文章: