Total Pageviews

Sunday, 21 July 2019

终端模拟器 (Terminal Emulator)与tty与Shell


随着计算机的进化,我们已经见不到专门的终端硬件了,取而代之的则是键盘与显示器。
但是没有了终端,我们要怎么与那些传统的、不兼容图形接口的命令行程序(比如说 GNU 工具集里的大部分命令)交互呢?这些程序并不能直接读取我们的键盘输入,也没办法把计算结果显示在我们的显示器上……(图形界面的原理我这里就不多说了,它们编程的时候图形接口还在娘胎里呢!)
这时候我们就需要一个程序来模拟传统终端的行为,即 终端模拟器 (Terminal Emulator)。
严格来讲,Terminal Emulator 的译名应该是「终端仿真器」。
对于那些命令行 (CLI) 程序,终端模拟器会「假装」成一个传统终端设备;而对于现代的图形接口,终端模拟器会「假装」成一个 GUI 程序。一个终端模拟器的标准工作流程是这样的:
  1. 捕获你的键盘输入;
  2. 将输入发送给命令行程序(程序会认为这是从一个真正的终端设备输入的);
  3. 拿到命令行程序的输出结果(STDOUT 以及 STDERR);
  4. 调用图形接口(比如 X11),将输出结果渲染至显示器。
终端模拟器有很多,这里就举几个经典的例子:
  • GNU/Linux:gnome-terminal、Konsole;
  • macOS:Terminal.app、iTerm2;
  • Windows:Win32 控制台、ConEmu 等。
终端模拟器:Hyper 与 wsl-terminal
在专门的终端硬件已经基本上仅存于计算机博物馆的现代,人们通常图省事儿,直接称呼终端模拟器为「终端」。

终端窗口 (Terminal Window) 与虚拟控制台 (Virtual Console)
大部分终端模拟器都是在图形用户界面 (GUI) 中运行的,但是也有例外。
比如在 GNU/Linux 操作系统中,按下 Ctrl + Alt + F1,F2…F6 等组合键可以切换出好几个黑不溜秋的全屏终端界面,而按下 Ctrl + Alt + F7 才是切换回图形界面。不过不要被它们唬着了,虽然它们并不运行在图形界面中,但其实它们也是终端模拟器的一种。
一个正在显示系统启动信息的虚拟控制台(图片来源:hacktolive.org,GPLv2)
这些全屏的终端界面与那些运行在 GUI 下的终端模拟器的唯一区别就是它们是 由操作系统内核直接提供的。这些由内核直接提供的终端界面被叫做 虚拟控制台 (Virtual Console),而上面提到的那些运行在图形界面上的终端模拟器则被叫做 终端窗口 (Terminal Window)。除此之外并没有什么差别。
当然了,因为终端窗口是跑在图形界面上的,所有如果图形界面宕掉了那它们也就跟着完蛋了。这时候你至少还可以切换到 Virtual Console 去救火,因为它们由内核直接提供,只要系统本身不出问题一般都可用(笑)。

那么 TTY 又是什么?

简单来说,tty 就是终端的统称。
为什么呢?看了上面的 2.1 节的同学应该知道,最早的 Unix 终端是 ASR-33 电传打字机。而电传打字机 (Teletype / Teletypewriter) 的英文缩写就是 tty,即 tty 这个名称的来源。
由于 Unix 被设计为一个多用户操作系统,所以人们会在计算机上连接多个终端(在当时,这些终端全都是电传打字机)。Unix 系统为了支持这些电传打字机,就设计了名为 tty 的子系统(没错,因为当时的终端全都是 tty,所以这个系统也被命名为了 tty,就是这么简单粗暴),将具体的硬件设备抽象为操作系统内部位于 /dev/tty* 的设备文件。
为什么要把电传打字机这个硬件设备抽象成「tty 设备」文件呢?有兴趣的同学可以去了解一下 Unix 操作系统中 Everything is a file 的概念。
▲ 还记得上面我们说过的特殊的终端,也就是通过 Ctrl + Alt + F1-6 呼出的那些虚拟控制台 (Virtual Console) 吗?其对应的就是上图中的 tty1 到 tty6
随着计算机的发展,终端设备已经不再限制于电传打字机,但是 tty 这个名称还是就这么留了下来。久而久之,它们的概念就混淆在了一起。所以在现代,tty 设备就是终端设备,终端设备就是 tty 设备,无需区分。
由于早期计算机上的 串行端口 (Serial Port) 最大的用途就是连接终端设备,所以当时的 Unix 会把串口上的设备也同样抽象为 tty 设备(位于 /dev/ttyS*)。因此,现在人们也经常将串口设备称呼为 tty 设备。
在 tty 子系统中后来还衍生出了 pty、ptmx、pts 等概念,这里就不详细展开了。有兴趣的同学可以参考一下这篇文章:Linux TTY/PTS 概述

Shell —— 提供用户界面的程序

大家都知道,操作系统有一个叫做 内核 (Kernel) 的东西,它管理着整台计算机的硬件,是现代操作系统中最基本的部分。但是,内核处于系统的底层,是不能让普通用户随意操作的,不然一个不小心系统就崩溃啦!
但我们总还是要让用户操作系统的,怎么办呢?这就需要一个专门的程序,它接受用户输入的命令,然后帮我们与内核沟通,最后让内核完成我们的任务。这个提供用户界面的程序被叫做 Shell (壳层)。
其实 Shell 只是提供了一个用户操作系统的入口,我们一般是通过 Shell 去调用其他各种各样的应用程序,最后来达成我们的目的。比如说我们想要知道一个文件的内容,我们会在 Shell 中输入命令 cat foo.txt,然后 Shell 会帮我们运行 cat 这个程序,cat 再去调用内核提供的 open 等系统调用来获取文件的内容。虽然并不是 Shell 直接去与内核交互,但广义上可以认为是 Shell 提供了与内核交互的用户界面。
至于为什么叫做 Shell,看下图就知道啦。是不是很像一层壳呢?

Shell 通常可以分为两种:命令行 Shell 与 图形 Shell。顾名思义,前者提供一个命令行界面 (CLI),后者提供一个图形用户界面 (GUI)。Windows 下的 explorer.exe 就是一个典型的图形 Shell(没错,它确实是,因为它接受来自你的指令,并且会帮你与内核交互完成你的指令)。
常见或历史上知名的命令行 Shell 有:
  • 适用于 Unix 及类 Unix 系统:
    • sh (Bourne shell),最经典的 Unix shell;
    • bash (Bourne-Again shell),目前绝大多数 Linux 发行版的默认 shell;
    • zsh (Z shell),我个人最喜欢的 shell;
    • fish (Friendly interactive shell),专注于易用性与友好用户体验的 shell;
  • Windows 下的 cmd.exe (命令提示符) 与 PowerShell
还有其他各种五花八门的 Shell 程序,这里就不一一列举了,有兴趣的自己去搜一搜吧。

Shell 与终端的分工
现在我们知道,终端干的活儿是从用户这里接收输入(键盘、鼠标等输入设备),扔给 Shell,然后把 Shell 返回的结果展示给用户(比如通过显示器)。而 Shell 干的活儿是从终端那里拿到用户输入的命令,解析后交给操作系统内核去执行,并把执行结果返回给终端。
不过 Shell 与终端的分工有一些容易混淆的地方,这里以例子进行说明:
  • 终端将用户的键盘输入转换为控制序列(除了字符以外的按键,比如 左方向键 → ^[[D),Shell 则解析并执行收到的控制序列(比如 ^[[D → 将光标向左移动);
  • 不过也有例外,比如终端在接收到 Ctrl + C 组合键时,不会把这个按键转发给当前的程序,而是会发送一个 SIGINT 信号(默认情况下,这会导致进程终止)。其他类似的特殊组合键有 Ctrl-Z 与 Ctrl-\ 等,可以通过 stty -a 命令查看当前终端的设置。
  • Shell 发出类似「把前景色改为红色(控制序列为 \033[31m)」「显示 foo」等指令;
  • 终端接收这些指令,并且照着 Shell 说的做,于是你就看到了终端上输出了一行红色的 foo
  • 除非被重定向,否则 Shell 永远不会知道它所执行命令的输出结果。我们可以在终端窗口中上下翻页查看过去的输出内容,这完全是终端提供的 feature,与 Shell 没有半毛钱关系;
  • 命令提示符 (Prompt) 是一个完全的 Shell 概念,与终端无关;
  • 行编辑、输入历史与自动补全等功能是由 Shell 提供的(比如 fish 这个 Shell 就有着很好用的历史命令与命令自动补全功能)。不过终端也能自己实现这些功能,比如说 XShell 这个终端模拟器就可以在本地写完一行命令,然后整条发送给远程服务器中的 Shell(在连接状况不佳时很有用,不然打个字都要卡半天);
  • 终端中的复制粘贴功能(Shift + Insert 或者鼠标右键等)基本上都是由终端提供的。举个例子,Windows 默认的终端对于复制粘贴的支持很屎,而换一个终端(例如 ConEmu)后就可以很好地支持复制粘贴。不过 Shell 以及其他命令行程序也可以提供自己的复制粘贴机制(例如 vim)。

6. 总结

计算机史这玩意,有趣是挺有趣的,就是查起资料来太费脑子。
为了不误人子弟,在这篇博文写作的过程中我也查阅了各种各样的文档和史料,力求内容的准确性。

7. 参考链接

No comments:

Post a Comment