linux tty
Contents
linux tty
- pts(pseudo-terminal slave)
用tty命令看看当前bash关联到了哪个tty
|
|
看 tty 都被哪些进程打开了
|
|
往tty里面直接写数据跟写标准输出是一样的效果
|
|
下面是 tty 和进程以及 I/O 设备交互的结构图:
|
|
两个pts间发送消息
|
|
列出系统支持的所有终端类型
|
|
比较两个终端的区别
|
|
TTY历史
支持多任务的计算机出现之前 在计算机出来以前,人们就已经在使用一种叫teletype的设备,用来相互之间传递信息,看起来像下面这样:
|
|
两个teletype之间用线连接起来,线两端可能也有类似于调制解调器之类的设备 (这里将它们忽略) ,在一端的teletype上敲键盘时,相应的数据会发送到另一端的teletype,具体功能是干什么的,我也不太了解。(我脑袋里面想到画面是在一端敲字,另一端打印出来)
这些都是老古董了,完全没接触过,所以只能简单的推测。
支持多任务的计算机出现之后 等到计算机支持多任务后,人们想到把这些teletype连到计算机上,作为计算机的终端,从而可以操作计算机。
使用teletype的主要原因有两个 (个人见解) :
现实中已经存在了大量不同厂商的teletype,可以充分利用现有资源
teletype的相关网络已经比较成熟,连起来方便
于是连接就发展成这样:
|
|
左边的Terminal就是各种各样的teletype
物理线路两边用上了Modem,就是我们常说的"猫”,那是因为后来网络已经慢慢的变发达了,大家可以共享连接了。 (大概推测,可能不对)
UART可以理解为将teletype的信号转换成计算机能识别的信号的设备
tty一词源于Teletypes,或者teletypewriters,原来指的是电传打字机,是通过串行线用打印机键盘通过阅读和发送信息的东西,后来这东西被键盘与显示器取代,所以现在叫终端比较合适。
终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。
TTY是电传打字机Teletypewriter的缩写, 带显示屏的视频终端出现之前,TTY是最流行的终端设备。
http://7056824.blog.51cto.com/69854/276610
https://www.zhihu.com/question/21711307
http://ytliu.info/blog/2013/09/28/ttyde-na-xie-shi-er/
tty是Teletype的缩写(转)
终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写。Teletype是最早出现的一种终端设备,很象电传打字机 (或者说就是) ,是由Teletype公司生产的。设备名放在特殊文件目录/dev/下,终端特殊设备文件一般有以下几种:
1.串行端口终端 (/dev/ttySn)
串行端口终端 (Serial Port Terminal) 是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。有段时间这些串行端口设备通常被称为终端设备,因为那时它的最大用途就是用来连接终端。这些串行端口所对应的设备名称是/dev/tts/0 (或/dev/ttyS0) 、/dev/tts/1 (或/dev/ttyS1) 等,设备号分别是 (4,0) 、 (4,1) 等,分别对应于DOS系统下的COM1、COM2等。若要向一个端口发送数据,可以在命令行上把标准输出重定向到这些特殊文件名上即可。例如,在命令行提示符下键入: echo test > /dev/ttyS1会把单词"test"发送到连接在ttyS1 (COM2) 端口的设备上。
2.伪终端 (/dev/pty/)
伪终端 (Pseudo Terminal) 是成对的逻辑终端设备,例如/dev/ptyp3和/dev/ttyp3 (或着在设备文件系统中分别是/dev/pty/m3和/dev/pty/s3) 。它们与实际物理设备并不直接相关。如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对的另一个上面 (ttyp3) 。而ttyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,而其中一个使用ttyp3的程序则认为自己正在与一个串行端口进行通信。这很象是逻辑设备对之间的管道操作。
对于ttyp3 (s3) ,任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用ptyp3的程序,则需要专门设计来使用ptyp3 (m3) 逻辑设备。
例如,如果某人在网上使用telnet程序连接到你的计算机上,则telnet程序就可能会开始连接到设备ptyp2 (m2) 上 (一个伪终端端口上) 。此时一个getty程序就应该运行在对应的ttyp2 (s2) 端口上。当telnet从远端获取了一个字符时,该字符就会通过m2、s2传递给getty程序,而getty程序就会通过s2、m2和telnet程序往网络上返回"login:“字符串信息。这样,登录程序与telnet程序就通过"伪终端"进行通信。通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端口上。
在使用设备文件系统 (device filesystem) 之前,为了得到大量的伪终端设备特殊文件,HP-UX AIX等使用了比较复杂的文件名命名方式。
3.控制终端 (/dev/tty)
如果当前进程有控制终端 (Controlling Terminal) 的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令"ps –ax"来查看进程与哪个控制终端相连。对于你登录的shell,/dev/tty就是你使用的终端,设备号是 (5,0) 。使用命令"tty"可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接。
4.控制台终端 (/dev/ttyn, /dev/console)
在UNIX系统中,计算机显示器通常被称为控制台终端 (Console) 。它仿真了类型为Linux的一种终端 (TERM=Linux) ,并且有一些设备特殊文件与之相关联: tty0、tty1、tty2等。当你在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1 –tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。
你可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev/tty0进行写操作,
5.其它类型
还针对很多不同的字符设备存在有很多其它种类的终端设备特殊文件。例如针对ISDN设备的/dev/ttyIn终端设备等。这里不再赘述。
pty (虚拟终端)
但是如果我们远程telnet到主机或使用xterm时不也需要一个终端交互么?是的,这就是虚拟终端pty(pseudo-tty)。
pts/ptmx(pts/ptmx结合使用,进而实现pty): 在Xwindows模式下的伪终端。 pts(pseudo-terminal slave)是pty的实现方法,与ptmx(pseudo-terminal master)配合使用实现pty。 man里面是这样说的: ptmx and pts - pseudo-terminal master and slave, pts是所谓的伪终端或虚拟终端,具体表现就是你打开一个终端,这个终端就叫pts/0,如果你再打开一个终端,这个新的终端就叫pts /1。
TTY历史 支持多任务的计算机出现之前 在计算机出来以前,人们就已经在使用一种叫teletype的设备,用来相互之间传递信息,看起来像下面这样:
|
|
两个teletype之间用线连接起来,线两端可能也有类似于调制解调器之类的设备 (这里将它们忽略) ,在一端的teletype上敲键盘时,相应的数据会发送到另一端的teletype,具体功能是干什么的,我也不太了解。(我脑袋里面想到画面是在一端敲字,另一端打印出来)
这些都是老古董了,完全没接触过,所以只能简单的推测。
支持多任务的计算机出现之后 等到计算机支持多任务后,人们想到把这些teletype连到计算机上,作为计算机的终端,从而可以操作计算机。
使用teletype的主要原因有两个 (个人见解) :
现实中已经存在了大量不同厂商的teletype,可以充分利用现有资源
teletype的相关网络已经比较成熟,连起来方便
于是连接就发展成这样:
|
|
左边的Terminal就是各种各样的teletype
物理线路两边用上了Modem,就是我们常说的"猫”,那是因为后来网络已经慢慢的变发达了,大家可以共享连接了。 (大概推测,可能不对)
UART可以理解为将teletype的信号转换成计算机能识别的信号的设备
内核TTY子系统 计算机为了支持这些teletype,于是设计了名字叫做TTY的子系统,内部结构如下:
|
|
UART driver对接外面的UART设备
Line discipline主要是对输入和输出做一些处理,可以理解它是TTY driver的一部分
TTY driver用来处理各种终端设备
用户空间的进程通过TTY driver来和终端打交道
为了简单起见,后面的介绍中不再单独列出UART driver和Line discipline,可以认为它们是TTY driver的一部分
TTY设备 对于每一个终端,TTY driver都会创建一个TTY设备与它对应,如果有多个终端连接过来,那么看起来就是这个样子的:
|
|
当驱动收到一个终端的连接时,就会根据终端的型号和参数创建相应的tty设备 (上图中设备名称叫ttyS0是因为大部分终端的连接都是串行连接) ,由于每个终端可能都不一样,有自己的特殊命令和使用习惯,于是每个tty设备的配置可能都不一样。比如按delete键的时候,有些可能是要删前面的字符,而有些可能是删后面的,如果没配置对,就会导致某些按键不是自己想要的行为,这也是我们在使用模拟终端时,如果默认的配置跟我们的习惯不符,需要做一些个性化配置的原因。
后来随着计算机的不断发展,teletype这些设备逐渐消失,我们不再需要专门的终端设备了,每个机器都有自己的键盘和显示器,每台机器都可以是其它机器的终端,远程的操作通过 ssh 来实现,但是内核 TTY 驱动这一架构没有发生变化,我们想要和系统中的进程进行 I/O 交互,还是需要通过 TTY 设备, 于是出现了各种终端模拟软件, 并且模拟的也是常见的几种终端,如 VT100、VT220、XTerm 等。
可以通过命令toe -a列出系统支持的所有终端类型
可以通过命令infocmp来比较两个终端的区别,比如infocmp vt100 vt220将会输出vt100和vt220的区别。
程序如何和TTY打交道 在讨论TTY设备是如何被创建及配置之前,我们先来看看TTY是如何被进程使用的:
先用tty命令看看当前bash关联到了哪个tty
dev@debian:~$ tty /dev/pts/1
看tty都被哪些进程打开了
dev@debian:~$ lsof /dev/pts/1 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 907 dev 0u CHR 136,1 0t0 4 /dev/pts/1 bash 907 dev 1u CHR 136,1 0t0 4 /dev/pts/1 bash 907 dev 2u CHR 136,1 0t0 4 /dev/pts/1 bash 907 dev 255u CHR 136,1 0t0 4 /dev/pts/1 lsof 1118 dev 0u CHR 136,1 0t0 4 /dev/pts/1 lsof 1118 dev 1u CHR 136,1 0t0 4 /dev/pts/1 lsof 1118 dev 2u CHR 136,1 0t0 4 /dev/pts/1
往tty里面直接写数据跟写标准输出是一样的效果
dev@dev:~$ echo aaa > /dev/pts/2 aaa pts也是tty设备,它们的关系后面会介绍到
通过上面的lsof可以看出,当前运行的bash和lsof进程的stdin(0u)、stdout(1u)、stderr(2u)都绑定到了这个TTY上。
下面是tty和进程以及I/O设备交互的结构图:
|
|
可以把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设备中
从上面可以看出,进程和tty打交道很简单,只要保证后台进程不要读写tty就可以了,即写后台程序时,要将stdin/stdout/stderr重定向到其它地方 (当然deamon程序还需要做很多其它处理) 。
先抛出两个问题(后面有答案):
当非前端进程组里面的进程 (后台进程) 往tty设备上写数据时,会发生什么?会输出到outpu上吗?
当非前端进程组里面的进程 (后台进程) 从tty设备上读数据时,会发生什么?进程会阻塞吗?
TTY是如何被创建的 下面介绍几种常见的情况下tty设备是如何创建的,以及input和output设备都是啥。
键盘显示器直连 (终端) 先看图再说话:
|
|
键盘、显示器都和内核中的终端模拟器相连,由模拟器决定创建多少tty,比如你在键盘上输入ctrl+alt+F1时,模拟器首先捕获到该输入,然后激活tty1,这样键盘的输入会转发到tty1,而tty1的输出会转发到显示器,同理用输入ctrl+alt+F2,就会切换到tty2。
当模拟器激活tty时如果发现没有进程与之关联,意味着这是第一次打开该tty,于是会启动配置好的进程并和该tty绑定,一般该进程就是负责login的进程。
当切换到tty2后,tty1里面的输出会输出到哪里呢?tty1的输出还是会输出给模拟器,模拟器里会有每个tty的缓存,不过由于模拟器的缓存空间有限,所以下次切回tty1的时候,只能看到最新的输出,以前的输出已经不在了。
不确定这里的终端模拟器对应内核中具体的哪个模块,但肯定有这么个东西存在
SSH远程访问
|
|
这里的Terminal可能是任何地方的程序,比如windows上的putty,所以不讨论客户端的Terminal程序是怎么和键盘、显示器交互的。由于Terminal要和ssh服务器打交道,所以肯定要实现ssh的客户端功能。
这里将建立连接和收发数据分两条线路解释,为了描述简洁,这里以sshd代替ssh服务器程序:
建立连接 1.Terminal请求和sshd建立连接
2.如果验证通过,sshd将创建一个新的session
3.调用API (posix_openpt()) 请求ptmx创建一个pts,创建成功后,sshd将得到和ptmx关联的fd,并将该fd和session关联起来。
pty (pseudo terminal device) 由两部分构成,ptmx是master端,pts是slave端,
进程可以通过调用API请求ptmx创建一个pts,然后将会得到连接到ptmx的读写fd和一个新创建的pts,
ptmx在内部会维护该fd和pts的对应关系,随后往这个fd的读写会被ptmx转发到对应的pts。
这里可以看到sshd已经打开了/dev/ptmx
dev@debian:~$ sudo lsof /dev/ptmx COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 1191 dev 8u CHR 5,2 0t0 6531 /dev/ptmx sshd 1191 dev 10u CHR 5,2 0t0 6531 /dev/ptmx sshd 1191 dev 11u CHR 5,2 0t0 6531 /dev/ptmx 4.同时sshd创建shell进程,将新创建的pts和shell绑定
收发消息 1.Terminal收到键盘的输入,Terminal通过ssh协议将数据发往sshd
2.sshd收到客户端的数据后,根据它自己管理的session,找到该客户端对应的关联到ptmx上的fd
3.往找到的fd上写入客户端发过来的数据
4.ptmx收到数据后,根据fd找到对应的pts (该对应关系由ptmx自动维护) ,将数据包转发给对应的pts
5.pts收到数据包后,检查绑定到自己上面的当前前端进程组,将数据包发给该进程组的leader
6.由于pts上只有shell,所以shell的read函数就收到了该数据包
7.shell对收到的数据包进行处理,然后输出处理结果 (也可能没有输出)
8.shell通过write函数将结果写入pts
9.pts将结果转发给ptmx
10.ptmx根据pts找到对应的fd,往该fd写入结果
11.sshd收到该fd的结果后,找到对应的session,然后将结果发给对应的客户端
键盘显示器直连 (图形界面) +———-+ +————+ | Keyboard |——>| | +———-+ | Terminal |————————–+ | Monitor |<——| | fork | +———-+ +————+ | | ↑ | | | | write | | read | | | | +—–|—|——————-+ | | | | | ↓ | ↓ | +——-+ | +——-+ | +——–+ | pts/0 |<———->| shell | | | | +——-+ | +——-+ | | ptmx |<->| pts/1 |<———->| shell | | | | +——-+ | +——-+ | +——–+ | pts/2 |<———->| shell | | +——-+ | +——-+ | Kernel | +—————————–+ 为了简化起见,本篇不讨论Linux下图形界面里Terminal程序是怎么和键盘、显示器交互的。
这里和上面的不同点就是,这里的Terminal不需要实现ssh客户端,但需要把ssh服务器要干的活也干了 (当然ssh通信相关的除外) 。
SSH + Screen/Tmux 常用Linux的同学应该对screen和tmux不陌生,通过它们启动的进程,就算网络断开了,也不会受到影响继续执行,下次连上去时还能看到进程的所有输出,还能继续接着干活。
这里以tmux为例介绍其原理:
+———-+ +————+ | Keyboard |——>| | +———-+ | Terminal | | Monitor |<——| | +———-+ +————+ | | ssh protocol | ↓ +————+ | | | ssh server |————————–+ | | fork | +————+ | | ↑ | | | | write | | read | | | | +—–|—|——————-+ | | ↓ | | ↓ | +——–+ +——-+ | +——-+ fork +————-+ | | ptmx |<->| pts/0 |<———->| shell |——–>| tmux client | | +——–+ +——-+ | +——-+ +————-+ | | | | ↑ | +——–+ +——-+ | +——-+ | | | ptmx |<->| pts/2 |<———->| shell | | | +——–+ +——-+ | +——-+ | | ↑ | Kernel | ↑ | +—–|—|——————-+ | | | | | | |w/r| +—————————+ | | | | fork | | ↓ | | +————-+ | | | | | tmux server |<——————————————–+ | | +————-+ 系统中的ptmx只有一个,上图中画出来了两个,目的是为了表明tmux服务器和sshd都用ptmx,但它们之间又互不干涉。
这种情况要稍微复杂一点,不过原理都是一样的,前半部分和普通ssh的方式是一样的,只是pts/0关联的前端进程不是shell了,而是变成了tmux客户端,所以ssh客户端发过来的数据包都会被tmux客户端收到,然后由tmux客户端转发给tmux服务器,而tmux服务器干的活和ssh的类似,也是维护一堆的session,为每个session创建一个pts,然后将tmux客户端发过来的数据转发给相应的pts。
由于tmux服务器只和tmux客户端打交道,和sshd没有关系,当终端和sshd的连接断开时,虽然pts/0会被关闭,和它相关的shell和tmux客户端也将被kill掉,但不会影响tmux服务器,当下次再用tmux客户端连上tmux服务器时,看到的还是上次的内容。
TTY和PTS的区别 从上面的流程中应该可以看出来了,对用户空间的程序来说,他们没有区别,都是一样的;从内核里面来看,pts的另一端连接的是ptmx,而tty的另一端连接的是内核的终端模拟器,ptmx和终端模拟器都只是负责维护会话和转发数据包;再看看ptmx和内核终端模拟器的另一端,ptmx的另一端连接的是用户空间的应用程序,如sshd、tmux等,而内核终端模拟器的另一端连接的是具体的硬件,如键盘和显示器。
常见的TTY配置 先先来看看当前tty的所有配置:
dev@dev:~$ stty -a speed 38400 baud; rows 51; columns 204; line = 0; intr = ^C; quit = ^; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0; -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel -iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc stty还可以用来修改tty的参数,用法请参考man stty
只要是有权限的程序,都可以通过Linux提供的API来修改TTY的配置,下面介绍一些常见的的配置项。
rows 51; columns 204; 这个配置一般由终端控制,当终端的窗口大小发生变化时,需要通过一定的手段修改该配置,比如ssh协议里面就有修改窗口大小的参数,sshd收到客户端的请求后,会通过API修改tty的这个参数,然后由tty通过信号SIGWINCH通知前端程序 (比如shell或者vim) ,前端程序收到信号后,再去读tty的这个参数,然后就知道如何调整自己的输出排版了。
intr = ^C tty除了在终端和前端进程之间转发数据之外,还支持很多控制命令,比如终端输入了CTRL+C,那么tty不会将该输入串转发给前端进程,而是将它转换成信号SIGINT发送给前端进程。这个就是用来配置控制命令对应的输入组合的,比如我们可以配置"intr = ^E"表示用CTRL+E代替CTRL+C。
start = ^Q; stop = ^S; 这是两个特殊的控制命令,估计经常有人会碰到,在键盘上不小心输入CTRL+S后,终端没反应了,即没输出,也不响应任何输入。这是因为这个命令会告诉TTY暂停,阻塞所有读写操作,即不转发任何数据,只有按了CTRL+Q后,才会继续。这个功能应该是历史遗留,以前终端和服务器之间没有流量控制功能,所以有可能服务器发送数据过快,导致终端处理不过来,于是需要这样一个命令告诉服务器不要再发了,等终端处理完了后在通知服务器继续。
该命令现在比较常用的一个场景就是用tail -f命令监控日志文件的内容时,可以随时按CTRL+S让屏幕停止刷新,看完后再按CTRL+Q让它继续刷,如果不这样的话,需要先CTRL+C退出,看完后在重新运行tail -f命令。
echo 在终端输入字符的时候,之所以我们能及时看到我们输入的字符,那是因为TTY在收到终端发过去的字符后,会先将字符原路返回一份,然后才交给前端进程处理,这样终端就能及时的显示输入的字符。echo就是用来控制该功能的配置项,如果是-echo的话表示disable echo功能。
-tostop 如果你在shell中运行程序的时候,后面添加了&,比如./myapp &,这样myapp这个进程就会在后台运行,但如果这个进程继续往tty上写数据呢?这个参数就用来控制是否将输出转发给终端,也即结果会不会在终端显示,这里”-tostop"表示会输出到终端,如果配置为"tostop"的话,将不输出到终端,并且tty会发送信号SIGTTOU给myapp,该信号的默认行为是将暂停myapp的执行。
TTY相关信号 除了上面介绍配置时提到的SIGINT,SIGTTOU,SIGWINCHU外,还有这么几个跟TTY相关的信号
SIGTTIN 当后台进程读tty时,tty将发送该信号给相应的进程组,默认行为是暂停进程组中进程的执行。暂停的进程如何继续执行呢?请参考下一篇文章中的SIGCONT。
SIGHUP
当tty的另一端挂掉的时候,比如ssh的session断开了,于是sshd关闭了和ptmx关联的fd,内核将会给和该tty相关的所有进程发送SIGHUP信号,进程收到该信号后的默认行为是退出进程。
SIGTSTP 终端输入CTRL+Z时,tty收到后就会发送SIGTSTP给前端进程组,其默认行为是将前端进程组放到后端,并且暂停进程组里所有进程的执行。
跟tty相关的信号都是可以捕获的,可以修改它的默认行为
https://segmentfault.com/a/1190000009082089
http://www.baidu.com/link?url=hpl3pR3uMIRNTSl_O3-AbUiUV75KHpBzfkfCXY5SUChbtFMs3SM3HXaz4DnZgRbvbM1AXf5Rc9LZngLtNlwegq
https://www.cnblogs.com/sparkdev/p/11460821.html
Author -
LastMod 2014-08-11