linux一切皆檔案之tty字元裝置(深入理解sshd建立pty的過程) (五)
一、知識準備
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頁):
ofollow,noindex" target="_blank">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 root89410 Nov25 ?00:00:00 /usr/sbin/sshd -D root31268940 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
sshd 894
建立了一個子程序 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 root31268940 03:16 ?00:00:00 sshd: root@pts/0 root312831260 03:16 pts/300: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 |--------+ |+---------++-----------+| |||| |+-----------+| +---------------------------------------+
執行命令:
ls
命令,通過TCP連線來到
3126
,
3126
將
ls
寫入PTM檔案描述符
8
(5) /dev/ptmx
查詢到關聯記錄 PTM: 8
對應PTS: /dev/pts/0
,把 ls
轉發到 /dev/pts/0
當中
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至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...