当你打开终端并输入命令时会发生什么?(下)
哈喽大家好,我是咸鱼
我们先来大致回顾一下文章《当你打开终端并输入命令时会发生什么?(上)》的内容
终端设备是由电传打字机演变过来的,电传打字机通过物理线与大型计算机连接在一块来实现输入输出
如上图,分别是二战时期的电传打字机和西门子 “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
命令知道对应的类型
[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