0x01 什么是標(biāo)準(zhǔn)輸入輸出
準(zhǔn)確來(lái)講,該問(wèn)題應(yīng)該是什么是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出。
在linux下,它們其實(shí)就是三個(gè)文件描述符,其中標(biāo)準(zhǔn)輸入是0,標(biāo)準(zhǔn)輸出是1,標(biāo)準(zhǔn)錯(cuò)誤輸出是2。
該定義我們也可以在各個(gè)編程語(yǔ)言中看到。
比如,這是rust標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出的定義:
其中,libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO對(duì)應(yīng)的值分別為:
比如,這是go標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出的定義:
其中,syscall.Stdin, syscall.Stdout, syscall.Stderr對(duì)應(yīng)的值分別為:
其他編程語(yǔ)言中也有類似的定義,感興趣的話可以自己看下。
0x02 為什么是0/1/2
這只是一種約定,linux內(nèi)核并不會(huì)對(duì)0/1/2文件描述符做任何特殊處理,之所以定義為0/1/2,是因?yàn)檫M(jìn)程文件描述符是從0開始依次遞增的,所以就用了前三個(gè)。
0x03 為什么要有這種約定
這是因?yàn)椋挥杏辛诉@種約定,linux世界的各個(gè)組件之間才能相互合作。
比如,在terminal emulator,即終端模擬器,中啟動(dòng)bash子進(jìn)程時(shí),因?yàn)橹纀ash的標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤輸出分別是0/1/2,那它就可以把bash子進(jìn)程的0/1/2文件描述符都指向自己,這樣當(dāng)bash做標(biāo)準(zhǔn)輸入輸出操作時(shí),它其實(shí)都是在和terminal emulator交互。
又比如,當(dāng)bash要執(zhí)行某個(gè)程序時(shí),因?yàn)樗滥繕?biāo)程序所使用的編程語(yǔ)言,把標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤輸出分別定義為0/1/2,這樣當(dāng)我們要求bash把目標(biāo)程序的標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤輸出重定向到其他文件時(shí),bash只需要修改目標(biāo)程序進(jìn)程的0/1/2文件描述符的文件指向就好了,非常簡(jiǎn)單。
諸如此類。
0x04 為什么是三個(gè)文件描述符而不是一個(gè)
上面我們講到,bash進(jìn)程的0/1/2文件描述符都指向terminal emulator,其實(shí)這個(gè)說(shuō)法并不準(zhǔn)確,它們真正指向的是terminal emulator向內(nèi)核分配的pty數(shù)據(jù)通道的slave端,這個(gè)在上篇文章?為什么Ctrl-C會(huì)中斷當(dāng)前運(yùn)行程序 中有詳細(xì)講過(guò)。
但不管怎樣,bash進(jìn)程的0/1/2文件描述符,都是指向內(nèi)核中的同一個(gè)文件實(shí)例,而該文件實(shí)例最終又指向了terminal emulator。
同樣,bash中運(yùn)行的其他程序,默認(rèn)情況下,其進(jìn)程的0/1/2文件描述符,是繼承自bash的,所以也同樣都指向了同一個(gè)terminal emulator。
既然進(jìn)程的0/1/2文件描述符,默認(rèn)都指向同一個(gè)terminal emulator,那為什么不只用一個(gè)文件描述符來(lái)表示它們的標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤輸出,而是要用三個(gè)呢,這不是浪費(fèi)了進(jìn)程的文件描述符嗎?
原因也很簡(jiǎn)單,這樣做更靈活。
雖然默認(rèn)情況下,進(jìn)程的0/1/2都指向了同一個(gè)terminal emulator,但它給了我們一種能力,使我們可以把進(jìn)程的標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤輸出指向不同的文件。
比如,我們?cè)赽ash中執(zhí)行 ./hello > a.log,就會(huì)把hello程序的標(biāo)準(zhǔn)輸出重定向到了a.log里,而標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)錯(cuò)誤輸出,還是指向的原來(lái)的terminal emulator。
0x05 什么是文件描述符
在linux世界里,一切皆文件。
比如我們創(chuàng)建一個(gè)socket,創(chuàng)建一個(gè)epoll實(shí)例,又或者是打開一個(gè)普通文件,所有的這些操作創(chuàng)建的目標(biāo)對(duì)象,在內(nèi)核里最終都是以一個(gè)file實(shí)例來(lái)表示的,該file實(shí)例會(huì)存放到進(jìn)程的一個(gè)文件數(shù)組中,而該數(shù)組的下標(biāo),就是文件描述符,即fd。
該fd值,會(huì)隨著我們使用的系統(tǒng)調(diào)用,返回給用戶程序,當(dāng)用戶程序想對(duì)目標(biāo)文件進(jìn)行各種操作時(shí),執(zhí)行該操作對(duì)應(yīng)的系統(tǒng)調(diào)用,把fd值再傳給內(nèi)核,這樣內(nèi)核就可以根據(jù)該fd找到對(duì)應(yīng)的file實(shí)例,進(jìn)而就可以執(zhí)行對(duì)應(yīng)的操作了。
0x06 0/1/2具體指向哪里
一個(gè)進(jìn)程的0/1/2文件描述符具體指向什么文件,是受執(zhí)行環(huán)境及命令參數(shù)影響的,下面我們就舉幾個(gè)常見的例子,再配合一些圖,來(lái)看看0/1/2具體指向哪里。
0x07 在terminal emulator中執(zhí)行hello程序
上圖中,我們?cè)趖erminal emulator中執(zhí)行hello程序,該操作產(chǎn)生的各種數(shù)據(jù),是按圖中實(shí)線箭頭方向流動(dòng)的。
我們?cè)趖erminal emulator中輸入./hello命令,該命令沿著內(nèi)核pty數(shù)據(jù)通道,到達(dá)bash的標(biāo)準(zhǔn)輸入。
bash從標(biāo)準(zhǔn)輸入中讀取./hello命令,然后調(diào)用fork函數(shù),新建一個(gè)子進(jìn)程,用于執(zhí)行hello程序。
hello程序執(zhí)行時(shí),會(huì)先向標(biāo)準(zhǔn)輸出寫hello字符串,然后再向標(biāo)準(zhǔn)錯(cuò)誤輸出寫world字符串。
這兩個(gè)字符串會(huì)沿著內(nèi)核pty數(shù)據(jù)通道,到達(dá)terminal emulator的pty master fd。
terminal emulator從pty master fd中讀取這些字符串,并顯示在界面上。
這種情況下,hello進(jìn)程的0/1/2文件描述符,都是指向內(nèi)核pty數(shù)據(jù)通道的slave端,并且通過(guò)該pty數(shù)據(jù)通道和terminal emulator交互。
0x08 在ssh中執(zhí)行hello程序
上圖中,我們先用ssh命令登陸到機(jī)器2,然后再在terminal emulator中輸入./hello命令,圖中實(shí)線表示該操作產(chǎn)生數(shù)據(jù)的流動(dòng)方向。
當(dāng)我們?cè)趖erminal emulator中輸入./hello命令后,該命令會(huì)沿著內(nèi)核pty數(shù)據(jù)通道,到達(dá)ssh進(jìn)程的標(biāo)準(zhǔn)輸入。
ssh進(jìn)程從標(biāo)準(zhǔn)輸入中讀取到./hello命令,然后將其寫到socket fd里。
然后,該命令會(huì)沿著socket fd指向的tcp連接,到達(dá)機(jī)器2的對(duì)應(yīng)socket端。
在機(jī)器2上,sshd進(jìn)程從它的socket fd中讀取到./hello命令,然后將其寫到pty master fd中。
這樣,該命令又會(huì)沿著機(jī)器2的內(nèi)核pty數(shù)據(jù)通道,到達(dá)bash進(jìn)程的標(biāo)準(zhǔn)輸入。
機(jī)器2上的bash進(jìn)程,從標(biāo)準(zhǔn)輸入中讀到該命令,然后調(diào)用fork函數(shù),創(chuàng)建一個(gè)子進(jìn)程,用于執(zhí)行hello程序。
hello程序執(zhí)行時(shí),會(huì)寫hello到標(biāo)準(zhǔn)輸出,寫world到標(biāo)準(zhǔn)錯(cuò)誤輸出,這兩個(gè)字符串又會(huì)沿著機(jī)器2的內(nèi)核pty數(shù)據(jù)通道,到達(dá)sshd進(jìn)程的pty master fd。
sshd進(jìn)程從pty master fd中讀取到hello進(jìn)程輸出的內(nèi)容,并寫到socket fd里。
該數(shù)據(jù)又沿著socket fd指向的tcp連接,最終會(huì)到達(dá)機(jī)器1對(duì)應(yīng)的socket端。
機(jī)器1中的ssh進(jìn)程,從socket fd里讀取到hello程序的輸出內(nèi)容,并將其寫到標(biāo)準(zhǔn)輸出。
該數(shù)據(jù)會(huì)沿著機(jī)器1的內(nèi)核pty數(shù)據(jù)通道,到達(dá)terminal emulator的pty master fd。
terminal emulator從pty master fd中讀取到對(duì)應(yīng)的數(shù)據(jù),最終將其顯示在界面上。
以上就是這張圖的完整流程。
在這種情況下,hello進(jìn)程的0/1/2文件描述符,指向的都是機(jī)器2上的pty數(shù)據(jù)通道的slave端,該端會(huì)再經(jīng)過(guò)一系列的鏈路,最終和機(jī)器1上的terminal emulator連接起來(lái)。
也就是說(shuō),我們?cè)跈C(jī)器1上的terminal emulator中輸入內(nèi)容,最終會(huì)被機(jī)器2上的hello進(jìn)程從標(biāo)準(zhǔn)輸入讀出來(lái),而機(jī)器2上hello進(jìn)程的各種輸出,最終會(huì)被傳送到機(jī)器1上的terminal emulator進(jìn)程,并在其界面上顯示出來(lái)。
0x09 在ssh中執(zhí)行hello程序并重定向標(biāo)準(zhǔn)輸出
這種情況和上面講的情況基本類似,只是額外把hello進(jìn)程的標(biāo)準(zhǔn)輸出重定向到了a.log文件,這里就不多講了,具體可見圖中內(nèi)容。
編輯:黃飛
?
評(píng)論