当你打开终端并输入命令时会发生什么?(下)

哈喽大家好,我是咸鱼

我们先来大致回顾一下文章《当你打开终端并输入命令时会发生什么?(上)》的内容

终端设备是由电传打字机演变过来的,电传打字机通过物理线与大型计算机连接在一块来实现输入输出

如上图,分别是二战时期的电传打字机和西门子 “Fernscheiber 100” 电传打字机

随着技术的不断发展(尤其是显示技术),带显示屏的终端设备随之诞生

而现在随着个人电脑的普及,出现了基于屏幕显示的图形用户界面(GUI),演变成了现在的计算机终端

现在的终端大多都是计算机上的一个应用程序,它们通常被称为终端模拟器,充当用户与操作系统交互的界面(比如说 Linux 中的 Xterm、Xshell,Windows 中的控制台),而不必使用专门的终端。输出系统是屏幕,输入系统是键盘

以 Linux 为例,当我们打开终端时,通常会启动一个 shell 进程,用于与用户交互。用户在终端中输入的命令将传递给 shell 进程,然后由 shell 解释和执行这些命令

这个过程包括将用户输入的命令解析为操作系统可以理解的指令,执行这些指令,并将执行结果返回给终端显示给用户

输入命令

当我们在终端中输入命令时,键盘输入的字符会被转换成相应的字符编码(比如说 backspace 键被转译成 ASCII 字符 0x08

这些字符通过终端写入到 PTY leader,接着 TTY driver 从 PTY leader 中读取字符并存储到 line discipline 中(line discipline 为 PTY 两端之间的中间缓冲区)

不但如此,line discipline 还负责解释来自 PTY leader 的字符然后根据自己的规则去处理它们(比如进行回退、删除字符等,或者处理特殊字符)

举个例子,line discipline 收到 backspace 时,它会根据自己的规则解释成 ERASE 字符,然后进行编辑,方法是删除最后一个字符

接着将删除操作返回给 PTY leader,这样终端就可以从 PTY leader 那里读取到更改并将其反映在终端显示中

需要注意的是,上面这段过程里字符还没有被写入到 PTY follower 中,只是处在【编辑】部分

当我们在键盘敲下 CTRL+C 时,line discipline 会解释成 INTR (INTERRUPT 的缩写),这时候就会向 PTY follower 发送一个 SIGINT 信号去中断在前台运行的任何进程

如果不是特殊字符(比如输入 ls),line discipline 会将字符返回给 PTY leader,终端程序读取并显示在屏幕上,这就是为什么你在键盘敲一个字符,显示器就会显示一个字符(echo 功能)

现在 shell 进程也会缓冲用户的输入,以实现一些高级的功能:比如命令历史记录或 tab 键自动补全

执行并解析命令

当我们输入完命令之后,就要按下回车键(Enter)来执行命令了

一旦按下回车键,line discipline 解释为换行字符(newline),通常表示为 NL

然后一并将用户的命令转发到 PTY follower ,而 shell 进程跟 follower 相连,shell 拿到命令之后就会去解析并执行

当 shell 进程接收到用户的输入和换行符时,它会开始解析并执行命令。这个过程包括命令解析、查找可执行文件或内置命令,以及执行相应的操作

首先对命令解析成一个一个 token 并进行语法/语义分析,以 ls 命令为例:

  • ls > foo.txt :正确
  • ls > :语法不正确, > 后面缺少内容
  • ls | foo.txt :语义不正确,管道的两端都需要是可运行的进程

然后接着解析那些不是 shell 关键字或者路径的 token,shell 需要知道这些 token 的含义,所以 shell 会去根据下面几个部分去递归查找 token 引用的内容:

  • aliases:命令别名,通常用于缩写复杂的命令(例如 alias ll="ls -lh"
  • function:函数
  • environment variables:环境变量
  • builtins:shell 内嵌命令(例如 cd pwd exit kill
  • PATH executables:shell 可以找到(通过 $PATH 变量)并运行的外部命令

我们可以通过 type 命令知道对应的类型

1
2
3
4
5
6
7
8
[root@minion1 ~]# type ll
ll 是 'ls -l --color=auto' 的别名

[root@minion1 ~]# type cd
cd 是 shell 内嵌

[root@minion1 ~]# type python
python 是 /usr/bin/python

与 shell 内嵌命令不同的是,可执行命令是单独的程序或脚本文件,具有执行权限,可以作为单独的进程执行

当用户在 shell 中输入一个命令时,shell 会查找可执行文件的位置,并在找到后创建一个新的子进程来运行该可执行文件,并将相应的命令参数传递给这个新的子进程

我们可以通过 pstree 命令来查看进程之间的关系

返回输出

当 shell 执行完命令之后,把生成的输出写入到 PTY follower ,接着传到 line discipline 中,line discipline 不会处理这些输出,而是转发给 PTY leader,然后终端就会读取并显示到屏幕上

参考文章:https://www.warp.dev/blog/what-happens-when-you-open-a-terminal-and-enter-ls