這篇文章主要介紹了Linux中TTY/PTS有什么區(qū)別,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
公司主營業(yè)務(wù):成都做網(wǎng)站、網(wǎng)站設(shè)計、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)是一支青春激揚、勤奮敬業(yè)、活力青春激揚、勤奮敬業(yè)、活力澎湃、和諧高效的團隊。公司秉承以“開放、自由、嚴謹、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團隊有機會用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)推出莒縣免費做網(wǎng)站回饋大家。
Linux TTY/PTS概述
當我們在鍵盤上敲下一個字母的時候,到底是怎么發(fā)送到相應(yīng)的進程的呢?我們通過ps、who等命令看到的類似tty1、pts/0這樣的輸出,它們的作用和區(qū)別是什么呢?
在計算機出來以前,人們就已經(jīng)在使用一種叫teletype的設(shè)備,用來相互之間傳遞信息,看起來像下面這樣:
+----------+ Physical Line +----------+ | teletype |<--------------------->| teletype | +----------+ +----------+
兩個teletype之間用線連接起來,線兩端可能也有類似于調(diào)制解調(diào)器之類的設(shè)備(這里將它們忽略),在一端的teletype上敲鍵盤時,相應(yīng)的數(shù)據(jù)會發(fā)送到另一端的teletype,具體功能是干什么的,我也不太了解。(我腦袋里面想到畫面是在一端敲字,另一端打印出來)
這些都是老古董了,完全沒接觸過,所以只能簡單的推測。
等到計算機支持多任務(wù)后,人們想到把這些teletype連到計算機上,作為計算機的終端,從而可以操作計算機。
使用teletype的主要原因有兩個(個人見解):
現(xiàn)實中已經(jīng)存在了大量不同廠商的teletype,可以充分利用現(xiàn)有資源
teletype的相關(guān)網(wǎng)絡(luò)已經(jīng)比較成熟,連起來方便
于是連接就發(fā)展成這樣:
+----------+ +----------+ +-------+ Physical Line +-------+ +------+ | | | Terminal |<->| Modem |<--------------------->| Modem |<->| UART |<->| Computer | +----------+ +-------+ +-------+ +------+ | | +----------+
左邊的Terminal就是各種各樣的teletype
物理線路兩邊用上了Modem,就是我們常說的“貓”,那是因為后來網(wǎng)絡(luò)已經(jīng)慢慢的變發(fā)達了,大家可以共享連接了。(大概推測,可能不對)
UART可以理解為將teletype的信號轉(zhuǎn)換成計算機能識別的信號的設(shè)備
計算機為了支持這些teletype,于是設(shè)計了名字叫做TTY的子系統(tǒng),內(nèi)部結(jié)構(gòu)如下:
+-----------------------------------------------+ | Kernel | | +--------+ | | +--------+ +------------+ | | | +----------------+ | | UART | | Line | | TTY |<---------->| User process A | <------>| |<->| |<->| | | +----------------+ | | driver | | discipline | | driver |<---------->| User process B | | +--------+ +------------+ | | | +----------------+ | +--------+ | | | +-----------------------------------------------+
UART driver對接外面的UART設(shè)備
Line discipline主要是對輸入和輸出做一些處理,可以理解它是TTY driver的一部分
TTY driver用來處理各種終端設(shè)備
用戶空間的進程通過TTY driver來和終端打交道
為了簡單起見,后面的介紹中不再單獨列出UART driver和Line discipline,可以認為它們是TTY driver的一部分
對于每一個終端,TTY driver都會創(chuàng)建一個TTY設(shè)備與它對應(yīng),如果有多個終端連接過來,那么看起來就是這個樣子的:
+----------------+ | TTY Driver | | | | +-------+ | +----------------+ +------------+ | | |<---------->| User process A | | Terminal A |<--------->| ttyS0 | | +----------------+ +------------+ | | |<---------->| User process B | | +-------+ | +----------------+ | | | +-------+ | +----------------+ +------------+ | | |<---------->| User process C | | Terminal B |<--------->| ttyS1 | | +----------------+ +------------+ | | |<---------->| User process D | | +-------+ | +----------------+ | | +----------------+
當驅(qū)動收到一個終端的連接時,就會根據(jù)終端的型號和參數(shù)創(chuàng)建相應(yīng)的tty設(shè)備(上圖中設(shè)備名稱叫ttyS0是因為大部分終端的連接都是串行連接),由于每個終端可能都不一樣,有自己的特殊命令和使用習慣,于是每個tty設(shè)備的配置可能都不一樣。比如按delete鍵的時候,有些可能是要刪前面的字符,而有些可能是刪后面的,如果沒配置對,就會導致某些按鍵不是自己想要的行為,這也是我們在使用模擬終端時,如果默認的配置跟我們的習慣不符,需要做一些個性化配置的原因。
后來隨著計算機的不斷發(fā)展,teletype這些設(shè)備逐漸消失,我們不再需要專門的終端設(shè)備了,每個機器都有自己的鍵盤和顯示器,每臺機器都可以是其它機器的終端,遠程的操作通過ssh來實現(xiàn),但是內(nèi)核TTY驅(qū)動這一架構(gòu)沒有發(fā)生變化,我們想要和系統(tǒng)中的進程進行I/O交互,還是需要通過TTY設(shè)備,于是出現(xiàn)了各種終端模擬軟件,并且模擬的也是常見的幾種終端,如VT100、VT220、XTerm等。
可以通過命令
toe -a
列出系統(tǒng)支持的所有終端類型可以通過命令infocmp來比較兩個終端的區(qū)別,比如
infocmp vt100 vt220
將會輸出vt100和vt220的區(qū)別。
在討論TTY設(shè)備是如何被創(chuàng)建及配置之前,我們先來看看TTY是如何被進程使用的:
#先用tty命令看看當前bash關(guān)聯(lián)到了哪個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里面直接寫數(shù)據(jù)跟寫標準輸出是一樣的效果 dev@dev:~$ echo aaa > /dev/pts/2 aaa
pts也是tty設(shè)備,它們的關(guān)系后面會介紹到
通過上面的lsof可以看出,當前運行的bash和lsof進程的stdin(0u)、stdout(1u)、stderr(2u)都綁定到了這個TTY上。
下面是tty和進程以及I/O設(shè)備交互的結(jié)構(gòu)圖:
Input +--------------------------+ R/W +------+ ----------->| |<---------->| bash | | pts/1 | +------+ <-----------| |<---------->| lsof | Output | Foreground process group | R/W +------+ +--------------------------+
可以把tty理解成一個管道(pipe),在一端寫的內(nèi)容可以從另一端讀取出來,反之亦然。
這里input和output可以簡單的理解為鍵盤和顯示器,后面會介紹在各種情況下input/ouput都連接的什么東西。
tty里面有一個很重要的屬性,叫Foreground process group,記錄了當前前端的進程組是哪一個。process group的概念會在下一篇文章中介紹,這里可以簡單的認為process group里面只有一個進程。
當pts/1收到input的輸入后,會檢查當前前端進程組是哪一個,然后將輸入放到進程組的leader的輸入緩存中,這樣相應(yīng)的leader進程就可以通過read函數(shù)得到用戶的輸入
當前端進程組里面的進程往tty設(shè)備上寫數(shù)據(jù)時,tty就會將數(shù)據(jù)輸出到output設(shè)備上
當在shell中執(zhí)行不同的命令時,前端進程組在不斷的變化,而這種變化會由shell負責更新到tty設(shè)備中
從上面可以看出,進程和tty打交道很簡單,只要保證后臺進程不要讀寫tty就可以了,即寫后臺程序時,要將stdin/stdout/stderr重定向到其它地方(當然deamon程序還需要做很多其它處理)。
先拋出兩個問題(后面有答案):
當非前端進程組里面的進程(后臺進程)往tty設(shè)備上寫數(shù)據(jù)時,會發(fā)生什么?會輸出到outpu上嗎?
當非前端進程組里面的進程(后臺進程)從tty設(shè)備上讀數(shù)據(jù)時,會發(fā)生什么?進程會阻塞嗎?
下面介紹幾種常見的情況下tty設(shè)備是如何創(chuàng)建的,以及input和output設(shè)備都是啥。
先看圖再說話:
+-----------------------------------------+ | Kernel | | +--------+ | +----------------+ +----------+ | +-------------------+ | tty1 |<---------->| User processes | | Keyboard |--------->| | +--------+ | +----------------+ +----------+ | | Terminal Emulator |<->| tty2 |<---------->| User processes | | Monitor |<---------| | +--------+ | +----------------+ +----------+ | +-------------------+ | tty3 |<---------->| User processes | | +--------+ | +----------------+ | | +-----------------------------------------+
鍵盤、顯示器都和內(nèi)核中的終端模擬器相連,由模擬器決定創(chuàng)建多少tty,比如你在鍵盤上輸入ctrl+alt+F1時,模擬器首先捕獲到該輸入,然后激活tty1,這樣鍵盤的輸入會轉(zhuǎn)發(fā)到tty1,而tty1的輸出會轉(zhuǎn)發(fā)到顯示器,同理用輸入ctrl+alt+F2,就會切換到tty2。
當模擬器激活tty時如果發(fā)現(xiàn)沒有進程與之關(guān)聯(lián),意味著這是第一次打開該tty,于是會啟動配置好的進程并和該tty綁定,一般該進程就是負責login的進程。
當切換到tty2后,tty1里面的輸出會輸出到哪里呢?tty1的輸出還是會輸出給模擬器,模擬器里會有每個tty的緩存,不過由于模擬器的緩存空間有限,所以下次切回tty1的時候,只能看到最新的輸出,以前的輸出已經(jīng)不在了。
不確定這里的終端模擬器對應(yīng)內(nèi)核中具體的哪個模塊,但肯定有這么個東西存在
+----------+ +------------+ | 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服務(wù)器打交道,所以肯定要實現(xiàn)ssh的客戶端功能。
這里將建立連接和收發(fā)數(shù)據(jù)分兩條線路解釋,為了描述簡潔,這里以sshd代替ssh服務(wù)器程序:
1.Terminal請求和sshd建立連接
2.如果驗證通過,sshd將創(chuàng)建一個新的session
3.調(diào)用API(posix_openpt())請求ptmx創(chuàng)建一個pts,創(chuàng)建成功后,sshd將得到和ptmx關(guān)聯(lián)的fd,并將該fd和session關(guān)聯(lián)起來。
#pty(pseudo terminal device)由兩部分構(gòu)成,ptmx是master端,pts是slave端, #進程可以通過調(diào)用API請求ptmx創(chuàng)建一個pts,然后將會得到連接到ptmx的讀寫fd和一個新創(chuàng)建的pts, #ptmx在內(nèi)部會維護該fd和pts的對應(yīng)關(guān)系,隨后往這個fd的讀寫會被ptmx轉(zhuǎn)發(fā)到對應(yīng)的pts。 #這里可以看到sshd已經(jīng)打開了/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創(chuàng)建shell進程,將新創(chuàng)建的pts和shell綁定
1.Terminal收到鍵盤的輸入,Terminal通過ssh協(xié)議將數(shù)據(jù)發(fā)往sshd
2.sshd收到客戶端的數(shù)據(jù)后,根據(jù)它自己管理的session,找到該客戶端對應(yīng)的關(guān)聯(lián)到ptmx上的fd
3.往找到的fd上寫入客戶端發(fā)過來的數(shù)據(jù)
4.ptmx收到數(shù)據(jù)后,根據(jù)fd找到對應(yīng)的pts(該對應(yīng)關(guān)系由ptmx自動維護),將數(shù)據(jù)包轉(zhuǎn)發(fā)給對應(yīng)的pts
5.pts收到數(shù)據(jù)包后,檢查綁定到自己上面的當前前端進程組,將數(shù)據(jù)包發(fā)給該進程組的leader
6.由于pts上只有shell,所以shell的read函數(shù)就收到了該數(shù)據(jù)包
7.shell對收到的數(shù)據(jù)包進行處理,然后輸出處理結(jié)果(也可能沒有輸出)
8.shell通過write函數(shù)將結(jié)果寫入pts
9.pts將結(jié)果轉(zhuǎn)發(fā)給ptmx
10.ptmx根據(jù)pts找到對應(yīng)的fd,往該fd寫入結(jié)果
11.sshd收到該fd的結(jié)果后,找到對應(yīng)的session,然后將結(jié)果發(fā)給對應(yīng)的客戶端
+----------+ +------------+ | Keyboard |------>| | +----------+ | Terminal |--------------------------+ | Monitor |<------| | fork | +----------+ +------------+ | | ↑ | | | | write | | read | | | | +-----|---|-------------------+ | | | | | ↓ | ↓ | +-------+ | +-------+ | +--------+ | pts/0 |<---------->| shell | | | | +-------+ | +-------+ | | ptmx |<->| pts/1 |<---------->| shell | | | | +-------+ | +-------+ | +--------+ | pts/2 |<---------->| shell | | +-------+ | +-------+ | Kernel | +-----------------------------+
為了簡化起見,本篇不討論Linux下圖形界面里Terminal程序是怎么和鍵盤、顯示器交互的。
這里和上面的不同點就是,這里的Terminal不需要實現(xiàn)ssh客戶端,但需要把ssh服務(wù)器要干的活也干了(當然ssh通信相關(guān)的除外)。
常用Linux的同學應(yīng)該對screen和tmux不陌生,通過它們啟動的進程,就算網(wǎng)絡(luò)斷開了,也不會受到影響繼續(xù)執(zhí)行,下次連上去時還能看到進程的所有輸出,還能繼續(xù)接著干活。
這里以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 |<--------------------------------------------+ | | +-------------+
系統(tǒng)中的ptmx只有一個,上圖中畫出來了兩個,目的是為了表明tmux服務(wù)器和sshd都用ptmx,但它們之間又互不干涉。
這種情況要稍微復雜一點,不過原理都是一樣的,前半部分和普通ssh的方式是一樣的,只是pts/0關(guān)聯(lián)的前端進程不是shell了,而是變成了tmux客戶端,所以ssh客戶端發(fā)過來的數(shù)據(jù)包都會被tmux客戶端收到,然后由tmux客戶端轉(zhuǎn)發(fā)給tmux服務(wù)器,而tmux服務(wù)器干的活和ssh的類似,也是維護一堆的session,為每個session創(chuàng)建一個pts,然后將tmux客戶端發(fā)過來的數(shù)據(jù)轉(zhuǎn)發(fā)給相應(yīng)的pts。
由于tmux服務(wù)器只和tmux客戶端打交道,和sshd沒有關(guān)系,當終端和sshd的連接斷開時,雖然pts/0會被關(guān)閉,和它相關(guān)的shell和tmux客戶端也將被kill掉,但不會影響tmux服務(wù)器,當下次再用tmux客戶端連上tmux服務(wù)器時,看到的還是上次的內(nèi)容。
從上面的流程中應(yīng)該可以看出來了,對用戶空間的程序來說,他們沒有區(qū)別,都是一樣的;從內(nèi)核里面來看,pts的另一端連接的是ptmx,而tty的另一端連接的是內(nèi)核的終端模擬器,ptmx和終端模擬器都只是負責維護會話和轉(zhuǎn)發(fā)數(shù)據(jù)包;再看看ptmx和內(nèi)核終端模擬器的另一端,ptmx的另一端連接的是用戶空間的應(yīng)用程序,如sshd、tmux等,而內(nèi)核終端模擬器的另一端連接的是具體的硬件,如鍵盤和顯示器。
先先來看看當前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的參數(shù),用法請參考
man stty
只要是有權(quán)限的程序,都可以通過Linux提供的API來修改TTY的配置,下面介紹一些常見的的配置項。
這個配置一般由終端控制,當終端的窗口大小發(fā)生變化時,需要通過一定的手段修改該配置,比如ssh協(xié)議里面就有修改窗口大小的參數(shù),sshd收到客戶端的請求后,會通過API修改tty的這個參數(shù),然后由tty通過信號SIGWINCH通知前端程序(比如shell或者vim),前端程序收到信號后,再去讀tty的這個參數(shù),然后就知道如何調(diào)整自己的輸出排版了。
tty除了在終端和前端進程之間轉(zhuǎn)發(fā)數(shù)據(jù)之外,還支持很多控制命令,比如終端輸入了CTRL+C,那么tty不會將該輸入串轉(zhuǎn)發(fā)給前端進程,而是將它轉(zhuǎn)換成信號SIGINT發(fā)送給前端進程。這個就是用來配置控制命令對應(yīng)的輸入組合的,比如我們可以配置“intr = ^E”表示用CTRL+E代替CTRL+C。
這是兩個特殊的控制命令,估計經(jīng)常有人會碰到,在鍵盤上不小心輸入CTRL+S后,終端沒反應(yīng)了,即沒輸出,也不響應(yīng)任何輸入。這是因為這個命令會告訴TTY暫停,阻塞所有讀寫操作,即不轉(zhuǎn)發(fā)任何數(shù)據(jù),只有按了CTRL+Q后,才會繼續(xù)。這個功能應(yīng)該是歷史遺留,以前終端和服務(wù)器之間沒有流量控制功能,所以有可能服務(wù)器發(fā)送數(shù)據(jù)過快,導致終端處理不過來,于是需要這樣一個命令告訴服務(wù)器不要再發(fā)了,等終端處理完了后在通知服務(wù)器繼續(xù)。
該命令現(xiàn)在比較常用的一個場景就是用tail -f
命令監(jiān)控日志文件的內(nèi)容時,可以隨時按CTRL+S讓屏幕停止刷新,看完后再按CTRL+Q讓它繼續(xù)刷,如果不這樣的話,需要先CTRL+C退出,看完后在重新運行tail -f
命令。
在終端輸入字符的時候,之所以我們能及時看到我們輸入的字符,那是因為TTY在收到終端發(fā)過去的字符后,會先將字符原路返回一份,然后才交給前端進程處理,這樣終端就能及時的顯示輸入的字符。echo就是用來控制該功能的配置項,如果是-echo的話表示disable echo功能。
如果你在shell中運行程序的時候,后面添加了&,比如./myapp &
,這樣myapp這個進程就會在后臺運行,但如果這個進程繼續(xù)往tty上寫數(shù)據(jù)呢?這個參數(shù)就用來控制是否將輸出轉(zhuǎn)發(fā)給終端,也即結(jié)果會不會在終端顯示,這里“-tostop”表示會輸出到終端,如果配置為“tostop”的話,將不輸出到終端,并且tty會發(fā)送信號SIGTTOU給myapp,該信號的默認行為是將暫停myapp的執(zhí)行。
除了上面介紹配置時提到的SIGINT,SIGTTOU,SIGWINCHU外,還有這么幾個跟TTY相關(guān)的信號
當后臺進程讀tty時,tty將發(fā)送該信號給相應(yīng)的進程組,默認行為是暫停進程組中進程的執(zhí)行。暫停的進程如何繼續(xù)執(zhí)行呢?請參考下一篇文章中的SIGCONT。
當tty的另一端掛掉的時候,比如ssh的session斷開了,于是sshd關(guān)閉了和ptmx關(guān)聯(lián)的fd,內(nèi)核將會給和該tty相關(guān)的所有進程發(fā)送SIGHUP信號,進程收到該信號后的默認行為是退出進程。
終端輸入CTRL+Z時,tty收到后就會發(fā)送SIGTSTP給前端進程組,其默認行為是將前端進程組放到后端,并且暫停進程組里所有進程的執(zhí)行。
跟tty相關(guān)的信號都是可以捕獲的,可以修改它的默認行為
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Linux中TTY/PTS有什么區(qū)別”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學習!