SSHD含义
SSH:安全外壳协议。为 Secure Shell 的缩写。SSH 为建立在应用层和传输层基础上的安全协议。
D:代表守护进程。
终端(Terminal)
只要能提供给计算机输入和输出功能,它就是终端,而与其所在的位置无关。我可以用ls命令列举五千公里以外的一台计算机上某个目录下的文件并且显示在我眼前的屏幕上,至于我的输入如何到达五千公里以外,这并不是我要关注的,也不是计算机要关注的,这显然只是一个通信方式问题。那么使用TCP/IP网络进行这类通信传输就是再显然不过的了。
这就是SSH使用的方法。我们知道,SSH是一个TCP/IP协议族的协议,而其上跑的却是一个远程登录后的终端流,这显然只是用TCP/IP构建了一条隧道,然后终端流通行于该隧道。除此之外,更简单的Telnet也不例外,也是通过一个TCP/IP隧道来封装承载远程登录的终端流。除却TCP/IP,如果我们执意使用卡车来运载我们的输入和输出,也完全是合适的,TCP/IP也好,卡车也好,它们只是通信手段,它们并非终端本身。
终端分类
本地终端
用VGA连接主机和显示器,用PS/2或者USB连接主机和键盘,这样的一个显示器/键盘组合就是一个本地终端。
用串口连接的远程终端
通过串口线把主机接到另外一个有显示器和键盘的主机,通过运行一个终端模拟程序,比如“Windows超级终端”来将这台主机的显示器和键盘借给串口对端的主机。
用TCP/IP承载的远程终端
类似Telnet,SSH这般。
前两类都是在本地就直接关联了物理设备的,比如VGA口啊,PS/2口啊,串口啊之类的,这种终端叫做物理终端,而第三类在本地则没有关联任何物理设备,注意,不要把物理网卡当成终端关联的物理设备,它只是隧道关联的物理设备,这里的物理网卡完全可以换成卡车,它们与终端并不直接相关,所以这类不直接关联物理设备的终端叫做伪终端。
tty(控制终端)
tty是最令人熟悉的了,在Linux中,/dev/ttyX代表的都是上述的物理终端,其中,/dev/tty1~/dev/tty63代表的是本地终端,也就是接到本机的键盘显示器可以操作的终端。换句话说,你往/dev/tty3里写个东西,它就会显示在显示器对应的终端。
为什么会有63个终端这么多呢?毕竟显示器只是一个单独的显示设备,键盘往往也只有一个,但Linux内核有能力知道现在该干什么,所以事实上Linux内核在初始化时会生成63个本地终端,通过键盘上的Fn-Alt-FX(X为1,2,3…)可以在这些终端之间切换,每切换到一个终端,该终端就是当前的焦点终端,比如说,你按下了Fn-Alt-F4组合键,那么此时第4个终端就是焦点终端,即/dev/tty4就是焦点终端设备。
谁是焦点终端会被内核记录为全局变量,这样只要有键盘输入,就会把输入的字符交给焦点终端。
系统中有没有什么变量可以表示焦点终端呢?当然有了,那就是/dev/console,不管你在哪里往/dev/console里写东西,这些东西总会出现在系统当前的焦点终端上!
按照以他人为中心,我们解释了/dev/console其实就是一个全局变量,指代当前的焦点终端,如果当前的焦点是/dev/tty4,那么/dev/console指的就是/dev/tty4,当然这一切都是由内核来维护的。
那么系统中有没有一个叫做自己的全局变量呢?当然有,那就是/dev/tty,也就是说,无论你在哪个终端下工作,当你往/dev/tty里写东西的时候,它总是会马上出现在你的眼前。
/dev/tty1~/dev/tty63我们知道了它们是什么,/dev/tty表示自己,/dev/console表示焦点终端这些我们也知道了,那么串口终端如何表示呢?很简单,以ttyS开头的就是串口连接的终端,比如ttyS1,ttyS2…
pty(伪终端)
伪终端和TUN/TAP虚拟网卡的原理很相似,模拟一个虚拟的终端设备,实现它的write,read等回调即可。为此,Linux设计出一对虚拟终端设备,即/dev/ptmx和/dev/pts/X,这就跟TUN/TAP网卡的网卡与字符设备之前的对应关系一致。
简单来讲,当有ssh客户端连接后,sshd会fork一个进程,然后在子进程中打开一个叫做/dev/pts/1(或者2,3,4,5…)的设备,然后和sshd进程的/dev/ptmx配对,这样在ptmx与pts之间就构成了一条管道,数据可以顺利被导入到sshd,然后通过TCP/IP封装发往ssh client所在的机器。
pts也是tty设备。
pty(pseudo terminal device)由两部分构成,ptmx是master端,pts是slave端,进程可以通过调用API请求ptmx创建一个pts,然后将会得到连接到ptmx的读写fd和一个新创建的pts,ptmx在内部会维护该fd和pts的对应关系,随后往这个fd的读写会被ptmx转发到对应的pts。
tty交互
Input +--------------------------+ R/W +------+
----------->| |<---------->| bash |
| pts/1 | +------+
<-----------| |<---------->| lsof |
Output | Foreground process group | R/W +------+
+--------------------------+
- 可以把tty理解成一个管道(pipe),在一端写的内容可以从另一端读取出来,反之亦然。
- 这里input和output可以简单的理解为键盘和显示器,后面会介绍在各种情况下input/ouput都连接的什么东西。
- tty里面有一个很重要的属性,叫Foreground process group,记录了当前前端的进程组是哪一个。process group的概念会在下一篇文章中介绍,这里可以简单的认为process group里面只有一个进程。
- 当pts/1收到input的输入后,会检查当前前端进程组是哪一个,然后将输入放到进程组的leader的输入缓存中,这样相应的leader进程就可以通过read函数得到用户的输入
- 当前端进程组里面的进程往tty设备上写数据时,tty就会将数据输出到output设备上
- 当在shell中执行不同的命令时,前端进程组在不断的变化,而这种变化会由shell负责更新到tty设备中
SSHD远程访问
+----------+ +------------+
| Keyboard |------>| |
+----------+ | Terminal |
| Monitor |<------| |
+----------+ +------------+
|
| ssh protocol
|
↓
+------------+
| |
| ssh server |--------------------------+
| | fork |
+------------+ |
| ↑ |
| | |
write | | read |
| | |
+-----|---|-------------------+ |
| | | | ↓
| ↓ | +-------+ | +-------+
| +--------+ | pts/0 |<---------->| shell |
| | | +-------+ | +-------+
| | ptmx |<->| pts/1 |<---------->| shell |
| | | +-------+ | +-------+
| +--------+ | pts/2 |<---------->| shell |
| +-------+ | +-------+
| Kernel |
+-----------------------------+
建立连接
- Terminal请求和sshd建立连接
- 如果验证通过,sshd将创建一个新的session
- 调用API(posix_openpt())请求ptmx创建一个pts,创建成功后,sshd将得到和ptmx关联的fd,并将该fd和session关联起来。
- 同时sshd创建shell进程,将新创建的pts和shell绑定
收发消息
- Terminal收到键盘的输入,Terminal通过ssh协议将数据发往sshd
- sshd收到客户端的数据后,根据它自己管理的session,找到该客户端对应的关联到ptmx上的fd
- 往找到的fd上写入客户端发过来的数据
- ptmx收到数据后,根据fd找到对应的pts(该对应关系由ptmx自动维护),将数据包转发给对应的pts
- pts收到数据包后,检查绑定到自己上面的当前前端进程组,将数据包发给该进程组的leader
- 由于pts上只有shell,所以shell的read函数就收到了该数据包
- shell对收到的数据包进行处理,然后输出处理结果(也可能没有输出)
- shell通过write函数将结果写入pts
- pts将结果转发给ptmx
- ptmx根据pts找到对应的fd,往该fd写入结果
- sshd收到该fd的结果后,找到对应的session,然后将结果发给对应的客户端
TTY相关信号
SIGTTIN
当后台进程读tty时,tty将发送该信号给相应的进程组,默认行为是暂停进程组中进程的执行。暂停的进程如何继续执行呢?请参考下一篇文章中的SIGCONT。
SIGHUP
当tty的另一端挂掉的时候,比如ssh的session断开了,于是sshd关闭了和ptmx关联的fd,内核将会给和该tty相关的所有进程发送SIGHUP信号,进程收到该信号后的默认行为是退出进程。
SIGTSTP
终端输入CTRL+Z时,tty收到后就会发送SIGTSTP给前端进程组,其默认行为是将前端进程组放到后端,并且暂停进程组里所有进程的执行。