首頁 > 運維 > linux運維 > 一起聊聊Linux TTY/PTS及其作用差異

一起聊聊Linux TTY/PTS及其作用差異

藏色散人
發布: 2021-09-19 17:00:18
轉載
3392 人瀏覽過

以下由linux系統教學專欄來介紹Linux TTY/PTS及其作用區別,希望對需要的朋友有幫助!

Linux TTY/PTS概述

當我們在鍵盤上敲下一個字母的時候,到底是怎麼發送到對應的進程的呢?我們透過ps、who等命令看到的類似tty1、pts/0這樣的輸出,它們的作用和差異是什麼呢?

TTY歷史

支援多任務的電腦出現之前

在電腦出來以前,人們就已經在使用一種叫teletype的設備,用來相互之間傳遞訊息,看起來像下面這樣:

+----------+     Physical Line     +----------+
| teletype |<--------------------->| teletype |
+----------+                       +----------+
登入後複製

兩個teletype之間用線連接起來,線兩端可能也有類似於調製解調器之類的設備(這裡將它們忽略),在一端的teletype上敲鍵盤時,對應的資料會傳送到另一端的teletype,具體功能是做什麼的,我也不太了解。 (我腦袋裡面想到畫面是在一端敲字,另一端印出來)

這些都是老古董了,完全沒接觸過,所以只能簡單的推測。

支援多任務的電腦出現之後

等到電腦支援多任務後,人們想到把這些teletype連到電腦上,作為電腦的終端,從而可以操作電腦。

使用teletype的主要原因有兩個(個人洞察):

  • #現實中已經存在了大量不同廠商的teletype,可以充分利用現有資源

  • teletype的相關網路已經比較成熟,連起來方便

#於是連接就發展成這樣:

                                                                      +----------+ 
+----------+   +-------+     Physical Line     +-------+   +------+   |          |
| Terminal |<->| Modem |<--------------------->| Modem |<->| UART |<->| Computer |
+----------+   +-------+                       +-------+   +------+   |          |
                                                                      +----------+
登入後複製
  • 左邊的Terminal就是各種各樣的teletype

  • 物理線路兩邊用上了Modem,就是我們常說的“貓”,那是因為後來網路已經慢慢的變發達了,大家可以共享連接了。 (大概推測,可能不對)

  • UART可以理解為將teletype的訊號轉換成電腦能辨識的訊號的裝置

核心TTY子系統

電腦為了支援這些teletype,於是設計了名字叫做TTY的子系統,內部結構如下:

    +-----------------------------------------------+
    |                    Kernel                     |
    |                                 +--------+    |
    |   +--------+   +------------+   |        |    |       +----------------+
    |   |  UART  |   |    Line    |   |  TTY   |<---------->| User process A |
<------>|        |<->|            |<->|        |    |       +----------------+
    |   | driver |   | discipline |   | driver |<---------->| User process B |
    |   +--------+   +------------+   |        |    |       +----------------+
    |                                 +--------+    |
    |                                               |
    +-----------------------------------------------+
登入後複製
  • UART driver對接外面的UART設備

  • Line discipline主要是對輸入和輸出做一些處理,可以理解它是TTY driver的一部分

  • ##TTY driver用來處理各種終端設備

  • 用戶空間的進程透過TTY driver來和終端處理

為了簡單起見,後面的介紹中不再單獨列出UART driver和Line discipline,可以認為它們是TTY driver的一部分

TTY設備

對於每一個終端,TTY driver都會創建一個TTY設備與它對應,如果有多個終端連接過來,那麼看起來就是這個樣子的:

                      +----------------+
                      |   TTY Driver   |
                      |                |
                      |   +-------+    |       +----------------+
 +------------+       |   |       |<---------->| User process A |
 | Terminal A |<--------->| ttyS0 |    |       +----------------+
 +------------+       |   |       |<---------->| User process B |
                      |   +-------+    |       +----------------+
                      |                |
                      |   +-------+    |       +----------------+
 +------------+       |   |       |<---------->| User process C |
 | Terminal B |<--------->| ttyS1 |    |       +----------------+
 +------------+       |   |       |<---------->| User process D |
                      |   +-------+    |       +----------------+
                      |                |
                      +----------------+
登入後複製
當驅動收到一個終端的連接時,就會根據終端的型號和參數創建相應的tty設備(上圖中設備名稱叫ttyS0是因為大部分終端的連接都是串列連接),由於每個終端可能都不一樣,有自己的特殊指令和使用習慣,所以每個tty設備的配置可能都不一樣。例如按delete鍵的時候,有些可能是要刪前面的字符,而有些可能是刪後面的,如果沒配置對,就會導致某些按鍵不是自己想要的行為,這也是我們在使用模擬終端時,如果預設的配置跟我們的習慣不符,需要做一些個人化配置的原因。

後來隨著電腦的不斷發展,teletype這些設備逐漸消失,我們不再需要專門的終端設備了,每個機器都有自己的鍵盤和顯示器,每台機器都可以是其它機器的終端,遠端的操作透過ssh來實現,但是核心TTY驅動這一架構沒有發生變化,我們想要和系統中的進程進行I/O交互,還是需要透過TTY設備,於是出現了各種終端模擬軟體,且模擬的也是常見的幾種終端,如VT100、VT220、XTerm等。

  1. 可以透過指令

    toe -a列出系統支援的所有終端類型

  2. 可以透過指令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裝置互動的結構圖:

   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设备中

从上面可以看出,进程和tty打交道很简单,只要保证后台进程不要读写tty就可以了,即写后台程序时,要将stdin/stdout/stderr重定向到其它地方(当然deamon程序还需要做很多其它处理)。

先抛出两个问题(后面有答案):

  • 当非前端进程组里面的进程(后台进程)往tty设备上写数据时,会发生什么?会输出到outpu上吗?

  • 当非前端进程组里面的进程(后台进程)从tty设备上读数据时,会发生什么?进程会阻塞吗?

TTY是如何被创建的

下面介绍几种常见的情况下tty设备是如何创建的,以及input和output设备都是啥。

键盘显示器直连(终端)

先看图再说话:

                   +-----------------------------------------+
                   |          Kernel                         |
                   |                           +--------+    |       +----------------+ 
 +----------+      |   +-------------------+   |  tty1  |<---------->| User processes |
 | Keyboard |--------->|                   |   +--------+    |       +----------------+
 +----------+      |   | Terminal Emulator |<->|  tty2  |<---------->| User processes |
 | Monitor  |<---------|                   |   +--------+    |       +----------------+
 +----------+      |   +-------------------+   |  tty3  |<---------->| User processes |
                   |                           +--------+    |       +----------------+
                   |                                         |
                   +-----------------------------------------+
登入後複製

键盘、显示器都和内核中的终端模拟器相连,由模拟器决定创建多少tty,比如你在键盘上输入ctrl+alt+F1时,模拟器首先捕获到该输入,然后激活tty1,这样键盘的输入会转发到tty1,而tty1的输出会转发到显示器,同理用输入ctrl+alt+F2,就会切换到tty2。

当模拟器激活tty时如果发现没有进程与之关联,意味着这是第一次打开该tty,于是会启动配置好的进程并和该tty绑定,一般该进程就是负责login的进程。

当切换到tty2后,tty1里面的输出会输出到哪里呢?tty1的输出还是会输出给模拟器,模拟器里会有每个tty的缓存,不过由于模拟器的缓存空间有限,所以下次切回tty1的时候,只能看到最新的输出,以前的输出已经不在了。

不确定这里的终端模拟器对应内核中具体的哪个模块,但肯定有这么个东西存在

SSH远程访问

 +----------+       +------------+
 | Keyboard |------>|            |
 +----------+       |  Terminal  |
 | Monitor  |<------|            |
 +----------+       +------------+
                          |
                          |  ssh protocol
                          |
                          ↓
                    +------------+
                    |            |
                    | ssh server |--------------------------+
                    |            |           fork           |
                    +------------+                          |
                        |   ↑                               |
                        |   |                               |
                  write |   | read                          |
                        |   |                               |
                  +-----|---|-------------------+           |
                  |     |   |                   |           ↓
                  |     ↓   |      +-------+    |       +-------+
                  |   +--------+   | pts/0 |<---------->| shell |
                  |   |        |   +-------+    |       +-------+
                  |   |  ptmx  |<->| pts/1 |<---------->| shell |
                  |   |        |   +-------+    |       +-------+
                  |   +--------+   | pts/2 |<---------->| shell |
                  |                +-------+    |       +-------+
                  |    Kernel                   |
                  +-----------------------------+
登入後複製

这里的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 = <undef>; 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相關的訊號都是可以捕捉的,可以修改它的預設行為

#結束語

本文介紹了常見的tty功能和特點,下一篇將詳細介紹和tty密切相關的進程session id,進程組,job,後台程式等,敬請期待。

相關推薦:《linux影片教學

以上是一起聊聊Linux TTY/PTS及其作用差異的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:segmentfault.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板