linux一切皆文件之tty字符设备(深入理解sshd创建pty的过程) (五)
一、知识准备
1、在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列)
2、操作这些不同的类型就像操作文件一样,比如增删改查等
3、块设备支持随机访问,而字符设备只能依据先后顺序来读取数据。最典型的字符设备就是tty
二、环境准备
组件 | 版本 |
---|---|
os | centos linux release 7.5.1804 |
三、什么是tty?
根据史料记载:
an asr33 teletype - origin of the abbreviation tty.
tty来源一种电传打印机(teletype),就像这样:
● 敲击键盘输入不同的字符,然后由打印机将字符打印在纸上
● 历史不断在往前发展,出现了计算机之后,计算机模拟了teletype的模式:通过外部终端输入,将输入的字符打印在屏幕上
● 在teletype与计算机之间用串口相连,并且在计算机上通过信号转换(模拟信号转换为数字信号),让计算机能够识别,从而操作计算机
● 由于计算机厂商众多,每个厂商都有自己风格的输入设备,所以计算机为了兼容这些设备,开发了内核tty模块
+-----------------+ | | +--------+ | +-------------+ | |teletype|-----------------> |serial | | +--------+ | |communication| | | +-----+-------+ | | | | | v | | +----------+ | +----------+ | |tty driver| |------->| display | | +----------+ | +----------+ | | |computer | +-----------------+
四、tty设备文件
登陆到操作系统(不使用ssh协议,而使用控制台直接登陆),首先查看当前进程号所使用的tty
[root@localhost ~]# tty /dev/tty1 [root@localhost ~]# ls -l /dev/tty1 crw--w---- 1 root tty 4, 1 nov 20 23:24 /dev/tty1
当前所使用的是/dev/tty1,并且tty1也分配了主设备号与次设备号(关于主设备号与次设备号,请看之前的文章:块设备文件)
查看进程打开的描述符
[root@localhost ~]# echo $$ 5598 [root@localhost ~]# ls -l /proc/5598/fd total 0 lrwx------ 1 root root 64 nov 19 22:23 0 -> /dev/tty1 lrwx------ 1 root root 64 nov 19 22:23 1 -> /dev/tty1 lrwx------ 1 root root 64 nov 19 22:23 2 -> /dev/tty1 lrwx------ 1 root root 64 nov 19 22:23 255 -> /dev/tty1
进程打开了4个文件描述符,这四个文件描述符都是/dev/tty1,他们的作用分别是:0
:标准输入1
:标准输出2
:标准错误255
:这个比较特殊,主要用于当tty重置的时候对0,1,2
的一份复制(个人观点是对tty之前的历史信息作为一份复制)
更多的信息,请拜读大神的书《shell scripting: expert recipes for linux, bash, and more》,这里是链接(大概在267页):
https://doc.lagout.org/operating%20system%20/linux/commands%20and%20shell%20programming/shell%20scripting.pdf
五、ssh登陆之后的tty
刚才介绍的都是操作系统提供的控制台登陆之后的情况,如果用ssh服务登陆之后会产生什么情况呢?
首先介绍一个非常重要的概念,伪终端pty:
● pty是一对虚拟的字符设备,提供双向通信。pty一般由master与slave组成
● pty的出现是为了满足现在的登陆需求:网络登陆(ssh登陆、telnet登陆等)、xwindow等
● 历史上有两套接口标准:分别是bsd与unix98,当前大多数pts都是基于unix98标准来实现的
● unix98的工作流程:
(1)进程对/dev/ptmx
调用open(),返回pseudoterminal master(ptm)的文件描述符,并且在/dev/pts
下创建pseudoterminal slave(pts): /dev/pts/0
(2)调用grantpt()修改pts的文件权限;调用unlockpt()对pts解锁;最后调用slavename()得到pts文件名字
(3)此时,ptm与pts都已经正常打开,并且建立一条通道,两端分别连接ptm与pts
(4)进程对ptm写的数据可以从pts读出来,反之亦然
下面重点介绍一下基于unix98实现的sshd pty(主要分为登陆阶段和执行命令阶段):
登陆:
(1)当进程ssh client请求与sshd建立登陆连接的时候,经过tcp握手以及tls握手之后,确认是一个合法的请求,sshd会fork()一个子进程出来专门服务于这条连接
[root@localhost ~]# ps -ef | grep sshd root 894 1 0 nov25 ? 00:00:00 /usr/sbin/sshd -d root 3126 894 0 nov25 ? 00:00:00 sshd: root@pts/0
(2)子进程3126
对/dev/ptmx
调用open(),得到ptm的文件描述符以及pts的文件名
#这里使用strace跟踪sshd主进程和它创建的子进程,然后打开另外一个shell登陆服务器 [root@localhost ~]# strace -p 894 -ff -o sshd strace: process 894 attached strace: process 3126 attached strace: process 3127 attached strace: process 3128 attached strace: process 3129 attached strace: process 3130 attached strace: process 3131 attached strace: process 3132 attached strace: process 3133 attached strace: process 3134 attached strace: process 3135 attached strace: process 3136 attached strace: process 3137 attached strace: process 3138 attached strace: process 3139 attached strace: process 3140 attached [root@localhost ~]# grep ptmx ./sshd.* ./sshd.3126:open("/dev/ptmx", o_rdwr) = 8
sshd894
创建了一个子进程3126
用来处理这条tcp连接。进程对/dev/ptmx
调用open(),得到ptm的文件描述符8
(2)子进程3126
在/dev/pts
下创建了一个字符设备文件/dev/pts/0
,8
与/dev/pts/0
成为一对master/slave
(3)子进程3126
会再fork()一个子进程3128
,子进程3128
打开/dev/pts/0
3个描述符(标准输入,标准输出,标准错误),并且执行操作系统默认的shell(本文中bash)
[root@localhost ~]# ps -ef | grep 3126 root 3126 894 0 03:16 ? 00:00:00 sshd: root@pts/0 root 3128 3126 0 03:16 pts/3 00:00:00 -bash [root@localhost ~]# ls -l /proc/3128/fd total 0 lrwx------ 1 root root 64 nov 26 03:16 0 -> /dev/pts/0 lrwx------ 1 root root 64 nov 26 03:16 1 -> /dev/pts/0 lrwx------ 1 root root 64 nov 26 03:16 2 -> /dev/pts/0 lrwx------ 1 root root 64 nov 26 03:22 255 -> /dev/pts/0
至此,通信流程大概是这样:
+----------------+ +------------+ | | | ssh client +---------->| sshd | +----+-------+ | | | +--------+-------+ | | | | | fork() | | | | | v | +----+-----+ fork() +----------+ +-----+ +---------------------->|pid: 3126 |-------------->|pid: 3128 |----->|bash | +-+--------+ +----------+ +-----+ | ^ | | +-------+ | +------|--------------------------------+ | | | +-----------+ | | | v | | | | | +---------+ fd=8 +-----------+ | | | |/dev/ptmx|---------->|/dev/pts/0 |--------+ | +---------+ +-----------+ | | | | | | +-----------+ | +---------------------------------------+
执行命令:
(4)当ssh client发出一个ls
命令,通过tcp连接来到3126
,3126
将ls
写入ptm文件描述符8
(5)/dev/ptmx
查询到关联记录 ptm:8
对应pts:/dev/pts/0
,把ls
转发到/dev/pts/0
当中
(6)3128
从0 -> /dev/pts/0
中读取之后执行ls
(7)ls
返回结果之后写入1 -> /dev/pts/0
,然后根据关联记录回写到/dev/ptmx
(8)3126
从/dev/ptmx
读取之后返回到ssh client
六、参考资料
http://osr600doc.sco.com/en/sdk_sysprog/_pseudo-tty_drivers_em_ptm_and_p.html
至此,本文结束
在下才疏学浅,有撒汤漏水的,请各位不吝赐教...
上一篇: JAVA poi 帮助类