一、知識準備 1、在linux中,一切皆為文件,所有不同種類的類型都被抽象成文件(比如:塊設備,socket套接字,pipe隊列) 2、操作這些不同的類型就像操作文件一樣,比如增刪改查等 3、塊設備支持隨機訪問,而字元設備只能依據先後順序來讀取數據。最典型的字元設備就是tty 二、環境準備 | 組件 ...
一、知識準備
1、在linux中,一切皆為文件,所有不同種類的類型都被抽象成文件(比如:塊設備,socket套接字,pipe隊列)
2、操作這些不同的類型就像操作文件一樣,比如增刪改查等
3、塊設備支持隨機訪問,而字元設備只能依據先後順序來讀取數據。最典型的字元設備就是tty
二、環境準備
組件 | 版本 |
---|---|
OS | CentOS Linux release 7.5.1804 |
三、什麼是tty?
根據史料記載:
An ASR33 Teletype - origin of the abbreviation tty.
tty來源一種電傳印表機(teletype),就像這樣:
● 敲擊鍵盤輸入不同的字元,然後由印表機將字元列印在紙上
● 歷史不斷在往前發展,出現了電腦之後,電腦模擬了teletype的模式:通過外部終端輸入,將輸入的字元列印在屏幕上
● 在teletype與電腦之間用串口相連,並且在電腦上通過信號轉換(模擬信號轉換為數字信號),讓電腦能夠識別,從而操作電腦
● 由於電腦廠商眾多,每個廠商都有自己風格的輸入設備,所以電腦為了相容這些設備,開發了內核tty模塊
+-----------------+
| |
+--------+ | +-------------+ |
|teletype|-----------------> |serial | |
+--------+ | |communication| |
| +-----+-------+ |
| | |
| v |
| +----------+ | +----------+
| |tty driver| |------->| display |
| +----------+ | +----------+
| |
|computer |
+-----------------+
四、tty設備文件
登陸到操作系統(不使用SSH協議,而使用控制台直接登陸),首先查看當前進程號所使用的tty
[root@localhost ~]# tty
/dev/tty1
[root@localhost ~]# ls -l /dev/tty1
crw--w---- 1 root tty 4, 1 Nov 20 23:24 /dev/tty1
當前所使用的是/dev/tty1,並且tty1也分配了主設備號與次設備號(關於主設備號與次設備號,請看之前的文章:塊設備文件)
查看進程打開的描述符
[root@localhost ~]# echo $$
5598
[root@localhost ~]# ls -l /proc/5598/fd
total 0
lrwx------ 1 root root 64 Nov 19 22:23 0 -> /dev/tty1
lrwx------ 1 root root 64 Nov 19 22:23 1 -> /dev/tty1
lrwx------ 1 root root 64 Nov 19 22:23 2 -> /dev/tty1
lrwx------ 1 root root 64 Nov 19 22:23 255 -> /dev/tty1
進程打開了4個文件描述符,這四個文件描述符都是/dev/tty1,他們的作用分別是:
0
:標準輸入
1
:標準輸出
2
:標準錯誤
255
:這個比較特殊,主要用於當tty重置的時候對0,1,2
的一份複製(個人觀點是對tty之前的歷史信息作為一份複製)
更多的信息,請拜讀大神的書《Shell Scripting: Expert Recipes for Linux, Bash, and more》,這裡是鏈接(大概在267頁):
https://doc.lagout.org/operating%20system%20/linux/Commands%20and%20Shell%20Programming/Shell%20Scripting.pdf
五、ssh登陸之後的tty
剛纔介紹的都是操作系統提供的控制台登陸之後的情況,如果用ssh服務登陸之後會產生什麼情況呢?
首先介紹一個非常重要的概念,偽終端pty:
● pty是一對虛擬的字元設備,提供雙向通信。pty一般由master與slave組成
● pty的出現是為了滿足現在的登陸需求:網路登陸(ssh登陸、telnet登陸等)、Xwindow等
● 歷史上有兩套介面標準:分別是BSD與unix98,當前大多數pts都是基於unix98標準來實現的
● unix98的工作流程:
(1)進程對/dev/ptmx
調用open(),返回pseudoterminal master(PTM)的文件描述符,並且在/dev/pts
下創建pseudoterminal slave(PTS): /dev/pts/0
(2)調用grantpt()修改PTS的文件許可權;調用unlockpt()對PTS解鎖;最後調用slavename()得到PTS文件名字
(3)此時,PTM與PTS都已經正常打開,並且建立一條通道,兩端分別連接PTM與PTS
(4)進程對PTM寫的數據可以從PTS讀出來,反之亦然
下麵重點介紹一下基於unix98實現的sshd pty(主要分為登陸階段和執行命令階段):
登陸:
(1)當進程ssh client請求與sshd建立登陸連接的時候,經過TCP握手以及tls握手之後,確認是一個合法的請求,sshd會fork()一個子進程出來專門服務於這條連接
[root@localhost ~]# ps -ef | grep sshd
root 894 1 0 Nov25 ? 00:00:00 /usr/sbin/sshd -D
root 3126 894 0 Nov25 ? 00:00:00 sshd: root@pts/0
(2)子進程3126
對/dev/ptmx
調用open(),得到PTM的文件描述符以及PTS的文件名
#這裡使用strace跟蹤sshd主進程和它創建的子進程,然後打開另外一個shell登陸伺服器
[root@localhost ~]# strace -p 894 -ff -o sshd
strace: Process 894 attached
strace: Process 3126 attached
strace: Process 3127 attached
strace: Process 3128 attached
strace: Process 3129 attached
strace: Process 3130 attached
strace: Process 3131 attached
strace: Process 3132 attached
strace: Process 3133 attached
strace: Process 3134 attached
strace: Process 3135 attached
strace: Process 3136 attached
strace: Process 3137 attached
strace: Process 3138 attached
strace: Process 3139 attached
strace: Process 3140 attached
[root@localhost ~]# grep ptmx ./sshd.*
./sshd.3126:open("/dev/ptmx", O_RDWR) = 8
sshd894
創建了一個子進程3126
用來處理這條TCP連接。進程對/dev/ptmx
調用open(),得到PTM的文件描述符8
(2)子進程3126
在/dev/pts
下創建了一個字元設備文件/dev/pts/0
,8
與/dev/pts/0
成為一對master/slave
(3)子進程3126
會再fork()一個子進程3128
,子進程3128
打開/dev/pts/0
3個描述符(標準輸入,標準輸出,標準錯誤),並且執行操作系統預設的shell(本文中bash)
[root@localhost ~]# ps -ef | grep 3126
root 3126 894 0 03:16 ? 00:00:00 sshd: root@pts/0
root 3128 3126 0 03:16 pts/3 00:00:00 -bash
[root@localhost ~]# ls -l /proc/3128/fd
total 0
lrwx------ 1 root root 64 Nov 26 03:16 0 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 26 03:16 1 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 26 03:16 2 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 26 03:22 255 -> /dev/pts/0
至此,通信流程大概是這樣:
+----------------+
+------------+ | |
| ssh client +---------->| sshd |
+----+-------+ | |
| +--------+-------+
| |
| |
| fork()
| |
| |
| v
| +----+-----+ fork() +----------+ +-----+
+---------------------->|pid: 3126 |-------------->|pid: 3128 |----->|bash |
+-+--------+ +----------+ +-----+
| ^
| |
+-------+ |
+------|--------------------------------+ |
| | +-----------+ | |
| v | | | |
| +---------+ fd=8 +-----------+ | |
| |/dev/ptmx|---------->|/dev/pts/0 |--------+
| +---------+ +-----------+ |
| | | |
| +-----------+ |
+---------------------------------------+
執行命令:
(4)當ssh client發出一個ls
命令,通過TCP連接來到3126
,3126
將ls
寫入PTM文件描述符8
(5)/dev/ptmx
查詢到關聯記錄 PTM:8
對應PTS:/dev/pts/0
,把ls
轉發到/dev/pts/0
當中
(6)3128
從0 -> /dev/pts/0
中讀取之後執行ls
(7)ls
返回結果之後寫入1 -> /dev/pts/0
,然後根據關聯記錄回寫到/dev/ptmx
(8)3126
從/dev/ptmx
讀取之後返回到ssh client
六、參考資料
http://man7.org/linux/man-pages/man7/pty.7.html
http://man7.org/linux/man-pages/man4/pts.4.html
http://osr600doc.sco.com/en/SDK_sysprog/_Pseudo-tty_Drivers_em_ptm_and_p.html
https://unix.stackexchange.com/questions/79334/how-does-a-linux-terminal-work
至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...