什么是终端
能实现数据输入、输出的统称为终端。
物理终端:传统显示器、键盘,通过 VGA、HDMI、USB 等连接。
软件终端(虚拟终端/伪终端):通过 TCP/IP 协议实现的终端,能模拟出来一个实现「数据输入、输出」的终端,比如:ssh、telnet …
这篇博客介绍的比较详细:「转」彻底理解Linux的各种终端类型以及概念。
伪终端的连接过程
在linux中有物理终端,有虚拟终端(伪终端)。
sshd 服务会打开一个 [dev/ptmx],这个文件是一个伪终端主设备文件。
bin/bash 会打开一个 dev/pts(0,1…), 是一个伪终端从设备文件。
ptmx/ptsx 它们通过伪终端设备驱动程序模拟出输入和输出的功能。
bin/bash 进程就可以实现数据读取和数据写入。
ssh客户端 ——————> 远程服务器的一个进程 ——-> ssh客户端。
输入单元(ssh客户端)——–> /bin/bash进程————–> 输出单元(ssh客户端)。
bin/bash 启动之后,对终端数据的读取和写入就是通过 pts 实现的(能实现标准输入,标准输出)。
dev/pts0 0 1 2 标准输入,标准输出,标准错误。
通过 strace 命令查看sshd服务:
strace 前进程关系:
├─sshd,9408
│ └─sshd,2888142
│ └─bash,2888170
│ └─pstree,2889779 -ap
strace 中:
$strace -f -s 65500 -o sshd.log -p 9408 # strace 查看系统调用,进行一个新的 SSH 连接
strace: Process 9408 attached
strace: Process 2888330 attached
strace: Process 2888331 attached
strace: Process 2888336 attached
strace: Process 2888342 attached
strace: Process 2888343 attached
strace: Process 2888344 attached
strace: Process 2888345 attached
strace: Process 2888346 attached
strace: Process 2888347 attached
strace: Process 2888348 attached
strace: Process 2888349 attached
strace: Process 2888350 attached
strace: Process 2888351 attached
strace: Process 2888352 attached
strace: Process 2888353 attached
strace: Process 2888354 attached
strace: Process 2888355 attached
strace: Process 2888356 attached
strace: Process 2888357 attached # 新的SSH连接
strace: Process 2888358 attached
strace: Process 2888359 attached
strace: Process 2888360 attached
strace: Process 2888361 attached
strace: Process 2888362 attached
strace: Process 2888363 attached
strace: Process 2888364 attached
strace: Process 2888365 attached
strace: Process 2888382 attached
^Cstrace: Process 9408 detached # 终止信号
strace: Process 2888330 detached
strace: Process 2888357 detached
新的 SSH 连接以后的进程关系:
├─sshd,9408
│ ├─sshd,2888142
│ │ └─bash,2888170
│ └─sshd,2888330
│ └─bash,2888357 # 新的SSH连接
│ └─pstree,2889432 -ap
查看 sshd.log:
# select 是一个 socket 接口函数
9408 select(95, [3], NULL, NULL, NULL) = 1 (in [3])
# 接收客户端连接
9408 accept(3, {sa_family=AF_INET, sin_port=htons(22670), sin_addr=inet_addr("123.139.68.156")}, [128->16]) = 4
9408 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR)
9408 pipe([5, 6]) = 0
9408 socketpair(AF_UNIX, SOCK_STREAM, 0, [7, 8]) = 0
# 创建 SSHD 子进程
9408 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1fc4482210) = 2888330
9408 close(6) = 0
...
# 打开 ptmx 伪终端主设备文件
2888330 openat(AT_FDCWD, "/dev/ptmx", O_RDWR) = 9
2888330 statfs("/dev/pts", {f_type=DEVPTS_SUPER_MAGIC, f_bsize=4096, f_blocks=0, f_bfree=0, f_bavail=0, f_files=0, f_ffree=0, f_fsid={val=[0, 0]}, f_namelen=255,
...
# 创建 bash 子进程
2888330 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1994e47210) = 2888357
2888357 set_robust_list(0x7f1994e47220, 24) = 0
...
2888357 close(11 <unfinished ...>
2888357 <... close resumed>) = 0
2888357 close(12 <unfinished ...>
2888357 <... close resumed>) = 0
2888357 close(9 <unfinished ...>
2888357 <... close resumed>) = 0
# 把自己设置为会话首进程
2888357 setsid( <unfinished ...>
2888357 openat(AT_FDCWD, "/dev/tty", O_RDWR|O_NOCTTY <unfinished ...>
2888357 <... openat resumed>) = -1 ENXIO (No such device or address)
2888357 setsid( <unfinished ...>
2888357 <... setsid resumed>) = 2888357
2888357 openat(AT_FDCWD, "/dev/tty", O_RDWR|O_NOCTTY <unfinished ...>
2888357 <... openat resumed>) = -1 ENXIO (No such device or address)
2888357 ioctl(10, TIOCSCTTY, 0 <unfinished ...>
2888357 <... ioctl resumed>) = 0
# 伪终端从设备文件
# 当 /bin/bash 进程打开这个文件时,就可以简单认为该进程已经连接了我们的输入(键盘)、输出单元(显示器)。
# 已经能实现数据的输入和输出,相当于连接了一个终端(这个终端是通过 [TCP/IP] 协议实现的)。
# Linux 内核(SSHD服务)模拟出一个终端:[0、1、2 标准输入、标准输出、标准错误]
2888357 openat(AT_FDCWD, "/dev/pts/1", O_RDWR <unfinished ...>
...
# 在终端输入 `ll` 的系统调用
# 说明伪终端通过 [TCP/IP] 传输
# 输入(键盘)
2888357 <... read resumed>"l", 1) = 1
# socket 监听
2888330 select(12, [4 5 11], [], NULL, NULL <unfinished ...>
2888357 select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 1 (in [0], left {tv_sec=0, tv_usec=0})
2888357 pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
# 输入(键盘)
2888357 read(0, "l", 1) = 1
2888357 select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
# 输出(显示器)
2888357 write(2, "ll", 2) = 2
总结:
sshd 接收客户端连接之后,clone,fork 一个进程,同时打开伪终端主设备文件 /dev/ptmx;
再fork一个进程,同时启动 bin/bash(etc/passwd)进程,该进程会打开一个伪终端从设备 dev/pts;
伪终端能实现数据输入(键盘),还能实现数据输出(显示器),就是这个进程对应的键盘、显示器,键盘(0),显示器(1,2);
主从设备终端通过伪终端设备驱动程序进行通信;
ssh 客户端输入的数据,可以当作远程服务器的键盘输入的数据;
远程服务器的输出,通过 TCP/IP 协议传输到 ssh 客户端;
理解这些对:进程组,会话,守护进程的学习很重要。