shell交互逻辑分析

在 terminal emulator(终端模拟器)中启动bash(shell)子进程时,它把bash子进程的0/1/2(标准输入/输出/错误输出)文件描述符(以此 fd 找到对应的 file 实例)都指向自己(真正指向的是 terminal emulator 向内核分配的PTY数据通道的slave端)。这样当bash做标准输入输出操作时,它其实都是在和 terminal emulator交互。

bash 本身不处理键盘事件,也不负责输出,只是解释用户输入。

当bash要执行某个程序时,一般语言编译出的目标程序,也会把标准输入/输出/错误输出分别定义为0/1/2。

在bash中运行程序时,默认情况下,子进程的0/1/2文件描述符指向是继承自bash的,所以也同样都指向了同一个 terminal emulator。

严格来讲,这里讲的是 pseudo terminal emulator 伪终端 (PTY), 与运行在内核的 terminal emulator 不同。PTY 运行在用户空间,通过内核中的数据通道,建立进程间的数据交互。

内核的 terminal emulator 读取键盘驱动传递的数据,向显卡驱动发送字符。通过 tty 驱动与用户进程相连。

pseudo terminal emulator 运行在用户空间,此时的 tty 驱动只负责 pseudo terminal emulator 进程与 shell 进程的交互,维护一个数据交互通道。而 pseudo terminal emulator 仍然与键盘或者显卡驱动交互。

当用户输入时,字符会被回传到 PTY master。在 terminal emulator 输入时,会在指定内存中缓冲这些字符。当用户按回车键时,它才将这些字符发送到 PTY slave。

terminal emulator 直接执行程序

在 terminal emulator 中执行程序的过程可以简化如下:

  1. 在terminal emulator中输入 ./a.out 命令,该命令沿着内核PTY数据通道,到达bash的标准输入。
  2. bash从标准输入中读取 ./a.out 命令,然后调用fork函数,新建一个子进程,用于执行 a.out 程序。
  3. 子进程的 标准输入/输出/错误输出 文件描述符继承自bash(图中的虚线表示在上述程序中没有数据传递)。
  4. a,out 程序执行时,会先向标准输出写 Hello 字符串,然后再向标准错误输出写world字符串。
  5. 这两个字符串会传递给内核PTY数据通道。terminal emulator从PTY master fd中读取这些字符串,并显示在界面上。

上述例子中,a.out 进程的0/1/2文件描述符,都是指向内核PTY数据通道的slave端,并且通过该PTY数据通道和terminal emulator交互。

ssh 连接远程终端执行程序

  1. 在terminal emulator中输入./a.out命令后,该命令会沿着内核PTY数据通道,到达ssh进程的标准输入。
  2. ssh进程从标准输入中读取到./a.out命令,然后将其写到socket fd里。
  3. 该命令会沿着socket fd指向的tcp连接,到达机器2的对应socket端。
  4. 在机器2上,sshd进程从它的socket fd中读取到./a.out命令,然后将其写到PTY master fd中。
  5. 该命令又会沿着机器2的内核PTY数据通道,到达bash进程的标准输入。
  6. 机器2上的bash进程,从标准输入中读到该命令,然后调用fork函数,创建一个子进程,用于执行a.out程序。
  7. a.out程序执行时,会写hello到标准输出,写world到标准错误输出,这两个字符串又会沿着机器2的内核PTY数据通道,到达sshd进程的PTY master fd。
  8. sshd进程从PTY master fd中读取到a.out进程输出的内容,并写到socket fd里。
  9. 该数据又沿着socket fd指向的tcp连接,最终会到达机器1对应的socket端。
  10. 机器1中的ssh进程,从socket fd里读取到a.out程序的输出内容,并将其写到标准输出。
  11. 该数据会沿着机器1的内核PTY数据通道,到达terminal emulator的PTY master fd。
  12. terminal emulator从PTY master fd中读取到对应的数据,最终将其显示在机器1屏幕上。

a.out 进程的 0 1 2 文件描述符都是指向机器2中 PTY slave 端,通过 tcp 链路与机器1 中 terminal emulator 相连。

机器1中对 terminal emulator 的输入,最终被机器2 的 ./a.out 读出。./a.out 的输出,最终返回到机器1的 terminal emulator 进程输出显示。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!