随着计算机的进化,我们已经见不到专门的终端硬件了,取而代之的则是键盘与显示器。
但是没有了终端,我们要怎么与那些传统的、不兼容图形接口的命令行程序(比如说 GNU 工具集里的大部分命令)交互呢?这些程序并不能直接读取我们的键盘输入,也没办法把计算结果显示在我们的显示器上……(图形界面的原理我这里就不多说了,它们编程的时候图形接口还在娘胎里呢!)
这时候我们就需要一个程序来模拟传统终端的行为,即 终端模拟器 (Terminal Emulator)。
对于那些命令行 (CLI) 程序,终端模拟器会「假装」成一个传统终端设备;而对于现代的图形接口,终端模拟器会「假装」成一个 GUI 程序。一个终端模拟器的标准工作流程是这样的:
- 捕获你的键盘输入;
- 将输入发送给命令行程序(程序会认为这是从一个真正的终端设备输入的);
- 拿到命令行程序的输出结果(STDOUT 以及 STDERR);
- 调用图形接口(比如 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*
的设备文件。
▲ 还记得上面我们说过的特殊的终端,也就是通过 Ctrl + Alt + F1-6 呼出的那些虚拟控制台 (Virtual Console) 吗?其对应的就是上图中的
tty1
到 tty6
。
随着计算机的发展,终端设备已经不再限制于电传打字机,但是 tty 这个名称还是就这么留了下来。久而久之,它们的概念就混淆在了一起。所以在现代,tty 设备就是终端设备,终端设备就是 tty 设备,无需区分。
在 tty 子系统中后来还衍生出了 pty、ptmx、pts 等概念,这里就不详细展开了。有兴趣的同学可以参考一下这篇文章:Linux TTY/PTS 概述。
Shell —— 提供用户界面的程序
大家都知道,操作系统有一个叫做 内核 (Kernel) 的东西,它管理着整台计算机的硬件,是现代操作系统中最基本的部分。但是,内核处于系统的底层,是不能让普通用户随意操作的,不然一个不小心系统就崩溃啦!
但我们总还是要让用户操作系统的,怎么办呢?这就需要一个专门的程序,它接受用户输入的命令,然后帮我们与内核沟通,最后让内核完成我们的任务。这个提供用户界面的程序被叫做 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. 参考链接
- 命令行界面 - Wikipedia
- What is the exact difference between a ‘terminal’, a ‘shell’, a ‘tty’ and a ‘console’?
- Why is a virtual terminal “virtual”, and what/why/where is the “real” terminal?
- 终端,Shell,“tty” 和控制台(console)有什么区别? - 知乎
- 你真的知道什么是终端吗? - Linux 大神博客
- 终端 - Wikipedia
- Terminal emulator - Wikipedia
- console(4): console terminal/virtual consoles - Linux man page
- Linux TTY/PTS 概述 - SegmentFault
- Linux TTY framework(1)_基本概念
- Shell (computing) - Wikipedia
- 学习 bash shell - 鸟哥的 Linux 私房菜
- Ubuntu Manpage: 控制终端代码 - Linux 控制终端转义和控制序列
No comments:
Post a Comment